Version 1.4.0
Snapdex API
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.
Base URL https://snapdex.dev/api/v1. All responses are JSON; send Accept: application/json.
Collections
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.
get/collections
List collections sdk: listCollections()
All collections of the team that owns the API key, alphabetically.
Responses
200The collections.
| data | object[] | |
| ↳ id | string | A ULID; what URLs and webhooks refer to the collection by. It stays the same when the collection is renamed. |
| ↳ name | string | |
| ↳ description | string | null | |
| ↳ has_groups | boolean | Whether products can share a group_id and searches report one result per group. |
| ↳ filterable_keys | string[] | The product metadata keys that searches can filter on; see the `filter` of a search. |
| ↳ model | string | null | The embedding model the index was built with. |
| ↳ products | integer | |
| ↳ images | object | |
| ↳ active_runs | integer | |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| ↳ webhook | object | null | Present when a webhook is configured. |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections -H "Authorization: Bearer $KEY"
post/collections
Create a collection sdk: createCollection()
Creates an empty collection and its vector index. has_groups is fixed at creation.
Request body · application/json
| name | string | |
| description | string | null | |
| has_groups | boolean | Only on creation. |
| filterable_keys | array | null | 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 | string | null | Where to POST events; null disables webhooks. A signing secret is generated the first time a URL is set. |
| rotate_webhook_secret | boolean | Generate a new signing secret (update only). |
{
"name": "Sneakers",
"description": "All sneaker product photos",
"has_groups": true
}Responses
201The new collection.
| data | object | |
| ↳ id | string | A ULID; what URLs and webhooks refer to the collection by. It stays the same when the collection is renamed. |
| ↳ name | string | |
| ↳ description | string | null | |
| ↳ has_groups | boolean | Whether products can share a group_id and searches report one result per group. |
| ↳ filterable_keys | string[] | The product metadata keys that searches can filter on; see the `filter` of a search. |
| ↳ model | string | null | The embedding model the index was built with. |
| ↳ products | integer | |
| ↳ images | object | |
| ↳ active_runs | integer | |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| ↳ webhook | object | null | Present when a webhook is configured. |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}402A plan limit was reached. `quota` names the limit; upgrade the plan or free up capacity.
| message | string | |
| quota | object | |
| ↳ metric | "searches_per_month" | "images" | "collections" | |
| ↳ limit | integer | |
| ↳ used | integer | |
{
"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
}
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"name":"Sneakers","has_groups":true}'get/collections/{collection}
Get a collection sdk: getCollection()
The collection with its product and image counts.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Responses
200The collection.
| data | object | |
| ↳ id | string | A ULID; what URLs and webhooks refer to the collection by. It stays the same when the collection is renamed. |
| ↳ name | string | |
| ↳ description | string | null | |
| ↳ has_groups | boolean | Whether products can share a group_id and searches report one result per group. |
| ↳ filterable_keys | string[] | The product metadata keys that searches can filter on; see the `filter` of a search. |
| ↳ model | string | null | The embedding model the index was built with. |
| ↳ products | integer | |
| ↳ images | object | |
| ↳ active_runs | integer | |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| ↳ webhook | object | null | Present when a webhook is configured. |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7 -H "Authorization: Bearer $KEY"
patch/collections/{collection}
Update a collection sdk: updateCollection()
Change the name, description, webhook or filterable keys. has_groups cannot be changed after creation.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Request body · application/json
| name | string | |
| description | string | null | |
| has_groups | boolean | Only on creation. |
| filterable_keys | array | null | 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 | string | null | Where to POST events; null disables webhooks. A signing secret is generated the first time a URL is set. |
| rotate_webhook_secret | boolean | Generate a new signing secret (update only). |
{
"description": "Sneakers, spring catalogue"
}Responses
200The updated collection.
| data | object | |
| ↳ id | string | A ULID; what URLs and webhooks refer to the collection by. It stays the same when the collection is renamed. |
| ↳ name | string | |
| ↳ description | string | null | |
| ↳ has_groups | boolean | Whether products can share a group_id and searches report one result per group. |
| ↳ filterable_keys | string[] | The product metadata keys that searches can filter on; see the `filter` of a search. |
| ↳ model | string | null | The embedding model the index was built with. |
| ↳ products | integer | |
| ↳ images | object | |
| ↳ active_runs | integer | |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| ↳ webhook | object | null | Present when a webhook is configured. |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X PATCH https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7 -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"description":"Spring catalogue"}'delete/collections/{collection}
Delete a collection sdk: deleteCollection()
Deletes the collection, all its products and images, and the index. This cannot be undone.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Responses
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X DELETE https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7 -H "Authorization: Bearer $KEY"
Products
What a search returns. Identified by your own id, with an optional name, group and free-form metadata.
get/collections/{collection}/products
List products sdk: listProducts()
Paginated products of the collection, oldest first. Filter with search (matches id and name) or group_id.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| searchquery | string | Substring of the product id or name. |
| group_idquery | string | Only products in this group. |
| pagequery | integer | |
| per_pagequery | integer | |
Responses
200A page of products.
| data | object[] | |
| ↳ id | string | Your identifier. |
| ↳ name | string | null | |
| ↳ group_id | string | null | |
| ↳ metadata | object | Free-form key/value pairs, returned with search results. A key with several values has a list. |
| ↳ images | object | |
| ↳ image_list | object[] | Only present on `getProduct`. |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| links | object | |
| ↳ first | string | null | |
| ↳ last | string | null | |
| ↳ prev | string | null | |
| ↳ next | string | null | |
| meta | object | |
| ↳ current_page | integer | |
| ↳ last_page | integer | |
| ↳ per_page | integer | |
| ↳ total | integer | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl "https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products?search=red&per_page=20" -H "Authorization: Bearer $KEY"
post/collections/{collection}/products
Create a product sdk: createProduct()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Request body · application/json
| id | string | Required on creation; unique within the collection. Cannot contain a slash, because it is part of the product's URL. |
| name | string | null | |
| group_id | string | null | |
| metadata | object | 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. |
{
"id": "sku-100",
"name": "Red runner",
"group_id": "runner",
"metadata": {
"brand": "Acme",
"colour": "red"
}
}Responses
201The new product.
| data | object | |
| ↳ id | string | Your identifier. |
| ↳ name | string | null | |
| ↳ group_id | string | null | |
| ↳ metadata | object | Free-form key/value pairs, returned with search results. A key with several values has a list. |
| ↳ images | object | |
| ↳ image_list | object[] | Only present on `getProduct`. |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"id":"sku-100","name":"Red runner","metadata":{"brand":"Acme"}}'get/collections/{collection}/products/{product}
Get a product sdk: getProduct()
The product with its metadata and the list of its images and their indexing status.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| productpath · required | string | Your product id, as given when the product was created. |
Responses
200The product, including `image_list`.
| data | object | |
| ↳ id | string | Your identifier. |
| ↳ name | string | null | |
| ↳ group_id | string | null | |
| ↳ metadata | object | Free-form key/value pairs, returned with search results. A key with several values has a list. |
| ↳ images | object | |
| ↳ image_list | object[] | Only present on `getProduct`. |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products/sku-100 -H "Authorization: Bearer $KEY"
patch/collections/{collection}/products/{product}
Update a product sdk: updateProduct()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| productpath · required | string | Your product id, as given when the product was created. |
Request body · application/json
| id | string | Required on creation; unique within the collection. Cannot contain a slash, because it is part of the product's URL. |
| name | string | null | |
| group_id | string | null | |
| metadata | object | 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. |
{
"name": "Red runner v2",
"metadata": {
"brand": "Acme",
"season": "SS26"
}
}Responses
200The updated product.
| data | object | |
| ↳ id | string | Your identifier. |
| ↳ name | string | null | |
| ↳ group_id | string | null | |
| ↳ metadata | object | Free-form key/value pairs, returned with search results. A key with several values has a list. |
| ↳ images | object | |
| ↳ image_list | object[] | Only present on `getProduct`. |
| ↳ created_at | datetime | |
| ↳ updated_at | datetime | |
| warning | string | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X PATCH https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products/sku-100 -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"name":"Red runner v2"}'delete/collections/{collection}/products/{product}
Delete a product sdk: deleteProduct()
Deletes the product and its images; they stop matching immediately.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| productpath · required | string | Your product id, as given when the product was created. |
Responses
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X DELETE https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products/sku-100 -H "Authorization: Bearer $KEY"
Images
Photos of a product. New images are indexed incrementally in the background; the live index keeps answering searches meanwhile.
get/collections/{collection}/products/{product}/images
List a product's images sdk: listProductImages()
All images of the product with their indexing status.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| productpath · required | string | Your product id, as given when the product was created. |
Responses
200The images.
| data | object[] | |
| ↳ id | string | A ULID. |
| ↳ product_id | string | |
| ↳ status | "pending" | "queued" | "processing" | "indexed" | "failed" | Only `indexed` images take part in searches. |
| ↳ error | string | null | Why indexing failed. |
| ↳ filename | string | null | |
| ↳ source_url | string | null | |
| ↳ indexed_at | datetime | |
| ↳ created_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products/sku-100/images -H "Authorization: Bearer $KEY"
post/collections/{collection}/products/{product}/images
Add images to a product sdk: addProductImages()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| productpath · required | string | Your product id, as given when the product was created. |
Request body · multipart/form-data
Request body · application/json
{
"urls": [
"https://cdn.example.com/sku-100-front.jpg",
"https://cdn.example.com/sku-100-side.jpg"
]
}Responses
202Accepted: the images and the indexing run that processes them.
| data | object[] | |
| ↳ id | string | A ULID. |
| ↳ product_id | string | |
| ↳ status | "pending" | "queued" | "processing" | "indexed" | "failed" | Only `indexed` images take part in searches. |
| ↳ error | string | null | Why indexing failed. |
| ↳ filename | string | null | |
| ↳ source_url | string | null | |
| ↳ indexed_at | datetime | |
| ↳ created_at | datetime | |
| run | object | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}402A plan limit was reached. `quota` names the limit; upgrade the plan or free up capacity.
| message | string | |
| quota | object | |
| ↳ metric | "searches_per_month" | "images" | "collections" | |
| ↳ limit | integer | |
| ↳ used | integer | |
{
"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
}
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/products/sku-100/images -H "Authorization: Bearer $KEY" -F "files[][email protected]" -F "files[][email protected]"
get/collections/{collection}/images/{image}
Get an image sdk: getImage()
The indexing status of one image, including the error when it failed.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| imagepath · required | string | The image id (a ULID, e.g. 01k6h3t7v2m9q8x4r5n0c1b2d3). |
Responses
200The image.
| data | object | |
| ↳ id | string | A ULID. |
| ↳ product_id | string | |
| ↳ status | "pending" | "queued" | "processing" | "indexed" | "failed" | Only `indexed` images take part in searches. |
| ↳ error | string | null | Why indexing failed. |
| ↳ filename | string | null | |
| ↳ source_url | string | null | |
| ↳ indexed_at | datetime | |
| ↳ created_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/images/01k6h3t7v2m9q8x4r5n0c1b2d3 -H "Authorization: Bearer $KEY"
delete/collections/{collection}/images/{image}
Delete an image sdk: deleteImage()
Removes the image from the index and from storage.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| imagepath · required | string | The image id (a ULID, e.g. 01k6h3t7v2m9q8x4r5n0c1b2d3). |
Responses
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X DELETE https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/images/01k6h3t7v2m9q8x4r5n0c1b2d3 -H "Authorization: Bearer $KEY"
post/collections/{collection}/imports
Import products and images from CSV sdk: importCsv()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Request body · multipart/form-data
Request body · application/json
{
"rows": "image_url,product_id,product_name,group_id\nhttps://cdn.example.com/a.jpg,sku-100,Red runner,runner"
}Responses
202Accepted.
| imported_images | integer | |
| run | object | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}402A plan limit was reached. `quota` names the limit; upgrade the plan or free up capacity.
| message | string | |
| quota | object | |
| ↳ metric | "searches_per_month" | "images" | "collections" | |
| ↳ limit | integer | |
| ↳ used | integer | |
{
"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
}
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/imports -H "Authorization: Bearer $KEY" -F "[email protected]"
Indexing runs
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.
get/collections/{collection}/runs
List indexing runs sdk: listRuns()
The 50 most recent runs, newest first.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Responses
200The runs.
| data | object[] | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/runs -H "Authorization: Bearer $KEY"
post/collections/{collection}/runs
Start a re-index or retry sdk: startRun()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Request body · application/json
| type * | "reindex" | "retry" | |
{
"type": "retry"
}Responses
200Nothing to do: there were no images to process.
202The started run.
| data | object | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/runs -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"retry"}'get/collections/{collection}/runs/{run}
Get a run sdk: getRun()
Poll this to follow progress. processed counts indexed plus failed images; the run is done when finished_at is set.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| runpath · required | string | The run id (a ULID, e.g. 01k6h3t7v2m9q8x4r5n0c1b2d3). |
Responses
200The run.
| data | object | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/runs/01k6h3s9w4c2e7f5g8j1k3m6n9 -H "Authorization: Bearer $KEY"
delete/collections/{collection}/runs/{run}
Cancel a run sdk: cancelRun()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
| runpath · required | string | The run id (a ULID, e.g. 01k6h3t7v2m9q8x4r5n0c1b2d3). |
Responses
200The run after cancelling.
| data | object | |
| ↳ id | string | A ULID. |
| ↳ type | "import" | "reindex" | "retry" | |
| ↳ status | "queued" | "running" | "completed" | "completed_with_errors" | "failed" | "cancelled" | |
| ↳ total | integer | |
| ↳ processed | integer | |
| ↳ indexed | integer | |
| ↳ failed | integer | |
| ↳ percent | integer | |
| ↳ replaces_index | boolean | True for a full re-index that swaps in a new index when it completes. |
| ↳ created_at | datetime | |
| ↳ started_at | datetime | |
| ↳ finished_at | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X DELETE https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/runs/01k6h3s9w4c2e7f5g8j1k3m6n9 -H "Authorization: Bearer $KEY"
Search
Match a photo against a collection.
post/collections/{collection}/search
Search by photo sdk: search()
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–1); confidence is the calibrated probability (0–1) 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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Request body · multipart/form-data
| image * | file | |
| limit | integer | Default 5. |
| filter | string | The SearchFilter as a JSON string, e.g. {"color":"red"}. Bracket fields such as filter[color]=red work too. |
Request body · application/json
| image_url * | string | |
| limit | integer | Default 5. |
| filter | object | 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. |
{
"image_url": "https://example.com/photo.jpg",
"limit": 3,
"filter": {
"color": "red"
}
}Responses
200The ranked matches.
| data | object[] | |
| ↳ product_id | string | |
| ↳ product_name | string | null | |
| ↳ group_id | string | null | |
| ↳ metadata | object | null | |
| ↳ score | number | Visual similarity of the best matching image, 0–1. |
| ↳ margin | number | Lead of this result over the runner-up (negative for lower results). |
| ↳ confidence | number | null | Calibrated probability that this result is correct, 0–1. |
| ↳ matching_images | integer | How many of the product's (or group's) images were among the nearest neighbours. |
| ↳ image_id | string | null | The id of the best matching image. |
| meta | object | |
| ↳ took_ms | integer | |
| ↳ limit | integer | |
{
"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
}
}401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}402A plan limit was reached. `quota` names the limit; upgrade the plan or free up capacity.
| message | string | |
| quota | object | |
| ↳ metric | "searches_per_month" | "images" | "collections" | |
| ↳ limit | integer | |
| ↳ used | integer | |
{
"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
}
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}502The 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.
503The search service is temporarily unavailable; retry with backoff.
curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/search -H "Authorization: Bearer $KEY" -F "[email protected]" -F "limit=3" -F 'filter={"color":"red"}'Webhooks
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.
get/collections/{collection}/webhook-deliveries
List webhook deliveries sdk: listWebhookDeliveries()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Responses
200The deliveries.
| data | object[] | |
| ↳ id | string | A ULID. |
| ↳ event | "indexing_run.finished" | "webhook.test" | |
| ↳ status | "pending" | "delivered" | "failed" | `failed` after all retries (about a day) were exhausted. |
| ↳ attempts | integer | |
| ↳ lastStatus | integer | null | HTTP status of the last attempt. |
| ↳ lastError | string | null | |
| ↳ createdAt | datetime | |
| ↳ deliveredAt | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/webhook-deliveries -H "Authorization: Bearer $KEY"
post/collections/{collection}/webhook-test
Send a test event sdk: sendTestWebhook()
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.
Parameters
| collectionpath · required | string | The collection id, a ULID (e.g. 01k6h2m4q8r3t5v7x9z1b3d5f7). |
Responses
202The queued delivery; poll the deliveries list for the outcome.
| data | object | |
| ↳ id | string | A ULID. |
| ↳ event | "indexing_run.finished" | "webhook.test" | |
| ↳ status | "pending" | "delivered" | "failed" | `failed` after all retries (about a day) were exhausted. |
| ↳ attempts | integer | |
| ↳ lastStatus | integer | null | HTTP status of the last attempt. |
| ↳ lastError | string | null | |
| ↳ createdAt | datetime | |
| ↳ deliveredAt | datetime | |
401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}404No such resource in this team.
{
"message": "No query results for model [App\\Models\\Collection]."
}422The request was invalid; `errors` lists the problems per field.
| message | string | |
| errors | object | |
{
"message": "The id field is required.",
"errors": {
"id": [
"The id field is required."
]
}
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl -X POST https://snapdex.dev/api/v1/collections/01k6h2m4q8r3t5v7x9z1b3d5f7/webhook-test -H "Authorization: Bearer $KEY"
Usage
Your plan, its quotas and how much of them you have used.
get/usage
Get plan and usage sdk: getUsage()
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
200The usage.
| data | object | |
| ↳ plan | object | |
| ↳ quotas | object | |
| ↳ period | object | |
| ↳ daily | object[] | The last 30 days, oldest first. |
{
"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
}
]
}
}401Missing, invalid or revoked API key.
{
"message": "Unauthenticated. Pass a valid API key as a Bearer token in the Authorization header."
}429Too many requests for your plan's per-minute limit. Wait the number of seconds in `Retry-After`, then try again.
{
"message": "Too Many Attempts."
}curl https://snapdex.dev/api/v1/usage -H "Authorization: Bearer $KEY"