{"openapi":"3.1.0","info":{"title":"AI-CORE V2 - API Gateway","description":"\nAPI-key authenticated gateway: the single public front door for external\nconsumers (Mindy, aipen, ...) to AI-CORE job services.\n\n**Start here: the [integration guide](.)** — authentication, sync vs async\nflows, polling, callbacks, error model.\n\n## Authentication\n\nEvery call needs `X-Api-Key` plus the identity headers `X-Tenant` /\n`X-User` (logged for usage accounting). The consumer system is derived\nfrom the API key itself.\n\n## Sync vs async\n\nJob routes accept the service's native request plus an optional `callback_url`:\n\n- `callback_url` set → immediate `202 {\"job_id\"}`; the terminal result is\n  POSTed to the callback (always delivered — errors and timeouts included).\n- `callback_url` absent, bounded task (metadata_extraction, summarization) →\n  the gateway parks the request up to 180 s and returns `200` with the native\n  response, degrading to `202` + `Location` poll header under load. Sync\n  callers MUST handle the `202` case.\n- `callback_url` absent, long task (pdf_anonymization) → fire-and-poll:\n  immediate `202` + `Location`, poll for the result (available 600 s after\n  completion).\n","contact":{"name":"Maggioli R&D - AI","email":"alessandro.neri@maggioli.it"},"version":"3.16.1"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health Check","description":"Health check endpoint for liveness probes","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/pdf_anonymization":{"post":{"tags":["pdf_anonymization"],"summary":"Async Pdf Anonymization","description":"Async-only: pdf-anonymization jobs can run up to ~900 s, far beyond the sync-emulation park budget, so this always returns 202 {job_id} immediately. With a callback_url the result is POSTed there when done; without one the call is fire-and-poll — follow the Location header to GET /api/v1/jobs/{job_id}/result (the terminal result stays available 600 s after completion, so prefer a callback for unattended integrations).","operationId":"async_pdf_anonymization_api_v1_pdf_anonymization_post","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"X-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant","title":"X-Tenant"},"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant"},{"name":"X-User","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End user on whose behalf the call is made (400 if missing)","title":"X-User"},"description":"End user on whose behalf the call is made (400 if missing)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPdfAnonymization"},"examples":{"async_callback":{"summary":"Submit with callback","description":"Returns 202 {job_id}; the anonymized PDF (base64) is POSTed to callback_url when done (jobs can run ~900 s). Exactly one of doc_reference / doc_content must be set.","value":{"doc_reference":"https://example.org/documents/referto.pdf","structured_detection":true,"embedded_files":"anonymize","callback_url":"https://consumer.example.org/webhooks/aicore"}},"fire_and_poll":{"summary":"Submit without callback (poll)","description":"Returns 202 {job_id} + Location; poll GET /api/v1/jobs/{job_id}/result until 200 (the result expires 600 s after completion).","value":{"doc_reference":"https://example.org/documents/referto.pdf"}}}}}},"responses":{"202":{"description":"Job accepted. With a callback_url the terminal result is POSTed there; without one, poll the Location header URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobAccepted"}}},"headers":{"Location":{"description":"Poll URL (GET /api/v1/jobs/{job_id}/result) — present only when the job was submitted without callback_url","schema":{"type":"string"}}}},"400":{"description":"Missing mandatory identity headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"401":{"description":"Missing, unknown, disabled or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"403":{"description":"Key not scoped for this service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"502":{"description":"Job failed without a business status, or upstream error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"503":{"description":"Orchestrator not enabled on this gateway.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metadata_extraction":{"post":{"tags":["metadata_extraction"],"summary":"Async Metadata Extraction","description":"Submit a metadata-extraction job to the orchestrator. With a callback_url the call is async: 202 {job_id} immediately and the result is POSTed to the callback. Without one (the pre-2.0 sync contract, restored in 2.1.0) the gateway parks the request on the orchestrator up to sync_wait_seconds and returns the native response; if the budget expires it degrades to 202 {job_id} and the caller polls GET /api/v1/jobs/{job_id}/result. Note: parked syncs share the orchestrator-bound connection pool (20 per gateway replica) - beyond that they queue client-side; the orchestrator additionally caps parked waiters server-side and degrades the overflow to 202.","operationId":"async_metadata_extraction_api_v1_metadata_extraction_post","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"X-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant","title":"X-Tenant"},"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant"},{"name":"X-User","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End user on whose behalf the call is made (400 if missing)","title":"X-User"},"description":"End user on whose behalf the call is made (400 if missing)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestMetadataExtraction"},"examples":{"sync":{"summary":"Sync-emulation (no callback_url)","description":"Parks up to 180 s and returns 200 with the native response; may degrade to 202 + Location. Exactly one of doc_reference / doc_content must be set.","value":{"doc_reference":"https://example.org/documents/decreto.pdf","document_kind":"auto","max_pages":10}},"async_callback":{"summary":"Async with callback","description":"Returns 202 {job_id} immediately; the terminal result is POSTed to callback_url. doc_content carries the PDF as base64 (truncated here).","value":{"doc_content":"JVBERi0xLjcKJb/3ov4KMSAwIG9iago8PC...","document_kind":"jurisprudence","language":"it","callback_url":"https://consumer.example.org/webhooks/aicore"}}}}}},"responses":{"202":{"description":"Job accepted. With a callback_url the terminal result is POSTed there; without one, poll the Location header URL (also the sync-emulation degrade path on budget expiry/overflow).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobAccepted"}}},"headers":{"Location":{"description":"Poll URL (GET /api/v1/jobs/{job_id}/result) — present only when the job was submitted without callback_url","schema":{"type":"string"}}}},"400":{"description":"Missing mandatory identity headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"401":{"description":"Missing, unknown, disabled or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"403":{"description":"Key not scoped for this service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"502":{"description":"Job failed without a business status, or upstream error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"503":{"description":"Orchestrator not enabled on this gateway.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"200":{"description":"Sync-emulation result (no callback_url): the service's native response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseMetadataExtraction"}}}},"504":{"description":"The job itself timed out (business timeout, error_code parity).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/summarization":{"post":{"tags":["summarization"],"summary":"Async Summarization","description":"Summarize a document already ingested into the platform, by `id_doc`. Three shapes: `concise` and `detailed` run a map-reduce over the whole document; `bullet_list` extracts per-topic points and reduces them to `nr_points` bullets (`nr_points` is required for `bullet_list` and rejected for the other two). With a callback_url the call is async: 202 {job_id} immediately and the result is POSTed to the callback. Without one the gateway parks the request on the orchestrator up to sync_wait_seconds and returns the native response; a long document can exceed that budget, in which case it degrades to 202 {job_id} and the caller polls GET /api/v1/jobs/{job_id}/result. Note: parked syncs share the orchestrator-bound connection pool (20 per gateway replica) - beyond that they queue client-side; the orchestrator additionally caps parked waiters server-side and degrades the overflow to 202.","operationId":"async_summarization_api_v1_summarization_post","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"X-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant","title":"X-Tenant"},"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant"},{"name":"X-User","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End user on whose behalf the call is made (400 if missing)","title":"X-User"},"description":"End user on whose behalf the call is made (400 if missing)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestSummarization"},"examples":{"concise":{"summary":"Concise summary (sync-emulation)","description":"Map-reduce over the whole document. Parks up to 180 s and returns 200 with the native response; a long document may degrade to 202 + Location.","value":{"id_doc":12345,"language":"it","type":"concise"}},"bullet_list":{"summary":"Bulleted list, N points","description":"Extracts points per topic and reduces them to nr_points bullets. nr_points is mandatory for this type only.","value":{"id_doc":12345,"language":"it","type":"bullet_list","nr_points":5}},"async_callback":{"summary":"Detailed summary, async with callback","description":"Returns 202 {job_id} immediately; the terminal result is POSTed to callback_url. Preferred for long documents.","value":{"id_doc":12345,"language":"en","type":"detailed","callback_url":"https://consumer.example.org/webhooks/aicore"}}}}}},"responses":{"202":{"description":"Job accepted. With a callback_url the terminal result is POSTed there; without one, poll the Location header URL (also the sync-emulation degrade path on budget expiry/overflow).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobAccepted"}}},"headers":{"Location":{"description":"Poll URL (GET /api/v1/jobs/{job_id}/result) — present only when the job was submitted without callback_url","schema":{"type":"string"}}}},"400":{"description":"Missing mandatory identity headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"401":{"description":"Missing, unknown, disabled or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"403":{"description":"Key not scoped for this service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"502":{"description":"Job failed without a business status, or upstream error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"503":{"description":"Orchestrator not enabled on this gateway.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"200":{"description":"Sync-emulation result (no callback_url): the service's native response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseSummarization"}}}},"504":{"description":"The job itself timed out (business timeout, error_code parity).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/result":{"get":{"tags":["jobs"],"summary":"Async Job Result","description":"Poll a parked (no-callback) job's result: 200 terminal result, 202 still\npending, 404 unknown/expired — the result stays available for the\norchestrator's result TTL after completion.\n\nAuthenticated without a fixed scope (jobs span services): the key's scope\nis checked against the job's own task after the lookup, and the result is\nonly served to the tenant that submitted it (results carry tenant document\ndata). Tenant mismatches return 404, not 403, so job ids can't be probed.","operationId":"async_job_result_api_v1_jobs__job_id__result_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","description":"The job_id returned by the 202 submit response","title":"Job Id"},"description":"The job_id returned by the 202 submit response"},{"name":"X-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant","title":"X-Tenant"},"description":"Tenant on whose behalf the call is made (400 if missing); job results are only visible to this tenant"},{"name":"X-User","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End user on whose behalf the call is made (400 if missing)","title":"X-User"},"description":"End user on whose behalf the call is made (400 if missing)"}],"responses":{"200":{"description":"Terminal result (status ok|error; errors keep their business error_code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobStatus"}}}},"202":{"description":"Still pending — poll again (suggested interval 2-5 s).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobStatus"}}}},"401":{"description":"Missing, unknown, disabled or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseDetail"}}}},"403":{"description":"Key not scoped for the polled job's task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"404":{"description":"Unknown job_id or result expired (600 s window after completion).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"503":{"description":"Orchestrator not enabled on this gateway.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scopes":{"get":{"tags":["admin"],"summary":"List Scopes","description":"Catalog of scopes a key can be issued for (the UI builds its checkboxes from this).","operationId":"list_scopes_admin_scopes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseScopes"}}}}},"security":[{"AdminKeyAuth":[]}]}},"/admin/api-keys":{"get":{"tags":["admin"],"summary":"List Api Keys","operationId":"list_api_keys_admin_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ResponseApiKeyItem"},"type":"array","title":"Response List Api Keys Admin Api Keys Get"}}}}},"security":[{"AdminKeyAuth":[]}]},"post":{"tags":["admin"],"summary":"Issue Api Key","description":"Issue a new API key. The plaintext key is returned once and never stored.","operationId":"issue_api_key_admin_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestIssueApiKey"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseIssueApiKey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"AdminKeyAuth":[]}]}},"/admin/api-keys/{key_id}":{"patch":{"tags":["admin"],"summary":"Update Api Key","description":"Edit an issued key in place: permissions, expiry, enable/disable, description. Only the\nfields sent are changed; the key value/hash is never touched, so consumers keep working.","operationId":"update_api_key_admin_api_keys__key_id__patch","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestUpdateApiKey"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseApiKeyItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Revoke Api Key","operationId":"revoke_api_key_admin_api_keys__key_id__delete","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api-keys/{key_id}/usage":{"get":{"tags":["admin"],"summary":"Get Api Key Usage","description":"Usage history of a key, newest first: every call the key was identified for\n(successes, downstream errors and denied attempts), filterable.","operationId":"get_api_key_usage_admin_api_keys__key_id__usage_get","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"service","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the service scope hit","title":"Service"},"description":"Exact-match filter on the service scope hit"},{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on X-Tenant","title":"Tenant"},"description":"Exact-match filter on X-Tenant"},{"name":"user","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on X-User","title":"User"},"description":"Exact-match filter on X-User"},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 lower time bound (inclusive)","title":"From"},"description":"ISO-8601 lower time bound (inclusive)"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 upper time bound (inclusive)","title":"To"},"description":"ISO-8601 upper time bound (inclusive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseUsage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/usage/summary":{"get":{"tags":["admin"],"summary":"Get Usage Summary","description":"Chargeback view: tokens and cost per system/tenant/model over a time window.\n\nCost is joined from the versioned price table at read time, so correcting a\nprice fixes every past report instead of requiring history to be rewritten.\n``complete: false`` means at least one group had no applicable price and the\ntotal therefore understates reality — check it before invoicing anything.","operationId":"get_usage_summary_admin_usage_summary_get","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"system","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the consumer system","title":"System"},"description":"Exact-match filter on the consumer system"},{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the tenant","title":"Tenant"},"description":"Exact-match filter on the tenant"},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 lower time bound (inclusive)","title":"From"},"description":"ISO-8601 lower time bound (inclusive)"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 upper time bound (inclusive)","title":"To"},"description":"ISO-8601 upper time bound (inclusive)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseUsageSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs":{"get":{"tags":["admin"],"summary":"Get Jobs","description":"Job history with the token spend and cost of each job.\n\nBefore this existed a job left no readable trace once its result key expired:\nthe table was written in Phase 1 but never read. Cost is joined from the\nversioned price table at read time by the same function the chargeback summary\nuses, so the two can never disagree.\n\n``stats`` describes the whole filtered window, not the returned page.","operationId":"get_jobs_admin_jobs_get","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"system","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the consumer system","title":"System"},"description":"Exact-match filter on the consumer system"},{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the tenant","title":"Tenant"},"description":"Exact-match filter on the tenant"},{"name":"service","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact-match filter on the service (== scope)","title":"Service"},"description":"Exact-match filter on the service (== scope)"},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"submitted | completed | failed","title":"Event"},"description":"submitted | completed | failed"},{"name":"delivery","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"delivered | parked | dead_lettered","title":"Delivery"},"description":"delivered | parked | dead_lettered"},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 lower bound on submitted_at (inclusive)","title":"From"},"description":"ISO-8601 lower bound on submitted_at (inclusive)"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 upper bound on submitted_at (inclusive)","title":"To"},"description":"ISO-8601 upper bound on submitted_at (inclusive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobs"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}":{"get":{"tags":["admin"],"summary":"Get Job Detail","description":"One job with every LLM call it made, each priced individually — the\narithmetic behind its cost, plus a link to its Langfuse session when one is\nconfigured.","operationId":"get_job_detail_admin_jobs__job_id__get","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseJobDetail"}}}},"404":{"description":"No job history for that id (never submitted, or older than the retention window)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/dlq":{"get":{"tags":["admin"],"summary":"Get Dlq","description":"Dead-lettered results, oldest first, with the stream depth the alert watches.","operationId":"get_dlq_admin_dlq_get","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"start","in":"query","required":false,"schema":{"type":"string","description":"Stream id to page from; pass the last entry_id seen","default":"-","title":"Start"},"description":"Stream id to page from; pass the last entry_id seen"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrchestratorDlq"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/dlq/{entry_id}/replay":{"post":{"tags":["admin"],"summary":"Replay Dlq Entry","description":"Re-queue a dead-lettered result so COLLECT delivers it again.\n\nThis re-attempts **delivery of a result that already exists** — the job is not\nre-run, so no tokens are spent and the consumer receives the same answer it\noriginally missed.","operationId":"replay_dlq_entry_admin_dlq__entry_id__replay_post","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrchestratorDlqAction"}}}},"404":{"description":"No such entry — already replayed, discarded or trimmed"},"422":{"description":"The entry never parsed as a result; discard it instead"}}}},"/admin/dlq/{entry_id}":{"delete":{"tags":["admin"],"summary":"Discard Dlq Entry","description":"Drop a dead-letter entry without re-queuing it — the only exit for an entry\nthat never parsed, and what lets the DLQ alert clear.","operationId":"discard_dlq_entry_admin_dlq__entry_id__delete","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrchestratorDlqAction"}}}},"404":{"description":"No such entry — already replayed, discarded or trimmed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/usage/prices":{"get":{"tags":["admin"],"summary":"Get Usage Prices","operationId":"get_usage_prices_admin_usage_prices_get","security":[{"AdminKeyAuth":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponsePrices"}}}}}},"put":{"tags":["admin"],"summary":"Put Usage Price","description":"Publish a unit price. Idempotent per (effective_from, provider, model, kind).\n\nPrices are configuration, not code: a provider price change is a call to this\nendpoint, never a deploy.","operationId":"put_usage_price_admin_usage_prices_put","security":[{"AdminKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsagePriceItem"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete Usage Price","description":"Retract a published price. The counterpart to PUT, and not a nicety.\n\nA hand-written row outranks anything ``price_sync`` writes with an older\n``startDate``, and Langfuse dates managed definitions from the model's launch —\nso a row published once keeps overriding list price forever, and until now\nthere was no way to give a model back to the sync but to edit the database by\nhand. All four key parts are required: a price is retracted deliberately, one\nrow at a time, never by prefix.","operationId":"delete_usage_price_admin_usage_prices_delete","security":[{"AdminKeyAuth":[]}],"parameters":[{"name":"effective_from","in":"query","required":true,"schema":{"type":"string","format":"date","description":"Exact date of the row to retract","title":"Effective From"},"description":"Exact date of the row to retract"},{"name":"model","in":"query","required":true,"schema":{"type":"string","description":"Exact model of the row to retract","title":"Model"},"description":"Exact model of the row to retract"},{"name":"kind","in":"query","required":true,"schema":{"type":"string","description":"input | output | cache_read | reasoning","title":"Kind"},"description":"input | output | cache_read | reasoning"},{"name":"provider","in":"query","required":false,"schema":{"type":"string","description":"Exact provider of the row; empty matches the provider-agnostic row","default":"","title":"Provider"},"description":"Exact provider of the row; empty matches the provider-agnostic row"}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No price row with that exact (effective_from, provider, model, kind)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Collaborator":{"properties":{"name":{"type":"string","title":"Name","description":"The collaborator's name, as printed in the document, never translated"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Role in the document's own terminology, lowercase, never translated (e.g. 'curatore', 'translator'); null when not stated"}},"type":"object","required":["name"],"title":"Collaborator"},"DocumentKind":{"type":"string","enum":["auto","normative","jurisprudence","praxis","book","generic"],"title":"DocumentKind"},"EmbeddedFilesMode":{"type":"string","enum":["anonymize","remove","keep"],"title":"EmbeddedFilesMode"},"ExtractedMetadata":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"reference_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Date","description":"Emission/act date, ISO format YYYY-MM-DD"},"publication_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publication Date","description":"Publication date (e.g. Gazzetta Ufficiale), ISO format YYYY-MM-DD"},"issuing_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuing Body","description":"Issuing body/authority (organo)"},"act_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Act Number","description":"Act/document number"},"act_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Act Type","description":"Act type, e.g. 'decreto legislativo', 'circolare'"},"isbn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Isbn"},"authors":{"items":{"type":"string"},"type":"array","title":"Authors","default":[]},"collaborators":{"items":{"$ref":"#/components/schemas/Collaborator"},"type":"array","title":"Collaborators","description":"Secondary contributors (curatori, traduttori, illustratori, prefatori), distinct from the primary authors","default":[]}},"type":"object","title":"ExtractedMetadata"},"FileType":{"type":"string","enum":["pdf","docx","txt","md","epub","html","xlsx"],"title":"FileType"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobLlmCall":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"llm_alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Alias"},"call_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Call Type"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"tokens":{"$ref":"#/components/schemas/JobTokens"},"cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost"}},"type":"object","required":["ts","tokens"],"title":"JobLlmCall","description":"One metered LLM call belonging to a job — the audit trail behind its cost."},"JobRow":{"properties":{"job_id":{"type":"string","title":"Job Id"},"submitted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Submitted At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"completed_at - submitted_at; null while a job is still open or if its submitted event was lost"},"event":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"delivery":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery"},"error_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Error Code"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service"},"task":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task"},"system":{"type":"string","title":"System"},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"},"user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User"},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Id"},"tokens":{"$ref":"#/components/schemas/JobTokens"},"cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost","description":"Null when at least one of the job's models had no applicable price — never a silent zero"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"}},"type":"object","required":["job_id","system"],"title":"JobRow","description":"One job, with the spend it caused.\n\n``event`` is the job's own outcome (submitted / completed / failed) while\n``delivery`` is how its result reached the consumer (delivered / parked /\ndead_lettered). They are independent: a failed job can be delivered perfectly\nand a successful one can end up dead-lettered."},"JobStats":{"properties":{"jobs":{"type":"integer","title":"Jobs","default":0},"by_event":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Event"},"by_delivery":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Delivery"},"tokens":{"$ref":"#/components/schemas/JobTokens"},"cost":{"type":"number","title":"Cost","default":0.0},"currency":{"type":"string","title":"Currency","default":"USD"},"complete":{"type":"boolean","title":"Complete","description":"False when some model in the window had no price, i.e. cost understates reality","default":true}},"type":"object","title":"JobStats","description":"Totals over the whole filtered window, NOT just the returned page — a\ndashboard number that changed with the page size would be a trap."},"JobTokens":{"properties":{"calls":{"type":"integer","title":"Calls","default":0},"input_tokens":{"type":"integer","title":"Input Tokens","default":0},"output_tokens":{"type":"integer","title":"Output Tokens","default":0},"cache_read_tokens":{"type":"integer","title":"Cache Read Tokens","default":0},"reasoning_tokens":{"type":"integer","title":"Reasoning Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0}},"type":"object","title":"JobTokens","description":"Token totals of one job, or of one LLM call inside it.\n\n``validate_assignment`` is load-bearing, not tidiness: these totals are\naccumulated field-by-field with ``setattr`` from Postgres aggregates, and\n``SUM()`` over a ``BigInteger`` column returns ``NUMERIC`` — a ``Decimal``.\nWithout validation on assignment the Decimal survives into the serializer and\nthe API answers ``\"input_tokens\": \"18992\"``, a string, for every non-zero\ntotal (a zero stays an int only because ``Decimal('0') or 0`` is falsy). It\nbelongs on the class rather than at each accumulation site because the next\nreader of the `jobs` table would have to remember the cast too."},"Language":{"type":"string","enum":["en","es","fr","de","zh","ja","ko","it","pt","ru","ar","nl","pl","tr","vi","hi","id","th","cs","sv","el","ro","hu","da","fi","no","uk","he","bn","ms","ta","ur","fa","bg","sk","hr","sr","lt","sl","et","lv","sw"],"title":"Language"},"OcrMode":{"type":"string","enum":["auto","force","off"],"title":"OcrMode"},"OrchestratorDlq":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/OrchestratorDlqEntry"},"type":"array","title":"Entries"},"depth":{"type":"integer","title":"Depth"}},"type":"object","required":["entries","depth"],"title":"OrchestratorDlq"},"OrchestratorDlqAction":{"properties":{"entry":{"$ref":"#/components/schemas/OrchestratorDlqEntry"},"depth":{"type":"integer","title":"Depth"}},"type":"object","required":["entry","depth"],"title":"OrchestratorDlqAction"},"OrchestratorDlqEntry":{"properties":{"entry_id":{"type":"string","title":"Entry Id"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service"},"task":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"error_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Error Code"},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url"},"system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System"},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"},"user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User"},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Id"},"parse_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parse Error"},"payload_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payload Preview"},"payload_chars":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payload Chars"},"payload_truncated":{"type":"boolean","title":"Payload Truncated","default":false}},"type":"object","required":["entry_id"],"title":"OrchestratorDlqEntry","description":"One dead-letter entry as the orchestrator describes it (epic #14).\n\nEverything but ``entry_id`` is optional on purpose: the DLQ holds both results\nwhose delivery failed (fully described) and entries that never parsed\n(``parse_error`` set, replay impossible, discard the only exit)."},"RequestIssueApiKey":{"properties":{"system":{"type":"string","minLength":1,"title":"System","description":"Consumer system the key is issued to"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Services the key can call: ['*'] or a subset of the known scopes"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Who is requesting the key (free text)"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"Optional ISO-8601 expiry (UTC); null = never expires"},"rate_limit_per_min":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Rate Limit Per Min","description":"Max requests per minute; null = unlimited"},"max_concurrent_jobs":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Max Concurrent Jobs","description":"Max jobs in flight; null = unlimited"}},"type":"object","required":["system","scopes"],"title":"RequestIssueApiKey"},"RequestMetadataExtraction":{"properties":{"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url"},"doc_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Content","description":"Base64-encoded document content"},"doc_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Reference","description":"URL or file:// reference to the document"},"file_type":{"anyOf":[{"$ref":"#/components/schemas/FileType"},{"type":"null"}],"description":"Type of the document file (pdf, docx, epub, ...); None lets the storage service auto-detect it"},"document_kind":{"$ref":"#/components/schemas/DocumentKind","description":"Document taxonomy hint; 'auto' lets the service classify the document itself","default":"auto"},"language":{"anyOf":[{"$ref":"#/components/schemas/Language"},{"type":"null"}],"description":"Output language for the generated free-text fields (e.g. description). A specific language forces that language; omit/null keeps the document's own language."},"max_pages":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Max Pages","description":"Maximum number of leading pages analyzed for extraction","default":10}},"type":"object","title":"RequestMetadataExtraction"},"RequestPdfAnonymization":{"properties":{"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url"},"doc_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Content","description":"Base64-encoded PDF content"},"doc_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Reference","description":"URL or file:// reference to the PDF"},"categories_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Categories Prompt","description":"Custom category-definitions prompt; default categories are used when omitted"},"structured_detection":{"type":"boolean","title":"Structured Detection","description":"Enable checksum-validated detectors for Italian fiscal codes and VAT numbers","default":true},"ocr_mode":{"$ref":"#/components/schemas/OcrMode","default":"auto"},"embedded_files":{"$ref":"#/components/schemas/EmbeddedFilesMode","description":"How to handle PDF embedded files (e.g. CDA/HL7 XML attachments): anonymize their textual content, remove them, or keep them untouched. In anonymize mode, non-textual attachments are removed (fail-closed).","default":"anonymize"},"scrub_metadata":{"type":"boolean","title":"Scrub Metadata","description":"Clear the PDF document metadata (Info dictionary and XMP), which may contain personal data such as patient identifiers.","default":true}},"type":"object","title":"RequestPdfAnonymization"},"RequestSummarization":{"properties":{"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url"},"id_doc":{"type":"integer","title":"Id Doc"},"language":{"$ref":"#/components/schemas/Language"},"type":{"$ref":"#/components/schemas/SummarizationType"},"nr_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Nr Points"}},"type":"object","required":["id_doc","language","type"],"title":"RequestSummarization"},"RequestUpdateApiKey":{"properties":{"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"rate_limit_per_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Limit Per Min"},"max_concurrent_jobs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrent Jobs"}},"type":"object","title":"RequestUpdateApiKey","description":"Partial update of an issued key. Only the fields present are changed; the key value is\nnever touched. Send ``expires_at: null`` to clear an expiry (make the key never expire)."},"ResponseApiKeyItem":{"properties":{"id":{"type":"string","title":"Id"},"system":{"type":"string","title":"System"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"enabled":{"type":"boolean","title":"Enabled"},"created_at":{"type":"string","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"rate_limit_per_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Limit Per Min"},"max_concurrent_jobs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrent Jobs"}},"type":"object","required":["id","system","scopes","enabled","created_at"],"title":"ResponseApiKeyItem"},"ResponseDetail":{"properties":{"detail":{"type":"string","title":"Detail"}},"type":"object","required":["detail"],"title":"ResponseDetail","description":"FastAPI HTTPException body — auth/validation failures raised by the\nrequire_scope / admin-key dependencies (endpoints/__init__.py)."},"ResponseError":{"properties":{"code":{"type":"integer","title":"Code"},"message":{"type":"string","title":"Message"}},"type":"object","required":["code","message"],"title":"ResponseError"},"ResponseIssueApiKey":{"properties":{"id":{"type":"string","title":"Id"},"system":{"type":"string","title":"System"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"api_key":{"type":"string","title":"Api Key","description":"The plaintext API key. Shown only once, at issue time."},"created_at":{"type":"string","title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","system","scopes","api_key","created_at"],"title":"ResponseIssueApiKey"},"ResponseJobAccepted":{"properties":{"job_id":{"type":"string","title":"Job Id","examples":["1f0c1e0a-7b6e-4b9e-9b1a-2a7c8d9e0f11"]}},"type":"object","required":["job_id"],"title":"ResponseJobAccepted","description":"202 submit body (job_routes.py): the job was accepted; the result\narrives on callback_url, or via GET /api/v1/jobs/{job_id}/result when the\nsubmit degraded from sync-emulation (Location header)."},"ResponseJobDetail":{"properties":{"job":{"$ref":"#/components/schemas/JobRow"},"calls":{"items":{"$ref":"#/components/schemas/JobLlmCall"},"type":"array","title":"Calls","description":"Oldest first: the order the job made them"},"langfuse_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Langfuse Url","description":"Deep link to this job's Langfuse session, when a template is configured"}},"type":"object","required":["job","calls"],"title":"ResponseJobDetail"},"ResponseJobStatus":{"properties":{"job_id":{"type":"string","title":"Job Id"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service","description":"Worker service slug, e.g. 'metadata-extraction'"},"task":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task","description":"Task / scope name, e.g. 'metadata_extraction'"},"status":{"type":"string","title":"Status","description":"\"pending\" | \"ok\" | \"error\""},"response":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Response","description":"The service's native response when status=ok"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message when status=error"},"error_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Error Code","description":"The job's business HTTP status when status=error (e.g. 504 timeout)"}},"type":"object","required":["job_id","status"],"title":"ResponseJobStatus","description":"Poll body (jobs.py): 200 terminal (status ok|error), 202 pending.\nMirrors OrchestratorJobOutcome (app/models/orchestrator.py) minus\nid_tenant, which is never exposed; None fields are omitted."},"ResponseJobs":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/JobRow"},"type":"array","title":"Rows","description":"Newest first"},"stats":{"$ref":"#/components/schemas/JobStats"}},"type":"object","required":["rows","stats"],"title":"ResponseJobs"},"ResponseMetadataExtraction":{"properties":{"detected_kind":{"$ref":"#/components/schemas/DocumentKind"},"detected_language":{"anyOf":[{"$ref":"#/components/schemas/Language"},{"type":"null"}],"description":"ISO code of the document's main/original language, independent of the output language; null if undetermined"},"metadata":{"$ref":"#/components/schemas/ExtractedMetadata"},"extracted_fields":{"items":{"type":"string"},"type":"array","title":"Extracted Fields","description":"Names of metadata fields with a non-empty extracted value","default":[]},"pages_analyzed":{"type":"integer","title":"Pages Analyzed"}},"type":"object","required":["detected_kind","metadata","pages_analyzed"],"title":"ResponseMetadataExtraction"},"ResponsePrices":{"properties":{"prices":{"items":{"$ref":"#/components/schemas/UsagePriceItem"},"type":"array","title":"Prices"}},"type":"object","required":["prices"],"title":"ResponsePrices"},"ResponseScopes":{"properties":{"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"}},"type":"object","required":["scopes"],"title":"ResponseScopes"},"ResponseSummarization":{"properties":{"text":{"type":"string","title":"Text"}},"type":"object","required":["text"],"title":"ResponseSummarization"},"ResponseUsage":{"properties":{"events":{"items":{"$ref":"#/components/schemas/UsageEvent"},"type":"array","title":"Events","description":"Matching usage events, newest first"}},"type":"object","required":["events"],"title":"ResponseUsage"},"ResponseUsageSummary":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/UsageSummaryRow"},"type":"array","title":"Rows","description":"Groups, heaviest token spend first"},"total_cost":{"type":"number","title":"Total Cost","description":"Sum of the priced rows only"},"currency":{"type":"string","title":"Currency"},"complete":{"type":"boolean","title":"Complete","description":"False when at least one group had no applicable price, i.e. total_cost understates reality"}},"type":"object","required":["rows","total_cost","currency","complete"],"title":"ResponseUsageSummary"},"SummarizationType":{"type":"string","enum":["detailed","concise","bullet_list"],"title":"SummarizationType"},"UsageEvent":{"properties":{"ts":{"type":"string","title":"Ts","description":"ISO-8601 timestamp of the call (UTC)"},"system":{"type":"string","title":"System","description":"Consumer system the API key was issued to"},"tenant":{"type":"string","title":"Tenant","description":"X-Tenant header of the call"},"user":{"type":"string","title":"User","description":"X-User header of the call"},"service":{"type":"string","title":"Service","description":"Service scope hit, e.g. 'pdf_anonymization'"},"status":{"type":"integer","title":"Status","description":"HTTP status returned to the caller"},"duration_ms":{"type":"integer","title":"Duration Ms"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id","description":"Job the call created, when it created one — the join key to the job and its token spend"}},"type":"object","required":["ts","system","tenant","user","service","status","duration_ms"],"title":"UsageEvent","description":"One authenticated gateway call, as recorded in the per-key usage stream."},"UsagePriceItem":{"properties":{"effective_from":{"type":"string","format":"date","title":"Effective From","description":"First day this price applies; a change is a new row, so past reports stay reproducible"},"provider":{"type":"string","title":"Provider","description":"Provider label, e.g. azureOpenAi (empty to price a model regardless of provider)"},"model":{"type":"string","title":"Model"},"kind":{"type":"string","title":"Kind","description":"Token kind billed: input | output | cache_read | reasoning"},"unit_price":{"type":"number","minimum":0.0,"title":"Unit Price","description":"Cost of one token"},"currency":{"type":"string","maxLength":3,"minLength":3,"title":"Currency","description":"Defaults to USD, the currency Langfuse publishes list prices in","default":"USD"}},"type":"object","required":["effective_from","provider","model","kind","unit_price"],"title":"UsagePriceItem","description":"One versioned unit price. Prices are per SINGLE token — providers quote\nper million, so divide before posting."},"UsageSummaryRow":{"properties":{"system":{"type":"string","title":"System"},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"calls":{"type":"integer","title":"Calls"},"input_tokens":{"type":"integer","title":"Input Tokens"},"output_tokens":{"type":"integer","title":"Output Tokens"},"cache_read_tokens":{"type":"integer","title":"Cache Read Tokens"},"reasoning_tokens":{"type":"integer","title":"Reasoning Tokens"},"total_tokens":{"type":"integer","title":"Total Tokens"},"cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost","description":"Cost from the versioned price table; null when no price covers this model"}},"type":"object","required":["system","calls","input_tokens","output_tokens","cache_read_tokens","reasoning_tokens","total_tokens"],"title":"UsageSummaryRow","description":"Token totals for one (system, tenant, provider, model) group."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","description":"API key issued by the gateway admin. 401 if missing, unknown, disabled or expired.","in":"header","name":"X-Api-Key"},"AdminKeyAuth":{"type":"apiKey","description":"Static master key protecting the API-key administration endpoints.","in":"header","name":"X-Admin-Key"}}},"tags":[{"name":"metadata_extraction","description":"Extract structured metadata from a PDF. Sync-emulation or callback."},{"name":"pdf_anonymization","description":"Anonymize a PDF. Always 202 (jobs can run ~900 s): result via callback_url, or poll without one."},{"name":"summarization","description":"Summarize an ingested document (concise, detailed or bulleted list). Sync-emulation or callback."},{"name":"jobs","description":"Poll parked (no-callback) job results. Tenant-guarded: another tenant's job reads as 404."},{"name":"admin","description":"API-key administration (X-Admin-Key). Web console at admin/ui."},{"name":"health","description":"Liveness probe."}]}