{"openapi":"3.1.0","info":{"title":"SightRadar Face Recognition API","version":"1.0.0","summary":"AWS-Rekognition-compatible face indexing & search, priced below Rekognition.","description":"SightRadar is a face-recognition SaaS. Index faces from photos, search a\ncollection with a selfie, detect & compare faces \u2014 billed on a **prepaid\ncredit** model (no per-month commitment).\n\n## Authentication\nAll `/v1/*` endpoints require an API key, sent as a Bearer token:\n\n```\nAuthorization: Bearer frs_<prefix>_<secret>\n```\n\nCreate keys in the [portal](https://sightradar.com) \u2192 **API Keys**, or via\n`POST /portal/keys`. The plaintext key is shown **once** at creation and is\nnever recoverable \u2014 store it securely.\n\n## Billing model\n- **Prepaid credits.** Each billable call *reserves* credits up front, then\n  *settles* on success or *refunds* on engine failure (you are never charged\n  for a 4xx/5xx).\n- **Per photo, never per face.** A photo with 0 or 5 faces is one charge.\n- Billable ops (index, search, search-by-id, selfie, detect, compare) cost\n  **62 credits** each by default (\u2248 50% of AWS Rekognition).\n- Control-plane ops (create/list/get/delete collection, wallet, usage, keys,\n  webhooks) are **free** (0 credits) but still require a valid key.\n- When your wallet hits zero, billable calls return **402 Payment Required**.\n  The `X-Credits-Remaining` response header reports your balance after each\n  billable call.\n\n## Idempotency\nBillable calls are idempotent **opt-in**: send an `Idempotency-Key` header to\nmake safe retries; a replay under the same key returns **409** before any\ncharge. Without the header, each call is unique (an identical re-run charges\nagain).\n\n## Real-time vs batch\n- **Real-time** (`/v1/collections/{id}/index`, `/search`, etc.): synchronous,\n  immediate response. Accepts a `url`, a `gcsKey`, or raw/multipart image\n  bytes (\u2264 30 MB).\n- **Batch** (`POST /v1/batches`): fire-and-forget, up to 1000 **URL-only**\n  photos; results delivered per-photo via your registered webhook (or poll\n  `GET /v1/batches/{id}`).\n"},"servers":[{"url":"https://api.sightradar.com","description":"Production API"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Collections","description":"Create and manage face collections (the registry of your indexed photos)."},{"name":"Faces","description":"Index, search, detect, and compare faces (billable operations)."},{"name":"Selfies","description":"Register a single-face selfie for later search-by-id."},{"name":"Batch","description":"Asynchronous bulk processing with webhook callbacks."},{"name":"Webhooks","description":"Register endpoints that receive per-photo batch results."},{"name":"Wallet & Usage","description":"Check your credit balance, usage, and auto-recharge config."},{"name":"API Keys","description":"Mint, list, and revoke API keys."},{"name":"Health","description":"Liveness and readiness probes (unauthenticated)."}],"paths":{"/healthz":{"get":{"tags":["Health"],"summary":"Liveness probe","description":"Process is up and serving. No dependencies. Unauthenticated.","security":[],"responses":{"200":{"description":"Service is alive.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/readyz":{"get":{"tags":["Health"],"summary":"Readiness probe","description":"Verifies the Postgres connection. 200 = ready, 503 = not ready. Unauthenticated.","security":[],"responses":{"200":{"description":"Ready to serve traffic.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ready"},"postgres":{"type":"string","example":"ok"}}}}}},"503":{"description":"Not ready (Postgres down or unconfigured).","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"not ready"},"postgres":{"type":"string","example":"down"}}}}}}}}},"/v1/collections":{"post":{"tags":["Collections"],"summary":"Create a collection","description":"Creates a new face collection owned by your account. Free (0 credits).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["collection_id"],"properties":{"collection_id":{"type":"string","description":"Your chosen identifier for the collection.","example":"event-2026-wedding"}}}}}},"responses":{"200":{"description":"Collection created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}},"400":{"description":"collection_id missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Collection already exists in a non-active state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Collections"],"summary":"List collections","description":"Lists your collections, newest paging via limit/offset. Free.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Your collections.","content":{"application/json":{"schema":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/collections/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Collection id."}],"get":{"tags":["Collections"],"summary":"Describe a collection","description":"Returns the collection with LIVE face/selfie counts merged from the engine. Free.","responses":{"200":{"description":"Collection detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Collections"],"summary":"Delete a collection (cascade)","description":"Schedules a durable cascade delete \u2014 all faces and selfies are removed\nasynchronously. Returns 202 with a workflow id. Free.\n","responses":{"202":{"description":"Deletion scheduled.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"deleting"},"workflow_id":{"type":"string"},"message":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/collections/{id}/metrics":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Collections"],"summary":"Collection metrics","description":"Live face/selfie counts straight from the engine's vector store. Free.","responses":{"200":{"description":"Counts.","content":{"application/json":{"schema":{"type":"object","properties":{"collection_id":{"type":"string"},"status":{"type":"string","example":"active"},"photo_count":{"type":"integer","format":"int64"},"face_count":{"type":"integer","format":"int64"},"selfie_count":{"type":"integer","format":"int64"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Engine metrics unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections/{id}/index":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Faces"],"summary":"Index faces from a photo","description":"Detects all quality-gated faces in an image and stores them in the\ncollection (Rekognition `IndexFaces`). **Billable: 62 credits.**\n\nProvide the image as JSON `{url}` / `{gcsKey}`, a multipart `file`, or\nraw image bytes. Optional `photoId` is the per-image key (defaults to a\ncontent hash).\n","parameters":[{"name":"photoId","in":"query","schema":{"type":"string"},"description":"Per-image key (also accepted in body)."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageInput"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"photoId":{"type":"string"}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Faces indexed.","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexResult"}}}},"400":{"$ref":"#/components/responses/BadImage"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"409":{"$ref":"#/components/responses/DuplicateRequest"},"413":{"$ref":"#/components/responses/ImageTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/collections/{id}/search":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Faces"],"summary":"Search a collection","description":"Search by a selfie image OR a precomputed 512-d embedding. Returns the\nmatching photo ids ranked by similarity. **Billable: 62 credits.**\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchInput"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"threshold":{"type":"number"},"limit":{"type":"integer"}}}}}},"responses":{"200":{"description":"Search result.","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"400":{"$ref":"#/components/responses/BadImage"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"409":{"$ref":"#/components/responses/DuplicateRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/collections/{id}/search-by-id":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Faces"],"summary":"Search by a stored selfie point id","description":"Search using a previously-registered selfie's `pointId` (Rekognition\n`SearchFaces` by FaceId). **Billable: 62 credits.**\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pointId"],"properties":{"pointId":{"type":"string","description":"A point_id returned by /selfies."},"threshold":{"type":"number","description":"Min cosine similarity (0-1)."},"limit":{"type":"integer","description":"Max matches to return."}}}}}},"responses":{"200":{"description":"Search result.","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"400":{"description":"pointId missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/collections/{id}/selfies":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Selfies"],"summary":"Register a selfie","description":"Register a single-face selfie (Rekognition `IndexFaces` MaxFaces=1).\nReturns a `point_id` you can later pass to `search-by-id`.\n**Billable: 62 credits.**\n","parameters":[{"name":"userId","in":"query","schema":{"type":"string"},"description":"Required (also accepted in body)."},{"name":"selfieId","in":"query","schema":{"type":"string"},"description":"Optional; defaults to a content hash."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ImageInput"},{"type":"object","required":["userId"],"properties":{"userId":{"type":"string"},"selfieId":{"type":"string"}}}]}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"userId":{"type":"string"},"selfieId":{"type":"string"}}}}}},"responses":{"200":{"description":"Selfie processed (check face_found).","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfieResult"}}}},"400":{"description":"userId missing or bad image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/detect":{"post":{"tags":["Faces"],"summary":"Detect faces (no storage)","description":"Detect + quality-gate faces in an image WITHOUT storing anything\n(Rekognition `DetectFaces`). **Billable: 62 credits.**\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageInput"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Detection result.","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectResult"}}}},"400":{"$ref":"#/components/responses/BadImage"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/compare":{"post":{"tags":["Faces"],"summary":"Compare two faces","description":"Compare the best face in two images (Rekognition `CompareFaces`).\nReturns cosine similarity (0-1) and a match boolean at the configured\nthreshold. **Billable: 62 credits.**\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Provide image refs OR precomputed embeddings for source & target.","properties":{"sourceUrl":{"type":"string"},"sourceGcsKey":{"type":"string"},"targetUrl":{"type":"string"},"targetGcsKey":{"type":"string"},"source_embedding":{"type":"array","items":{"type":"number"},"minItems":512,"maxItems":512},"target_embedding":{"type":"array","items":{"type":"number"},"minItems":512,"maxItems":512}}}}}},"responses":{"200":{"description":"Comparison result.","headers":{"X-Credits-Remaining":{"$ref":"#/components/headers/CreditsRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareResult"}}}},"400":{"description":"Bad body or missing source/target.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/EngineUnavailable"}}}},"/v1/wallet":{"get":{"tags":["Wallet & Usage"],"summary":"Get credit balance","description":"Returns your current prepaid credit balance. Free.","responses":{"200":{"description":"Balance.","content":{"application/json":{"schema":{"type":"object","properties":{"balance_credits":{"type":"integer","format":"int64","example":5000000}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/usage":{"get":{"tags":["Wallet & Usage"],"summary":"Usage report","description":"Per-operation usage aggregation over the last N days (default 30, max 365). Free.","parameters":[{"name":"days","in":"query","schema":{"type":"integer","default":30,"maximum":365}}],"responses":{"200":{"description":"Usage breakdown.","content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","example":30},"by_op":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","example":"index"},"calls":{"type":"integer","format":"int64"},"credits":{"type":"integer","format":"int64"}}}},"total_calls":{"type":"integer","format":"int64"},"total_credits":{"type":"integer","format":"int64"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/batches":{"post":{"tags":["Batch"],"summary":"Submit a batch job","description":"Fire-and-forget bulk processing of up to 1000 **URL-only** photos.\nbase64 is rejected in batch \u2014 use real-time endpoints for inline images.\nResults arrive per-photo via your registered webhook, or poll\n`GET /v1/batches/{id}`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["collection_id","op","photos"],"properties":{"collection_id":{"type":"string"},"op":{"type":"string","enum":["index","match"]},"webhook_endpoint_id":{"type":"string","description":"Optional; an endpoint id from POST /v1/webhooks."},"photos":{"type":"array","maxItems":1000,"items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"external_id":{"type":"string"}}}}}}}}},"responses":{"202":{"description":"Batch accepted.","content":{"application/json":{"schema":{"type":"object","properties":{"batch_id":{"type":"string"},"total_photos":{"type":"integer"},"status":{"type":"string","example":"pending"},"message":{"type":"string"}}}}}},"400":{"description":"Validation error (missing fields, base64 in batch, too large).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/batches/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Batch"],"summary":"Get batch status","description":"Poll a batch's progress (webhook-down fallback). Free.","responses":{"200":{"description":"Batch status.","content":{"application/json":{"schema":{"type":"object","properties":{"batch_id":{"type":"string"},"collection_id":{"type":"string"},"op":{"type":"string"},"status":{"type":"string"},"total_photos":{"type":"integer"},"succeeded":{"type":"integer"},"failed":{"type":"integer"},"pending":{"type":"integer"},"claimed":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/webhooks":{"post":{"tags":["Webhooks"],"summary":"Register a webhook endpoint","description":"Register an HTTPS URL to receive per-photo batch results. We sign each\ndelivery with HMAC-SHA256 over `timestamp.body`. If you omit `secret`,\none is generated and returned **once**.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Public HTTPS URL."},"secret":{"type":"string","description":"Optional signing secret; generated if omitted."}}}}}},"responses":{"200":{"description":"Endpoint registered.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_endpoint_id":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"},"secret":{"type":"string","description":"Returned ONCE only when generated."},"note":{"type":"string"}}}}}},"400":{"description":"Invalid or non-HTTPS/private URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"description":"Webhook encryption key not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","responses":{"200":{"description":"Your webhook endpoints.","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"type":"object","properties":{"webhook_endpoint_id":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/portal/keys":{"get":{"tags":["API Keys"],"summary":"List API keys","description":"Returns your keys' non-secret metadata. Free.","responses":{"200":{"description":"Keys.","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyMeta"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["API Keys"],"summary":"Create an API key","description":"Mints a new key. The `plaintext` is returned **once** and is never\nrecoverable afterward. Max 50 active keys per account.\n","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"production-key"}}}}}},"responses":{"201":{"description":"Key created (plaintext shown once).","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"plaintext":{"type":"string","example":"frs_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Active key limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/keys/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["API Keys"],"summary":"Revoke an API key","responses":{"200":{"description":"Revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean","example":true}}}}}},"400":{"description":"Invalid key id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/portal/auto-recharge":{"get":{"tags":["Wallet & Usage"],"summary":"Get auto-recharge config","description":"Returns your auto-recharge settings and mandate state. Free.","responses":{"200":{"description":"Config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRecharge"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Wallet not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Wallet & Usage"],"summary":"Update auto-recharge config","description":"Enable/disable auto-recharge and set the threshold + recharge amount.\nEnabling requires a positive threshold and a recharge amount \u2265 the\nplatform minimum top-up. A payment mandate must be authorized separately\nin the portal before the sweep will act.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"threshold_credits":{"type":"integer","format":"int64"},"recharge_credits":{"type":"integer","format":"int64"}}}}}},"responses":{"200":{"description":"Updated config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRecharge"}}}},"400":{"description":"Validation error (negative amounts, below minimum, no threshold).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Wallet not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"frs_<prefix>_<secret>","description":"Send your API key as: Authorization: Bearer frs_<prefix>_<secret>"}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Opt-in idempotency. A replay under the same key returns 409 before any charge."}},"headers":{"CreditsRemaining":{"description":"Your credit balance after this billable call.","schema":{"type":"integer","format":"int64"}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"unauthorized"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadImage":{"description":"Cannot decode image, or no url/gcsKey/body provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InsufficientCredits":{"description":"Wallet balance too low for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"insufficient credits"}}}},"DuplicateRequest":{"description":"A request with this Idempotency-Key is in flight or already processed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ImageTooLarge":{"description":"Image exceeds the 30 MB encoded size cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Per-key RPS or per-customer concurrency limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"EngineUnavailable":{"description":"Engine error (the credit hold is refunded automatically).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}}},"Collection":{"type":"object","properties":{"collection_id":{"type":"string"},"status":{"type":"string","enum":["active","deleting","deleted"],"example":"active"},"photo_count":{"type":"integer","format":"int64"},"face_count":{"type":"integer","format":"int64"},"selfie_count":{"type":"integer","format":"int64"},"created_at":{"type":"string","format":"date-time"}}},"ImageInput":{"type":"object","description":"Provide exactly one image source.","properties":{"url":{"type":"string","format":"uri","description":"Public image URL."},"gcsKey":{"type":"string","description":"Google Cloud Storage object key."},"photoId":{"type":"string","description":"Optional per-image key."}}},"SearchInput":{"type":"object","description":"Provide an embedding OR an image source.","properties":{"embedding":{"type":"array","items":{"type":"number"},"minItems":512,"maxItems":512},"url":{"type":"string","format":"uri"},"gcsKey":{"type":"string"},"threshold":{"type":"number","description":"Min cosine similarity (0-1)."},"limit":{"type":"integer","description":"Max matches."}}},"BBox":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"w":{"type":"integer"},"h":{"type":"integer"}}},"IndexedFace":{"type":"object","properties":{"face_index":{"type":"integer"},"point_id":{"type":"string","description":"The faceId analog; usable with search-by-id."},"det_score":{"type":"number"},"min_px":{"type":"integer"},"bbox":{"$ref":"#/components/schemas/BBox"}}},"IndexResult":{"type":"object","properties":{"collection_id":{"type":"string"},"photo_id":{"type":"string"},"indexed":{"type":"integer","description":"Faces stored."},"detected_face_count":{"type":"integer"},"rejected_face_count":{"type":"integer","description":"Detected but quality-gated out."},"faces":{"type":"array","items":{"$ref":"#/components/schemas/IndexedFace"}},"model_version":{"type":"string"}}},"Match":{"type":"object","properties":{"photo_id":{"type":"string"},"score":{"type":"number","description":"Cosine similarity (0-1)."}}},"SearchResult":{"type":"object","properties":{"collection_id":{"type":"string"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/Match"}},"photo_ids":{"type":"array","items":{"type":"string"}},"reason":{"type":"string","description":"Present when no match: no_face | low_quality_selfie | point_not_found."},"model_version":{"type":"string"}}},"SelfieResult":{"type":"object","properties":{"face_found":{"type":"boolean"},"reason":{"type":"string","description":"no_face when face_found=false."},"collection_id":{"type":"string"},"user_id":{"type":"string"},"selfie_id":{"type":"string"},"point_id":{"type":"string","description":"Pass to search-by-id."},"embedding":{"type":"array","items":{"type":"number"},"description":"512-d L2-normalized vector."},"det_score":{"type":"number"},"quality_passed":{"type":"boolean"},"model_version":{"type":"string"}}},"DetectedFace":{"type":"object","properties":{"face_index":{"type":"integer"},"det_score":{"type":"number"},"min_px":{"type":"integer"},"bbox":{"$ref":"#/components/schemas/BBox"},"quality_passed":{"type":"boolean"}}},"DetectResult":{"type":"object","properties":{"detected_face_count":{"type":"integer"},"gated_face_count":{"type":"integer"},"faces":{"type":"array","items":{"$ref":"#/components/schemas/DetectedFace"}}}},"CompareResult":{"type":"object","properties":{"face_found":{"type":"boolean"},"similarity":{"type":["number","null"],"description":"Cosine similarity (0-1)."},"match":{"type":"boolean"},"threshold":{"type":"number"}}},"ApiKeyMeta":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","example":"active"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}}},"AutoRecharge":{"type":"object","properties":{"enabled":{"type":"boolean"},"threshold_credits":{"type":"integer","format":"int64"},"recharge_credits":{"type":"integer","format":"int64"},"has_mandate":{"type":"boolean"},"dodo_customer_id":{"type":"string"},"failures":{"type":"integer"},"min_topup_credits":{"type":"integer","format":"int64"}}}}}}