{"openapi":"3.1.0","info":{"title":"Snapdex API","version":"1.4.0","description":"Index product images and find products by photo. Create a collection, add products with their images, wait for indexing, then search with any photo. Every mutation that touches the index is asynchronous: it returns an indexing run you can poll."},"servers":[{"url":"https:\/\/snapdex.dev\/api\/v1"}],"security":[{"apiKey":[]}],"tags":[{"name":"Collections","description":"A collection is one searchable catalogue with its own index. Products of a collection with `has_groups` can share a `group_id`; searches then report one result per group."},{"name":"Products","description":"What a search returns. Identified by your own `id`, with an optional name, group and free-form metadata."},{"name":"Images","description":"Photos of a product. New images are indexed incrementally in the background; the live index keeps answering searches meanwhile."},{"name":"Indexing runs","description":"Background work on the index. Imports add new images to the live index; a full re-index builds a fresh index and swaps it in when done."},{"name":"Search","description":"Match a photo against a collection."},{"name":"Webhooks","description":"Instead of polling runs, set a `webhook_url` on a collection and we POST an `indexing_run.finished` event to it when any import, retry or re-index completes. Every request is signed; see the Webhooks guide."},{"name":"Usage","description":"Your plan, its quotas and how much of them you have used."}],"paths":{"\/collections":{"get":{"tags":["Collections"],"operationId":"listCollections","summary":"List collections","description":"All collections of the team that owns the API key, alphabetically.","responses":{"200":{"description":"The collections.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Collection"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections -H \"Authorization: Bearer $KEY\""},"post":{"tags":["Collections"],"operationId":"createCollection","summary":"Create a collection","description":"Creates an empty collection and its vector index. `has_groups` is fixed at creation.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CollectionInput"},"example":{"name":"Sneakers","description":"All sneaker product photos","has_groups":true}}}},"responses":{"201":{"description":"The new collection.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Collection"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"402":{"$ref":"#\/components\/responses\/QuotaExceeded"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application\/json\" -d '{\"name\":\"Sneakers\",\"has_groups\":true}'"}},"\/collections\/{collection}":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"get":{"tags":["Collections"],"operationId":"getCollection","summary":"Get a collection","description":"The collection with its product and image counts.","responses":{"200":{"description":"The collection.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Collection"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7 -H \"Authorization: Bearer $KEY\""},"patch":{"tags":["Collections"],"operationId":"updateCollection","summary":"Update a collection","description":"Change the name, description, webhook or filterable keys. `has_groups` cannot be changed after creation.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CollectionInput"},"example":{"description":"Sneakers, spring catalogue"}}}},"responses":{"200":{"description":"The updated collection.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Collection"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X PATCH $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7 -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application\/json\" -d '{\"description\":\"Spring catalogue\"}'"},"delete":{"tags":["Collections"],"operationId":"deleteCollection","summary":"Delete a collection","description":"Deletes the collection, all its products and images, and the index. This cannot be undone.","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X DELETE $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7 -H \"Authorization: Bearer $KEY\""}},"\/collections\/{collection}\/products":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"get":{"tags":["Products"],"operationId":"listProducts","summary":"List products","description":"Paginated products of the collection, oldest first. Filter with `search` (matches id and name) or `group_id`.","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Substring of the product id or name."},{"name":"group_id","in":"query","schema":{"type":"string"},"description":"Only products in this group."},{"name":"page","in":"query","schema":{"type":"integer","minimum":1}},{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"A page of products.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/Paginated"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Product"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl \"$BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products?search=red&per_page=20\" -H \"Authorization: Bearer $KEY\""},"post":{"tags":["Products"],"operationId":"createProduct","summary":"Create a product","description":"Create the product first, then add its images. `id` is your own identifier and must be unique within the collection; `group_id` is only stored for collections with `has_groups`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ProductInput"},"example":{"id":"sku-100","name":"Red runner","group_id":"runner","metadata":{"brand":"Acme","colour":"red"}}}}},"responses":{"201":{"description":"The new product.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Product"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application\/json\" -d '{\"id\":\"sku-100\",\"name\":\"Red runner\",\"metadata\":{\"brand\":\"Acme\"}}'"}},"\/collections\/{collection}\/products\/{product}":{"parameters":[{"$ref":"#\/components\/parameters\/collection"},{"$ref":"#\/components\/parameters\/product"}],"get":{"tags":["Products"],"operationId":"getProduct","summary":"Get a product","description":"The product with its metadata and the list of its images and their indexing status.","responses":{"200":{"description":"The product, including `image_list`.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Product"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products\/sku-100 -H \"Authorization: Bearer $KEY\""},"patch":{"tags":["Products"],"operationId":"updateProduct","summary":"Update a product","description":"Partial update: only the fields you send change. Sending `metadata` replaces the whole map. Changing `group_id` only affects search after a full re-index, because the group is stored in the index; the response then carries a `warning`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ProductInput"},"example":{"name":"Red runner v2","metadata":{"brand":"Acme","season":"SS26"}}}}},"responses":{"200":{"description":"The updated product.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Product"},"warning":{"type":"string"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X PATCH $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products\/sku-100 -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application\/json\" -d '{\"name\":\"Red runner v2\"}'"},"delete":{"tags":["Products"],"operationId":"deleteProduct","summary":"Delete a product","description":"Deletes the product and its images; they stop matching immediately.","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X DELETE $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products\/sku-100 -H \"Authorization: Bearer $KEY\""}},"\/collections\/{collection}\/products\/{product}\/images":{"parameters":[{"$ref":"#\/components\/parameters\/collection"},{"$ref":"#\/components\/parameters\/product"}],"get":{"tags":["Images"],"operationId":"listProductImages","summary":"List a product's images","description":"All images of the product with their indexing status.","responses":{"200":{"description":"The images.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Image"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products\/sku-100\/images -H \"Authorization: Bearer $KEY\""},"post":{"tags":["Images"],"operationId":"addProductImages","summary":"Add images to a product","description":"Upload files (multipart `files[]`) or pass public `urls` (JSON) that we download. The images are stored and an import run indexes them in the background; existing images are untouched. Poll the returned run, or the product, to see when they are `indexed`. JPG, PNG or WebP, up to 15 MB and 100 images per request.","requestBody":{"required":true,"content":{"multipart\/form-data":{"schema":{"type":"object","properties":{"files[]":{"type":"array","items":{"type":"string","format":"binary"}}},"required":["files[]"]}},"application\/json":{"schema":{"type":"object","properties":{"urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":100}},"required":["urls"]},"example":{"urls":["https:\/\/cdn.example.com\/sku-100-front.jpg","https:\/\/cdn.example.com\/sku-100-side.jpg"]}}}},"responses":{"202":{"description":"Accepted: the images and the indexing run that processes them.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Image"}},"run":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"402":{"$ref":"#\/components\/responses\/QuotaExceeded"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/products\/sku-100\/images -H \"Authorization: Bearer $KEY\" -F \"files[]=@front.jpg\" -F \"files[]=@side.jpg\""}},"\/collections\/{collection}\/images\/{image}":{"parameters":[{"$ref":"#\/components\/parameters\/collection"},{"name":"image","in":"path","required":true,"schema":{"type":"string"},"description":"The image id (a ULID, e.g. `01k6h3t7v2m9q8x4r5n0c1b2d3`)."}],"get":{"tags":["Images"],"operationId":"getImage","summary":"Get an image","description":"The indexing status of one image, including the error when it failed.","responses":{"200":{"description":"The image.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Image"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/images\/01k6h3t7v2m9q8x4r5n0c1b2d3 -H \"Authorization: Bearer $KEY\""},"delete":{"tags":["Images"],"operationId":"deleteImage","summary":"Delete an image","description":"Removes the image from the index and from storage.","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X DELETE $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/images\/01k6h3t7v2m9q8x4r5n0c1b2d3 -H \"Authorization: Bearer $KEY\""}},"\/collections\/{collection}\/imports":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"post":{"tags":["Images"],"operationId":"importCsv","summary":"Import products and images from CSV","description":"Bulk-create products and their images from CSV rows with a header: `image_url`, `product_id`, and optionally `product_name` and `group_id`. Unknown product ids are created, known ones get the images added. Up to 10,000 rows per request. Send the CSV as a multipart `csv` file or as a `rows` string.","requestBody":{"required":true,"content":{"multipart\/form-data":{"schema":{"type":"object","properties":{"csv":{"type":"string","format":"binary"}},"required":["csv"]}},"application\/json":{"schema":{"type":"object","properties":{"rows":{"type":"string"}},"required":["rows"]},"example":{"rows":"image_url,product_id,product_name,group_id\nhttps:\/\/cdn.example.com\/a.jpg,sku-100,Red runner,runner"}}}},"responses":{"202":{"description":"Accepted.","content":{"application\/json":{"schema":{"type":"object","properties":{"imported_images":{"type":"integer"},"run":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"402":{"$ref":"#\/components\/responses\/QuotaExceeded"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/imports -H \"Authorization: Bearer $KEY\" -F \"csv=@products.csv\""}},"\/collections\/{collection}\/runs":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"get":{"tags":["Indexing runs"],"operationId":"listRuns","summary":"List indexing runs","description":"The 50 most recent runs, newest first.","responses":{"200":{"description":"The runs.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/runs -H \"Authorization: Bearer $KEY\""},"post":{"tags":["Indexing runs"],"operationId":"startRun","summary":"Start a re-index or retry","description":"`retry` re-processes failed and pending images into the live index. `reindex` embeds every image into a fresh index that replaces the live one on completion (needed after group changes or a model upgrade); search keeps working meanwhile. A re-index cannot start while another run is active.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["reindex","retry"]}},"required":["type"]},"example":{"type":"retry"}}}},"responses":{"200":{"description":"Nothing to do: there were no images to process.","content":{"application\/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"data":{"type":"null"}}}}}},"202":{"description":"The started run.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/runs -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application\/json\" -d '{\"type\":\"retry\"}'"}},"\/collections\/{collection}\/runs\/{run}":{"parameters":[{"$ref":"#\/components\/parameters\/collection"},{"name":"run","in":"path","required":true,"schema":{"type":"string"},"description":"The run id (a ULID, e.g. `01k6h3t7v2m9q8x4r5n0c1b2d3`)."}],"get":{"tags":["Indexing runs"],"operationId":"getRun","summary":"Get a run","description":"Poll this to follow progress. `processed` counts indexed plus failed images; the run is done when `finished_at` is set.","responses":{"200":{"description":"The run.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/runs\/01k6h3s9w4c2e7f5g8j1k3m6n9 -H \"Authorization: Bearer $KEY\""},"delete":{"tags":["Indexing runs"],"operationId":"cancelRun","summary":"Cancel a run","description":"Stops an active run. Images that were already processed keep their state; the rest go back to how they were. Cancelling a finished run is a no-op.","responses":{"200":{"description":"The run after cancelling.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/IndexingRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X DELETE $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/runs\/01k6h3s9w4c2e7f5g8j1k3m6n9 -H \"Authorization: Bearer $KEY\""}},"\/collections\/{collection}\/search":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"post":{"tags":["Search"],"operationId":"search","summary":"Search by photo","description":"Send a photo as a multipart `image` file or as a public `image_url`. Results are the best matching products, best first, one per group when the collection has groups. `score` is the visual similarity of the best matching image (0\u20131); `confidence` is the calibrated probability (0\u20131) that the top result is correct, so a threshold like 0.8 gives you a reliable yes\/no. Only indexed images take part. Each call counts towards the monthly search quota; the `X-Quota-Searches-Limit` and `X-Quota-Searches-Remaining` response headers tell you where you stand. Send a `filter` to only consider products whose metadata matches it, e.g. `{\"color\": \"red\", \"size\": [\"41\", \"42\"]}`; the keys have to be among the collection's `filterable_keys`. The filter narrows the search itself, so it still finds the best matches among the products that qualify.","requestBody":{"required":true,"content":{"multipart\/form-data":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary"},"limit":{"type":"integer","minimum":1,"maximum":20,"default":5},"filter":{"type":"string","description":"The `SearchFilter` as a JSON string, e.g. `{\"color\":\"red\"}`. Bracket fields such as `filter[color]=red` work too."}},"required":["image"]}},"application\/json":{"schema":{"type":"object","properties":{"image_url":{"type":"string","format":"uri"},"limit":{"type":"integer","minimum":1,"maximum":20,"default":5},"filter":{"$ref":"#\/components\/schemas\/SearchFilter"}},"required":["image_url"]},"example":{"image_url":"https:\/\/example.com\/photo.jpg","limit":3,"filter":{"color":"red"}}}}},"responses":{"200":{"description":"The ranked matches.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Match"}},"meta":{"type":"object","properties":{"took_ms":{"type":"integer"},"limit":{"type":"integer"}}}}},"example":{"data":[{"product_id":"sku-100","product_name":"Red runner","group_id":"runner","metadata":{"brand":"Acme"},"score":0.9312,"margin":0.41,"confidence":0.98,"matching_images":2,"image_id":"01k6h3t7v2m9q8x4r5n0c1b2d3"}],"meta":{"took_ms":61,"limit":5}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"402":{"$ref":"#\/components\/responses\/QuotaExceeded"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"},"502":{"description":"The search service could not process the photo, for example because it is not a usable image. The message says why; retrying the same photo will not help.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"503":{"description":"The search service is temporarily unavailable; retry with backoff.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/search -H \"Authorization: Bearer $KEY\" -F \"image=@photo.jpg\" -F \"limit=3\" -F 'filter={\"color\":\"red\"}'"}},"\/collections\/{collection}\/webhook-deliveries":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"List webhook deliveries","description":"The 50 most recent events sent to the collection's webhook, newest first, with the outcome of the attempts so far. Use it to debug an endpoint that is not receiving events.","responses":{"200":{"description":"The deliveries.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/WebhookDelivery"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/webhook-deliveries -H \"Authorization: Bearer $KEY\""}},"\/collections\/{collection}\/webhook-test":{"parameters":[{"$ref":"#\/components\/parameters\/collection"}],"post":{"tags":["Webhooks"],"operationId":"sendTestWebhook","summary":"Send a test event","description":"Queues a `webhook.test` event to the collection's webhook URL so you can verify your endpoint and signature check without waiting for a real run. Requires a configured `webhook_url`.","responses":{"202":{"description":"The queued delivery; poll the deliveries list for the outcome.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WebhookDelivery"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl -X POST $BASE\/collections\/01k6h2m4q8r3t5v7x9z1b3d5f7\/webhook-test -H \"Authorization: Bearer $KEY\""}},"\/usage":{"get":{"tags":["Usage"],"operationId":"getUsage","summary":"Get plan and usage","description":"The plan of the team that owns the key, its quotas with what is used, when the monthly search counter resets, and daily counts of requests, searches and indexed images for the last 30 days.","responses":{"200":{"description":"The usage.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Usage"}}},"example":{"data":{"plan":{"id":"free","name":"Free","requests_per_minute":120},"quotas":{"searches_per_month":{"limit":1000,"used":412,"remaining":588,"hard_limit":1000},"images":{"limit":5000,"used":1290,"remaining":3710,"hard_limit":5000},"collections":{"limit":3,"used":1,"remaining":2,"hard_limit":3}},"period":{"start":"2026-09-01T00:00:00Z","resets_at":"2026-10-01T00:00:00Z"},"daily":[{"date":"2026-09-24","requests":57,"searches":40,"images_indexed":12}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}},"x-curl":"curl $BASE\/usage -H \"Authorization: Bearer $KEY\""}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Create keys under Settings \u2192 API keys. Send `Authorization: Bearer <key>` on every request. A key sees everything its team owns."}},"parameters":{"collection":{"name":"collection","in":"path","required":true,"schema":{"type":"string"},"description":"The collection id, a ULID (e.g. `01k6h2m4q8r3t5v7x9z1b3d5f7`)."},"product":{"name":"product","in":"path","required":true,"schema":{"type":"string"},"description":"Your product id, as given when the product was created."}},"responses":{"Unauthenticated":{"description":"Missing, invalid or revoked API key.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"},"example":{"message":"Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."}}}},"NotFound":{"description":"No such resource in this team.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"},"example":{"message":"No query results for model [App\\Models\\Collection]."}}}},"ValidationError":{"description":"The request was invalid; `errors` lists the problems per field.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationError"},"example":{"message":"The id field is required.","errors":{"id":["The id field is required."]}}}}},"QuotaExceeded":{"description":"A plan limit was reached. `quota` names the limit; upgrade the plan or free up capacity.","content":{"application\/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"quota":{"type":"object","properties":{"metric":{"type":"string","enum":["searches_per_month","images","collections"]},"limit":{"type":"integer"},"used":{"type":"integer"}}}}},"example":{"message":"The monthly search quota of your plan is used up. Upgrade the plan or wait for the next month.","quota":{"metric":"searches_per_month","limit":1000,"used":1000}}}}},"TooManyRequests":{"description":"Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.","headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per minute on your plan.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"},"example":{"message":"Too Many Attempts."}}}}},"schemas":{"Collection":{"type":"object","properties":{"id":{"type":"string","description":"A ULID; what URLs and webhooks refer to the collection by. It stays the same when the collection is renamed."},"name":{"type":"string"},"description":{"type":["string","null"]},"has_groups":{"type":"boolean","description":"Whether products can share a group_id and searches report one result per group."},"filterable_keys":{"type":"array","items":{"type":"string"},"description":"The product metadata keys that searches can filter on; see the `filter` of a search."},"model":{"type":["string","null"],"description":"The embedding model the index was built with."},"products":{"type":"integer"},"images":{"type":"object","properties":{"total":{"type":"integer"},"indexed":{"type":"integer"},"waiting":{"type":"integer"},"failed":{"type":"integer"}}},"active_runs":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"webhook":{"type":["object","null"],"description":"Present when a webhook is configured.","properties":{"url":{"type":"string","format":"uri"},"secret":{"type":"string","description":"HMAC secret used to sign deliveries."}}}}},"CollectionInput":{"type":"object","properties":{"name":{"type":"string","maxLength":100},"description":{"type":["string","null"],"maxLength":1000},"has_groups":{"type":"boolean","default":false,"description":"Only on creation."},"filterable_keys":{"type":["array","null"],"items":{"type":"string","maxLength":100},"maxItems":20,"description":"The product metadata keys that searches can filter on. Only these keys are copied into the search index; changing them updates the index in the background within moments, without a re-index. Keys cannot contain `=`. Send `null` or `[]` to turn filtering off."},"webhook_url":{"type":["string","null"],"format":"uri","description":"Where to POST events; null disables webhooks. A signing secret is generated the first time a URL is set."},"rotate_webhook_secret":{"type":"boolean","description":"Generate a new signing secret (update only)."}}},"Product":{"type":"object","properties":{"id":{"type":"string","description":"Your identifier."},"name":{"type":["string","null"]},"group_id":{"type":["string","null"]},"metadata":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"description":"Free-form key\/value pairs, returned with search results. A key with several values has a list."},"images":{"type":"object","properties":{"total":{"type":"integer"},"indexed":{"type":"integer"},"failed":{"type":"integer"}}},"image_list":{"type":"array","items":{"$ref":"#\/components\/schemas\/Image"},"description":"Only present on `getProduct`."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ProductInput":{"type":"object","properties":{"id":{"type":"string","maxLength":255,"pattern":"^[^\/]+$","description":"Required on creation; unique within the collection. Cannot contain a slash, because it is part of the product's URL."},"name":{"type":["string","null"],"maxLength":255},"group_id":{"type":["string","null"],"maxLength":255},"metadata":{"type":"object","additionalProperties":{"oneOf":[{"type":["string","number","boolean"],"maxLength":1000},{"type":"array","items":{"type":["string","number","boolean"],"maxLength":1000},"minItems":1,"maxItems":50}]},"maxProperties":50,"description":"Free-form key\/value pairs, returned with search results. A value is text or a number, or a list of up to 50 for a key with several values, e.g. `{\"color\": [\"red\", \"white\"]}`; values are stored as text. The values of the collection's `filterable_keys` can also be used to filter searches, and a filter matches any value of a list."}}},"Image":{"type":"object","properties":{"id":{"type":"string","description":"A ULID."},"product_id":{"type":"string"},"status":{"type":"string","enum":["pending","queued","processing","indexed","failed"],"description":"Only `indexed` images take part in searches."},"error":{"type":["string","null"],"description":"Why indexing failed."},"filename":{"type":["string","null"]},"source_url":{"type":["string","null"]},"indexed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"IndexingRun":{"type":"object","properties":{"id":{"type":"string","description":"A ULID."},"type":{"type":"string","enum":["import","reindex","retry"]},"status":{"type":"string","enum":["queued","running","completed","completed_with_errors","failed","cancelled"]},"total":{"type":"integer"},"processed":{"type":"integer"},"indexed":{"type":"integer"},"failed":{"type":"integer"},"percent":{"type":"integer"},"replaces_index":{"type":"boolean","description":"True for a full re-index that swaps in a new index when it completes."},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"}}},"Match":{"type":"object","properties":{"product_id":{"type":"string"},"product_name":{"type":["string","null"]},"group_id":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},"score":{"type":"number","description":"Visual similarity of the best matching image, 0\u20131."},"margin":{"type":"number","description":"Lead of this result over the runner-up (negative for lower results)."},"confidence":{"type":["number","null"],"description":"Calibrated probability that this result is correct, 0\u20131."},"matching_images":{"type":"integer","description":"How many of the product's (or group's) images were among the nearest neighbours."},"image_id":{"type":["string","null"],"description":"The id of the best matching image."}}},"SearchFilter":{"type":"object","description":"Limits a search to products whose metadata matches. Each key must be one of the collection's `filterable_keys`; every key has to match, and when a key has a list of values any of them will do. Values are compared as exact text, so `true` and `42` match the metadata values \"true\" and \"42\". A product whose metadata has a list for a key matches when any of its values does. Products without a value for a filtered key do not match.","additionalProperties":{"oneOf":[{"type":["string","number","boolean"]},{"type":"array","items":{"type":["string","number","boolean"]},"minItems":1,"maxItems":50}]},"example":{"color":"red","size":["41","42"]}},"Paginated":{"type":"object","properties":{"data":{"type":"array","items":[]},"links":{"type":"object","properties":{"first":{"type":["string","null"]},"last":{"type":["string","null"]},"prev":{"type":["string","null"]},"next":{"type":["string","null"]}}},"meta":{"type":"object","properties":{"current_page":{"type":"integer"},"last_page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"}}}}},"Error":{"type":"object","properties":{"message":{"type":"string"}}},"ValidationError":{"type":"object","properties":{"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","description":"A ULID."},"event":{"type":"string","enum":["indexing_run.finished","webhook.test"]},"status":{"type":"string","enum":["pending","delivered","failed"],"description":"`failed` after all retries (about a day) were exhausted."},"attempts":{"type":"integer"},"lastStatus":{"type":["integer","null"],"description":"HTTP status of the last attempt."},"lastError":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":["string","null"],"format":"date-time"}}},"WebhookEvent":{"type":"object","properties":{"event":{"type":"string","enum":["indexing_run.finished","webhook.test"]},"created_at":{"type":"string","format":"date-time"},"collection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"run":{"$ref":"#\/components\/schemas\/IndexingRun","description":"Only for indexing_run.finished."},"message":{"type":"string","description":"Only for webhook.test."}}},"Quota":{"type":"object","properties":{"limit":{"type":["integer","null"],"description":"null means unlimited."},"used":{"type":"integer"},"remaining":{"type":["integer","null"]},"hard_limit":{"type":["integer","null"],"description":"Usage is refused past this number: the limit itself, or the ceiling of the overage when the plan bills overage for this metric. null means usage is never refused."},"overage":{"type":"object","description":"Whether usage may continue past the limit and be billed afterwards. `units` is how far past the limit the team is right now.","properties":{"allowed":{"type":"boolean"},"units":{"type":"integer"},"price_per_thousand":{"type":["number","null"],"description":"Price per 1,000 units over the limit in the plan currency; null when the plan has no overage for this metric."}}}}},"Usage":{"type":"object","properties":{"plan":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"requests_per_minute":{"type":"integer"}}},"quotas":{"type":"object","properties":{"searches_per_month":{"$ref":"#\/components\/schemas\/Quota"},"images":{"$ref":"#\/components\/schemas\/Quota"},"collections":{"$ref":"#\/components\/schemas\/Quota"}}},"period":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"resets_at":{"type":"string","format":"date-time"}}},"daily":{"type":"array","description":"The last 30 days, oldest first.","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"requests":{"type":"integer"},"searches":{"type":"integer"},"images_indexed":{"type":"integer"}}}}}}}},"webhooks":{"indexing_run.finished":{"post":{"summary":"An indexing run finished","description":"Sent when an import, retry or full re-index reaches a final status (`completed`, `completed_with_errors`, `failed` or `cancelled`). Respond with any 2xx within 15 seconds; anything else is retried with growing delays (1 min, 5 min, 15 min, 1 h, 2 h, 6 h) before the delivery is marked failed.","requestBody":{"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WebhookEvent"},"example":{"event":"indexing_run.finished","created_at":"2026-09-24T19:40:12Z","collection":{"id":"01k6h2m4q8r3t5v7x9z1b3d5f7","name":"Sneakers"},"run":{"id":"01k6h3s9w4c2e7f5g8j1k3m6n9","type":"import","status":"completed","total":12,"processed":12,"indexed":12,"failed":0,"percent":100,"replaces_index":false,"created_at":"2026-09-24T19:39:50Z","started_at":"2026-09-24T19:39:51Z","finished_at":"2026-09-24T19:40:12Z"}}}}},"responses":{"200":{"description":"Acknowledged."}}}}}}