# Similarity AI API documentation Web guide: /docs Interactive demo: /demo ## Getting started Get your account API key, index your public image URLs, then search by image URL or indexed image ID. Use the API base URL shown in /docs for the paths below. ## Authentication From a trusted server, send: Authorization: Bearer YOUR_TENANT_API_KEY Your key identifies your account. Keep API keys and signing secrets on your server. Omit tenant_id from requests. For browser requests, your server can authorize the caller and issue a short-lived HMAC grant. Send Authorization: HMAC YOUR_SIGNED_GRANT. Grants expire within five minutes and bind the method, exact path and query, exact body bytes and idempotency key. Node.js server signing example: import { createHash, createHmac, randomUUID } from 'node:crypto'; const path = '/v1/images/match'; const idempotencyKey = randomUUID(); const body = JSON.stringify({ images: [{ query_id: 'query-001', image_url: approvedImageUrl }], top_k: 5, filters: [], }); const expiresAt = Date.now() + 5 * 60_000; const digest = createHash('sha256').update(body).digest('base64url'); const prefix = ['grant', tenantSigningKeyId, expiresAt, digest].join('.'); const message = [prefix, 'POST', path, idempotencyKey].join('\n'); const signature = createHmac('sha256', tenantSigningSecret) .update(message).digest('base64url'); const authorization = prefix + '.' + signature; // After authorizing the caller, return with Cache-Control: no-store: // { path, body, authorization, idempotencyKey } Send the returned body string unchanged; parsing and re-serializing can invalidate the signature. GET signs an empty body, exact path and query, and empty idempotency-key line. PATCH appends the exact quoted If-Match header as a fifth signing-message line and includes an idempotency key. DELETE signs the exact URL and body and needs no idempotency key. Read grants can be reused until expiry. ## Idempotency and retries Index, match and bulk submissions require Idempotency-Key: 16-128 printable ASCII characters. Use a fresh UUID for each new operation and reuse it only for retries of that operation. HMAC binds the key. Completed keys remain reserved for 24 hours; queued/running operations remain reserved until completion. After expiry, reuse starts a new operation and may incur another charge. Index retries return retained outcomes. Completed match responses are not retained; a reused match key returns result_not_retained without another search. ## Metadata and attributes metadata: descriptive JSON returned with matches; up to 16 KiB. Nested JSON is allowed, but only top-level scalar fields support filtering. attributes: flat strings, finite numbers and booleans; at most 32 fields and 16 KiB total. Strings allow up to 2,048 characters. Keys start with a letter, contain letters/digits/underscores, and are at most 64 characters. constructor and prototype are reserved. Example: {"metadata":{"title":"House in Lahore","listing_url":"https://example.com/property/123","city":"Lahore"},"attributes":{"bedrooms":3,"property_type":"house","available":true}} These fields are supplied by you, not inferred from images. They describe records and restrict eligible results; they do not change visual similarity scores. ## Index images POST /v1/images/index Headers: Authorization, Content-Type: application/json, Idempotency-Key. Body: {"images":[{"external_id":"product-001","image_url":"https://your-public-host.example/product-001.jpg","metadata":{"title":"Example product"},"attributes":{}}]} Supply 1-20 images. external_id and public image_url are required per item. Give each image a unique external_id within your account. For multiple photos of one listing, use IDs like listing-123-photo-1 and listing-123-photo-2. Response includes request_id, profile_id, results and summary. Each result has item_index, external_id, status and, when available, image_id. Statuses: indexed, duplicate, pending, failed. Inspect every item. Resubmitting an indexed external_id with the exact same URL returns duplicate and the existing image_id. A different URL returns external_id_conflict. ## Bulk indexing POST /v1/images/bulk Headers: Authorization, Content-Type: application/json, Idempotency-Key. Body: {"manifest_url":"https://your-public-host.example/catalog.jsonl","manifest_sha256":"REPLACE_WITH_THE_64_CHARACTER_SHA256"} Returns HTTP 202 with bulk_job_id, status and status_url. SHA-256 covers the exact uncompressed UTF-8 manifest bytes. Use one index item per JSONL line. Limits: 100 MiB, 100,000 physical lines, 64 KiB per line. Blank or malformed lines produce item errors. Public HTTP(S) URLs and image safety limits apply. Bulk indexing is offered for 50,000 or more new images per job. GET /v1/bulk/{job_id} Check job progress and counts. A job finishes as completed, rejected, or failed. A completed job can contain failed items. GET /v1/bulk/{job_id}/items?limit=20 Read item outcomes, image IDs and safe errors. Limits: 1-100. Use returned next_cursor for subsequent pages. Jobs and pages are tenant-scoped. GET requests require authentication but no idempotency key. ## Get an image GET /v1/images/{image_id} Returns the owned image's ID, external ID, URL, metadata, attributes, status and profile. Foreign or unknown IDs return 404. Use this endpoint to inspect pending indexing. Includes attribute_version and a matching quoted ETag, initially "0". ## Update metadata and attributes PATCH /v1/images/{image_id}/update Headers: Authorization, Content-Type: application/json, Idempotency-Key, If-Match: "0" (use the current ETag from GET). Body: {"attributes":{"bedrooms":3,"property_type":"house","available":true},"metadata":{"title":"Updated listing"}} Send attributes, metadata or both. Each supplied object is replaced completely; omitted fields remain unchanged. An empty object clears that field. Metadata supports nested JSON up to 16 KiB and 64 nesting levels. The shared version advances once per update. Image URL and external ID cannot be changed here. No image fetching or re-embedding is required; this operation is unbilled. 200: updated. 202: pending; poll GET for published values and version. 428: missing If-Match. 412: stale version. 409: overlapping mutation. Retry with the same key, body and version; completed results replay for 24 hours. ## Delete images DELETE /v1/images/{image_id} Returns 204 when the image is removed. You can safely retry after a timeout or retryable error. DELETE /v1/images Headers: Authorization, Content-Type: application/json. Body: {"image_ids":["IMAGE_ID_1","IMAGE_ID_2"]} Send 1-20 distinct IDs. The response reports success or failure for each image. Retry with the same IDs after a timeout. DELETE needs no idempotency key. Deletion is free. Indexing the image again counts as a new index operation. ## Find matches POST /v1/images/match Headers: Authorization, Content-Type: application/json, Idempotency-Key. Body: {"images":[{"query_id":"query-001","image_url":"https://your-public-host.example/query.jpg"}],"top_k":5,"filters":[]} Send up to 50 images per request. Give each query a unique query_id and supply either a public image_url or an indexed image_id. To query an indexed image, use: {"query_id":"query-001","image_id":"YOUR_RETURNED_IMAGE_ID"} The indexed source is excluded from its own results. Use top_k to limit each result list to 1-50 items (default 10). Add filters to search only part of your catalog. Successful searches count toward usage even when they return no matches. Succeeded queries return up to top_k candidates in each list. matches contains near-exact copies (near_exact) and verified edited copies (edited_copy). needs_verification contains candidates with inconclusive or unavailable evidence. Results include catalog image IDs, external IDs, URLs, metadata, and match_reason. Use the order returned by the API. The verification field contains geometric evidence when available. Similarity scores measure visual resemblance. Use match_reason to understand why the API returned a match. Responses summarize successful, failed, and billable queries. ## Filtering filters restrict stored eligible records within the authenticated account. All conditions must pass (AND). Fields: metadata., attributes.; a bare key means attributes. Missing fields or wrong types do not match. String equality is case-sensitive. At most 10 conditions; nested paths, raw database filters, OR and NOT are unsupported. eq: string, number or boolean equality. in: 1-50 strings or 1-50 numbers, never mixed types. gte / lte: numbers or UTC timestamps, e.g. 2026-10-24T00:00:00.000Z. Repeat a field with gte and lte for a range. Example filters: [{"field":"metadata.city","op":"eq","value":"Lahore"},{"field":"attributes.bedrooms","op":"gte","value":3},{"field":"attributes.property_type","op":"in","value":["house","flat"]}] This restricts stored records; it does not detect cities or bedrooms in images. ## Pricing USD per image: standard indexing $0.01, bulk indexing $0.005 (advertised minimum 50,000 new images per job), successful similarity search $0.002 for both image_url and image_id queries, including searches with zero matches. Failed or duplicate indexing adds no charge. Demo accounts have zero billable amounts. The $299 monthly minimum is applied against usage. Pilot onboarding is $500, with $1,000 due if you continue after the pilot. Custom integration is $3,000. See /pricing for details. ## Monthly usage GET /v1/usage/monthly/{YYYY-MM} Example: /v1/usage/monthly/2026-10 Get actual and billable counts, USD amounts by operation, and the report timestamp for a UTC calendar month. Refreshing the report creates no charges. ## Health GET /full-health (authenticated): returns ready plus database, search and inference checks. All must be ready for indexing/search. Missing provider/model readiness: 503. Invalid tenant key: 401. GET /health (public): process liveness only; 200 does not prove model/provider readiness. ## Errors and limits Processed batches return HTTP 200 with per-item outcomes, including mixed success and failure. Inspect every item. Request-level error envelope: {"error":{"code":"dependency_unavailable","message":"dependency unavailable","retryable":true},"request_id":"..."} 401: missing/invalid authentication, expired grant, unknown/revoked key. 404: unknown or inaccessible image. 409: idempotency conflict, running operation, result_not_retained or overlapping mutation. 412: stale update version. 413: JSON body larger than 1 MiB. 422: invalid envelope or idempotency key. 428: missing If-Match for an update. 429: capacity full; respect Retry-After. 503: provider/model unavailable. 504: processing deadline exceeded. Images: JPEG, PNG or single-frame WebP; at least 224 pixels per side, at most 4096 per side, 12 million pixels and 20 MiB. URLs must be public HTTP(S) on standard ports. Local/private destinations and unsafe redirects are rejected.