{"info":{"title":"ARSE - Automated Recognition and Structured Extraction","contact":{"url":"https://console.hank.ai/","name":"Hank"},"license":{"name":"Proprietary - © Hank. All rights reserved.","identifier":"LicenseRef-Proprietary"},"summary":"Page-accurate structured extraction of payer documents, with gated X12 835 remittances.","version":"0.1.0","description":"**ARSE - Automated Recognition and Structured Extraction.**\n\nARSE ingests inbound payer documents (EOBs, correspondence, mixed \"digital lockbox\" mail)\nover an API, processes them page-by-page with vision LLMs, and returns a machine-readable\ntree of every patient, document, and claim inside - emitting gated X12 835 remittances for\nEOBs.\n\n### Authentication\n\nAll routes live behind the Hank Caddy **edge**, which validates the caller's bearer token\nand injects a gateway secret plus identity headers. ARSE never sees the bearer token; it\ntrusts the edge-injected headers **only** after a constant-time match on the gateway\nsecret:\n\n* `X-Hank-Gateway-Secret` - the shared edge secret (constant-time compared; a mismatch is\n  `401`).\n* `X-Hank-Organization-Id`, `-Plan-Key`, `-Token-Id`, `-Scopes`, `-Is-Admin` - the resolved\n  identity. Every tenant-scoped route resolves its row `WHERE org_id = <caller org>`; a\n  cross-org or unknown id is an indistinguishable **404** (never a `403` existence oracle).\n\n### Error shape\n\nEvery non-2xx returns the same envelope: `{\"error\": {\"type\", \"code\", \"message\"}}` (the\n`ApiErrorResponse` schema). Branch on `error.code` (a stable slug), not the message.\n\n### Delivery ergonomics\n\n`POST /process` **never rejects on content** - malformed/zero-byte/oversized files\nare accepted, queued, and resolved to an error state downstream (only auth and an\nidempotency conflict return non-2xx at intake). Poll `GET /files/{file_id}` for a\n`progress` block + `Retry-After` while in flight, or the full result (with an `ETag` for\nconditional GETs) when done.\n\n> **Paths are root-relative to the `servers` base** (`/v1/arse`): a public caller reaches\n> them through the Hank edge as `api.hank.ai/v1/arse/process`, which strips `/v1/arse` and\n> forwards `/process` to the backend.\n>\n> **Note:** the live-progress WebSocket (`/files/{file_id}/progress`) is part of\n> the API but is **not** described in this OpenAPI document - OpenAPI does not model\n> WebSocket channels. See the AsyncAPI doc for the progress/webhook event contracts.\n"},"tags":[{"name":"intake","description":"Never-reject file intake - accept + durably queue a payer document."},{"name":"files","description":"Org-scoped status + result delivery (progress, Retry-After, ETag)."},{"name":"remittances","description":"Emitted X12 835s + the operator review loop (release / reject / edit-and-rebuild) and the exceptions queue."},{"name":"webhooks","description":"Per-org callback registration (SSRF-guarded, secret encrypted at rest)."},{"name":"finops","description":"Per-org spend, budgets, burn status, and the held-but-billed gap."},{"name":"calibration","description":"Shadow-calibration report: gate would-have verdict vs the human release/reject decision (auto-release flip readiness)."},{"name":"worklist","description":"Correspondence SLA worklist - deadline-sorted analyst work (denials/appeals)."},{"name":"audit","description":"PHI-read accounting-of-disclosures query (HIPAA)."},{"name":"onboarding","description":"Per-org provisioning readiness checklist."},{"name":"progress","description":"Live page-by-page progress WebSocket (not described in OpenAPI - see AsyncAPI)."},{"name":"system","description":"Liveness/readiness probes."}],"paths":{"/health":{"get":{"tags":["system"],"summary":"Liveness/readiness probe","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Healthcheck","additionalProperties":{"type":"string"}}}},"description":"Successful Response"}},"description":"Liveness/readiness. Open (no gateway secret) - the platform probe carries no\nedge headers. Returns ``{\"status\": \"ok\"}`` whenever the process is serving.","operationId":"healthCheck"}},"/process":{"post":{"tags":["intake"],"summary":"Accept a file for processing (never rejects on content)","responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessAccepted","type":"object","title":"Response Processfile","additionalProperties":true}}},"description":"Accepted and durably queued (or a true Idempotency-Key replay)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"Missing or invalid gateway secret (the only intake-time rejection besides 409)."},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"idempotency_conflict","type":"Conflict","message":"the request conflicts with current state"}}}},"description":"Idempotency-Key reused with different content - never aliased or double-created."}},"description":"Accept a file and queue it for processing. **Never rejects on content.**","operationId":"processFile"}},"/webhooks":{"get":{"tags":["webhooks"],"summary":"List the caller org's registered webhook endpoints (secrets never included)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"}},"description":"List the caller org's registered webhook endpoints (secrets never included).\nOrg-scoped: only the caller's own endpoints, any authenticated member may read.","operationId":"listWebhooks"},"post":{"tags":["webhooks"],"summary":"Register a per-org webhook endpoint","responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unsafe_url","type":"BadRequest","message":"the request was malformed"}}}},"description":"Invalid events, or a URL the SSRF guard rejects (non-https / internal / private host)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"duplicate_url","type":"Conflict","message":"the request conflicts with current state"}}}},"description":"Duplicate URL for this org, or the per-org endpoint cap is reached."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unprocessable_entity","type":"UnprocessableEntity","message":"the request body failed validation"}}}},"description":"the request body failed validation"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"encryption_unavailable","type":"ServerError","message":"an internal error occurred"}}}},"description":"WEBHOOK_ENCRYPTION_KEY is not configured - we refuse to store an unprotectable secret."}},"description":"Register a per-org webhook endpoint.\n\nValidates the events are a non-empty subset of ``completed``/``exception``, runs the URL\nthrough the **SSRF guard at registration time** (https-only, public host - an internal\nURL is a 400, not a stored endpoint discovered later), rejects a duplicate ``(org_id,\nurl)`` or a registration past the per-org cap (both **409**), encrypts the signing secret,\nand inserts the row scoped to the caller's org. Returns **201** with the endpoint (secret\nomitted). A missing/invalid ``WEBHOOK_ENCRYPTION_KEY`` is a **500** (fail loud - we will\nnot store a webhook whose secret we cannot protect).","operationId":"registerWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookBody"}}},"required":true}}},"/worklist":{"get":{"tags":["worklist"],"summary":"The org's correspondence SLA worklist (deadline-sorted)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"invalid_as_of","type":"BadRequest","message":"the request was malformed"}}}},"description":"as_of is not an ISO date, or band/status is not a known value."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"intent","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Intent"},"required":false},{"in":"query","name":"band","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Band"},"required":false},{"in":"query","name":"status","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"required":false},{"in":"query","name":"as_of","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of"},"required":false},{"in":"query","name":"limit","schema":{"type":"integer","title":"Limit","default":100,"maximum":500,"minimum":1},"required":false},{"in":"query","name":"offset","schema":{"type":"integer","title":"Offset","default":0,"minimum":0},"required":false}],"description":"The org's correspondence SLA worklist - actionable items, deadline-sorted.\n\nOrg-scoped (any authenticated org member); never admin-gated. Returns the actionable\ncorrespondence documents for the caller's org, each with its soonest due date,\n``days_remaining``, ``aging_band``, and work-item ``status``, **sorted soonest-first with\nunknown-deadline items surfaced at the top**.\n\nQuery params:\n\n* ``?status=`` - ``new``/``in_progress``/``resolved``/``snoozed``. Omitted ⇒ the **active**\n  queue (excludes resolved + still-snoozed; a snoozed item whose snooze elapsed re-surfaces).\n* ``?intent=`` - filter to a single correspondence intent.\n* ``?band=``   - filter to one aging band (overdue/urgent/soon/ok/unknown).\n* ``?as_of=YYYY-MM-DD`` - override \"now\" (deterministic for tests); defaults to today.\n* ``?limit=&offset=`` - page the (already sorted) result.","operationId":"worklist"}},"/audit/phi":{"get":{"tags":["audit"],"summary":"Query this org's PHI-read disclosure log (newest-first)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"invalid_date","type":"BadRequest","message":"the request was malformed"}}}},"description":"A from/to query param is not an ISO date (YYYY-MM-DD)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"action","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"},"required":false},{"in":"query","name":"target_id","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id"},"required":false},{"in":"query","name":"from","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"},"required":false},{"in":"query","name":"to","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"required":false},{"in":"query","name":"limit","schema":{"type":"integer","title":"Limit","default":50},"required":false},{"in":"query","name":"offset","schema":{"type":"integer","title":"Offset","default":0},"required":false}],"description":"Return this org's PHI-read disclosure log, newest-first.\n\nAdmin-scoped, org-scoped. Optional ``?action=&target_id=&from=&to=`` filters narrow\nthe window; ``limit``/``offset`` paginate (clamped). The org filter comes from the\nidentity alone, so another tenant's rows are unreachable.","operationId":"listPhiAccess"}},"/exceptions":{"get":{"tags":["remittances"],"summary":"Org-scoped queue of held 835s + documents needing review","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","title":"Limit","default":50},"required":false},{"in":"query","name":"offset","schema":{"type":"integer","title":"Offset","default":0},"required":false}],"description":"The org-scoped exception queue: everything in this org needing a human.\n\nTwo sources, both ``WHERE org_id = :caller_org``:\n\n* ``Remittance`` rows with ``review_state=\"held\"`` (a gated-but-unreleased 835), and\n* ``DocumentResult`` rows with ``needs_review=True`` (a document the pipeline flagged).\n\nRead-only; any authenticated org member may read their own org's queue. Paginated\nsimply (``limit``/``offset``, clamped to a sane window). The two lists are returned\nside by side so the console can render the held 835s and the flagged docs together.\n\nThe served held-remittance bodies carry the full ``x12_835`` (PHI), so a successful\nread is a disclosure: one PHI-read audit row is written per served remittance,\n**before** the body is serialised - fail-closed, like the per-file list (a failed\naudit write 500s rather than emit unaudited PHI). Bounded by the page limit.","operationId":"listExceptions"}},"/calibration":{"get":{"tags":["calibration"],"summary":"Shadow-calibration report: gate verdict vs human disposition","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"}},"description":"The shadow-calibration report: this org's gate would-have verdicts joined\nagainst the human release/reject decisions, so an operator can judge readiness to flip\nauto-release on. Admin-scoped, org-scoped. The same aggregation backs the console card;\nthis endpoint adds the admin gate and the ``org_id`` envelope.\n\nPre-telemetry rows (all five telemetry columns NULL, emitted by an earlier release) are\ncounted in ``cohort.pre_telemetry`` but excluded from every agreement figure - a NULL\nverdict is never fabricated into an agree/disagree.","operationId":"calibration"}},"/finops/spend":{"get":{"tags":["finops"],"summary":"Aggregate this org's spend by operation / model / day","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"invalid_date","type":"BadRequest","message":"the request was malformed"}}}},"description":"A from/to query param is not an ISO date (YYYY-MM-DD)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"from","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"},"required":false},{"in":"query","name":"to","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"required":false}],"description":"Aggregate this org's ``usage_events`` over an optional ``?from=&to=`` date range.\nThe same aggregation backs the FinOps panel; this endpoint adds the admin gate, date\nparsing, and the ``org_id``/``from``/``to`` envelope.\n\nProvider cost only - **not** margin. The credit side (what the org was charged) lives in\nthe control plane, not in this service's database, so a margin view is not available\nhere.","operationId":"spend"}},"/finops/budget":{"get":{"tags":["finops"],"summary":"Get this org's spend budget","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"}},"description":"Return this org's budget. Admin-scoped, org-scoped.\nWhen the org has never set a budget, returns ``budget_usd: null`` + ``set: false``\n(a 200, not a 404 - \"no budget yet\" is a normal state, not a missing resource).","operationId":"getBudget"},"put":{"tags":["finops"],"summary":"Set (upsert) this org's spend budget","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unprocessable_entity","type":"UnprocessableEntity","message":"the request body failed validation"}}}},"description":"budget_usd is missing or negative."}},"description":"Set (upsert) this org's budget. Admin-scoped, org-scoped - the org\ncomes from the identity, **never** the body, so an admin can only set *their own*\norg's budget. The upsert is idempotent and returns the stored budget.","operationId":"putBudget","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetBody"}}},"required":true}}},"/finops/status":{"get":{"tags":["finops"],"summary":"Current-period budget burn (the 50/80/100% bands)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"}},"description":"The current-period budget burn - the data behind the 50/80/100%\nalerts. Admin-scoped, org-scoped. The same computation backs the FinOps panel;\nthis endpoint adds the admin gate and serialises the result.\n\n* ``ok``   - < 50%\n* ``warn`` - ≥ 50%\n* ``high`` - ≥ 80%\n* ``over`` - ≥ 100%\n\nWhen no budget is set, ``band`` is ``no_budget`` and ``percent`` is ``null`` - there is\nnothing to burn against, so we don't fabricate a percentage or alert.","operationId":"budgetStatus"}},"/files/{file_id}":{"get":{"tags":["files"],"summary":"Get a file's status (in flight) or full result (done)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"304":{"description":"Result unchanged since the ETag in If-None-Match (no body)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"file_not_found","type":"NotFound","message":"not found"}}}},"description":"Unknown file, or one belonging to another org (no existence oracle)."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"file_id","schema":{"type":"string","title":"File Id"},"required":true}],"description":"Return a file's status (while in flight) or its full result (when done).\n\nOrg-scoped: a cross-org / unknown / malformed id all return the same bare 404.\nA done/partial file's body is the persisted ``ArseFileResult`` with an ``ETag``\n(honoring ``If-None-Match`` → 304); an in-flight file's body carries a\n``progress`` block + ``Retry-After``; an error/partial file carries the typed\nerror envelope.","operationId":"getFile"}},"/worklist/summary":{"get":{"tags":["worklist"],"summary":"Worklist counts per aging band, intent, and work-item status","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"invalid_as_of","type":"BadRequest","message":"the request was malformed"}}}},"description":"as_of is not an ISO date (YYYY-MM-DD)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"as_of","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of"},"required":false}],"description":"Worklist counts for the org - totals per ``aging_band``, per ``intent``, and per work-item\n``status``. Org-scoped; same actionable set + clock + ONE query as the worklist\nlisting. Counts every actionable item regardless of status, so resolved/snoozed\nitems are accounted (never invisible). Lets the console badge \"N overdue / M in progress\"\nwithout pulling the full list.","operationId":"worklistSummary"}},"/onboarding/readiness":{"get":{"tags":["onboarding"],"summary":"Run the provisioning readiness checks for the caller's org","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"}},"description":"Run the provisioning readiness checks for the caller's org.\n\nReturns ``{ready, checks}`` where ``checks`` is the per-check report (each carrying\n``name``/``status``/``detail``/``remediation``) and ``ready`` is ``false`` iff any\ncheck is ``fail``. Warnings are surfaced but do not block readiness.\n\nAuthenticated + org-scoped: the org comes from the identity, so the webhook-encryption\nand model-routing checks read only this org's rows.","operationId":"readiness"}},"/webhooks/{webhook_id}":{"delete":{"tags":["webhooks"],"summary":"Delete one of the caller org's webhook endpoints","responses":{"204":{"description":"Deleted (no body)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"not_found","type":"NotFound","message":"not found"}}}},"description":"Unknown endpoint, or one belonging to another org (no existence oracle)."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"webhook_id","schema":{"type":"string","title":"Webhook Id"},"required":true}],"description":"Delete one of the caller org's webhook endpoints.\n\nResolved ``WHERE id = :id AND org_id = :caller_org``: a cross-org or unknown (or\nmalformed) id is a bare **404** - the resolve fails before anything is deleted, so a\ncross-org caller never learns the id exists. On success, returns **204**.","operationId":"deleteWebhook"}},"/finops/held-but-billed":{"get":{"tags":["finops"],"summary":"Built (metered) 835s vs released","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"admin required"}},"description":"The held-but-billed gap: 835s we **built** (and therefore metered under\n``arse.emit_835``) versus the ones that actually **released**. Admin-scoped, org-scoped.\nThe same computation backs the FinOps panel.\n\nProvider cost only - **not** margin. We surface the *count* gap (and the provider_cost we\nincurred on the built 835s); turning that into a dollar margin figure needs the\ncontrol-plane credit charged per 835, which is not in this service's database.","operationId":"heldButBilled"}},"/files/{file_id}/remittances":{"get":{"tags":["remittances"],"summary":"List a file's emitted 835s + review state","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"not_found","type":"NotFound","message":"not found"}}}},"description":"not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"file_id","schema":{"type":"string","title":"File Id"},"required":true}],"description":"List a file's emitted 835s + review state. Org-scoped: a cross-org / unknown /\nmalformed file id all return the same bare 404. Any authenticated org member may\nread their own org's remittances (read is not admin-gated).\n\nThe served ``x12_835`` strings are PHI, so a successful list is a disclosure and is\nwritten to the PHI-read audit log (``remittance_read``) **before** the body is\nserialised - fail-closed, like the result read. The 404 paths disclose nothing and\nare deliberately not logged.","operationId":"listRemittances"}},"/files/{file_id}/remittances/{remittance_id}/reject":{"post":{"tags":["remittances"],"summary":"Reject a held 835 (admin AND org owner)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"Same-org caller who is not an admin (reject requires admin AND org ownership)."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"not_found","type":"NotFound","message":"not found"}}}},"description":"not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"already_released","type":"Conflict","message":"the request conflicts with current state"}}}},"description":"The remittance is already released (money moved) and cannot be rejected."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unprocessable_entity","type":"UnprocessableEntity","message":"the request body failed validation"}}}},"description":"The request body is missing a non-blank reason."}},"parameters":[{"in":"path","name":"file_id","schema":{"type":"string","title":"File Id"},"required":true},{"in":"path","name":"remittance_id","schema":{"type":"string","title":"Remittance Id"},"required":true}],"description":"Reject a held remittance (admin **and** org owner). Same authz as\nrelease. Resolve scoped by org (404 on mismatch). The rejection then runs the admin\ngate and records the ``reason`` (in the ``balance_deltas`` JSONB under\n``reject_reason``) + stamps ``released_by``/``released_at``. Idempotent: re-rejecting\nan already-``rejected`` remit is a 200 no-op that does not overwrite the original\nreason or actor. Rejecting an already-``released`` remit is a **409**\n(``already_released``) - money moved; correcting a released remit is the\nedit-and-rebuild loop, never a silent overwrite. The non-blank-reason invariant is\nenforced by ``RejectBody`` (a blank reason is a 422 before this handler runs).","operationId":"rejectRemittance","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectBody"}}},"required":true}}},"/files/{file_id}/remittances/{remittance_id}/rebuild":{"post":{"tags":["remittances"],"summary":"Edit-and-rebuild a remittance from a corrected EOB (admin AND org owner)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"Same-org caller who is not an admin (rebuild requires admin AND org ownership)."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"not_found","type":"NotFound","message":"not found"}}}},"description":"not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"invalid_corrected_eob","type":"UnprocessableEntity","message":"the request body failed validation"}}}},"description":"The corrected EOBExtraction body failed validation."}},"parameters":[{"in":"path","name":"file_id","schema":{"type":"string","title":"File Id"},"required":true},{"in":"path","name":"remittance_id","schema":{"type":"string","title":"Remittance Id"},"required":true}],"description":"Rebuild a remittance from an operator-corrected EOB (the correction loop).\n\nSame authz as release/reject (admin AND org owner). The body is a corrected\n``EOBExtraction`` JSON - the operator's fixes to mis-extracted amounts/codes.\nWe re-run the **exact** same build → balance → pyx12 → gate pipeline\nused for the original production, over the corrected EOB, and UPDATE the\nrow's ``x12_835``/``balanced``/``balance_deltas``/``fabricated_codes``/``review_state``.\n\nNEVER fabricates: the operator supplies the corrected values; we only rebuild +\nre-gate. A rebuild NEVER auto-releases - regardless of the org's auto-release setting,\na clean re-gated remit lands ``held`` with ``would_auto_release=True`` in its telemetry,\nand a human clicks Release (maker-checker: the corrector must not implicitly become the\nreleaser, and a rejected remit must never flip to released-equivalent in one call).\nRebuilding over a prior human release/reject increments ``dispositions_superseded`` so\nthe calibration report can surface the erased observation. ``degraded`` carries the\nfile's original degraded flag (a degraded production never auto-releases). A malformed\ncorrected EOB body → **422** (this is *our* trusted operator input, so a validation\noracle here is fine - it isn't another tenant's id).","operationId":"rebuildRemittance","requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Corrected","additionalProperties":true}}},"required":true}}},"/files/{file_id}/remittances/{remittance_id}/release":{"post":{"tags":["remittances"],"summary":"Release a held 835 (admin AND org owner)","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"unauthorized","type":"Unauthorized","message":"missing or invalid gateway secret"}}}},"description":"missing or invalid gateway secret"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"admin_required","type":"Forbidden","message":"admin required"}}}},"description":"Same-org caller who is not an admin (release requires admin AND org ownership)."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"not_found","type":"NotFound","message":"not found"}}}},"description":"not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"},"example":{"error":{"code":"already_rejected","type":"Conflict","message":"the request conflicts with current state"}}}},"description":"The remittance is already rejected and cannot be flipped to released."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"file_id","schema":{"type":"string","title":"File Id"},"required":true},{"in":"path","name":"remittance_id","schema":{"type":"string","title":"Remittance Id"},"required":true}],"description":"Release a held remittance (admin **and** org owner).\n\nResolve scoped by org (404 on any mismatch - a cross-org admin never learns the\nid exists). The release then runs the admin gate\n(403 for a same-org non-admin) and the held → released transition. **Idempotent**: a\nremittance already ``released`` is a 200 no-op (the actor/time are not re-stamped). A\n``rejected`` remittance cannot be flipped to released - that is a 409 conflict.","operationId":"releaseRemittance"}}},"openapi":"3.1.0","servers":[{"url":"https://api.hank.ai/v1/arse","description":"ARSE API base at the Hank edge."},{"url":"/","description":"Current docs origin direct backend root (local/dev Try it out)."}],"security":[{"bearerAuth":[]}],"components":{"schemas":{"ApiError":{"type":"object","title":"ApiError","required":["type","code","message"],"properties":{"code":{"type":"string","title":"Code","description":"Stable machine-readable slug, e.g. file_not_found"},"type":{"type":"string","title":"Type","description":"PascalCase error class, e.g. NotFound"},"message":{"type":"string","title":"Message","description":"Short human-readable explanation"}},"description":"The inner error object: a typed class, a stable machine code, a human message.\n\n* ``type`` - PascalCase error class (``Unauthorized``/``NotFound``/``Forbidden``/…),\n  coarse enough to switch on.\n* ``code`` - a stable snake_case slug a client can branch on without string-matching\n  the message (``file_not_found``, ``admin_required``, ``idempotency_conflict``, …).\n* ``message`` - a short human-readable explanation; not for programmatic use."},"BudgetBody":{"type":"object","title":"BudgetBody","required":["budget_usd"],"properties":{"budget_usd":{"anyOf":[{"type":"number","minimum":0},{"type":"string"}],"title":"Budget Usd"}},"description":"The PUT budget body. ``budget_usd`` is parsed straight to ``Decimal`` (exact,\nnever float) and must be non-negative - a negative ceiling is nonsense."},"RejectBody":{"type":"object","title":"RejectBody","required":["reason"],"properties":{"reason":{"type":"string","title":"Reason","minLength":1}},"description":"The reject request body. ``reason`` is required and non-blank - a rejection\nmust explain itself for the audit trail."},"ProcessAccepted":{"type":"object","title":"ProcessAccepted","required":["file_id","status"],"properties":{"status":{"type":"string","title":"Status","description":"Always \"queued\" on accept (or the replayed file's status)"},"file_id":{"type":"string","title":"File Id","description":"The durable id the caller polls / opens a progress WS on"}},"description":"The 202 intake acknowledgement: the durable ``file_id`` and its initial status.\n\nThis is the *only* 2xx the intake endpoint returns - a true replay of a prior\n``Idempotency-Key`` hands back the ORIGINAL ``file_id`` with the same shape (so a\ncaller can't tell a replay from a fresh accept, by design). Content problems never\nreach here; they are resolved downstream and surfaced via the file's poll endpoint."},"ValidationError":{"type":"object","title":"ValidationError","required":["loc","msg","type"],"properties":{"ctx":{"type":"object","title":"Context"},"loc":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"}}},"ApiErrorResponse":{"type":"object","title":"ApiErrorResponse","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ApiError"}},"description":"The error envelope returned on every non-2xx: ``{\"error\": {type, code, message}}``.\n\nThis is the single error body shape across the ARSE API. It is registered as an\nOpenAPI component so a generated client gets one typed error model for the whole\nsurface."},"HTTPValidationError":{"type":"object","title":"HTTPValidationError","properties":{"detail":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail"}}},"RegisterWebhookBody":{"type":"object","title":"RegisterWebhookBody","required":["url","secret","events"],"properties":{"url":{"type":"string","title":"Url","minLength":1},"events":{"type":"array","items":{"type":"string"},"title":"Events","minItems":1},"secret":{"type":"string","title":"Secret","minLength":1}},"description":"Webhook registration body. ``url`` and ``secret`` required; ``events`` a non-empty\nsubset of the valid events. The URL/event validity is checked in the handler (so a bad\nURL is a 4xx with a clear code, and an SSRF rejection is reported as such)."}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A console-minted API key (mint/manage at console.hank.ai). Calls route through the Hank edge (api.hank.ai/v1/arse) and are metered per the catalog. Send it as `Authorization: Bearer <key>`."}}}}