{"info":{"title":"HANK MEDICALNOTES Autonomous Self Service","version":"0.0.0","description":"The /v1 customer API for the HANK MEDICALNOTES service (summarize / generate / classify medical notes). All examples are synthetic; no PHI.\n\n**Authentication.** This API is token-based. Get an access token at [console.hank.ai](https://console.hank.ai) and send it as `Authorization: Bearer <token>` on every request. On this page, click **Authorize** (top-right), paste just the token, and *Try it out* will send it for you.\n\n**Base URL.** Production requests go through the metered Hank edge at `https://api.hank.ai/v1/medicalnotes`. Calling the backend host directly bypasses metering and the edge auth path."},"paths":{"/v1/stats":{"get":{"tags":["stats"],"summary":"Get Stats","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Get Stats V1 Stats Get","additionalProperties":true}}},"description":"Successful Response"}},"description":"Happiness score (+ sub-scores + trend + $/hours saved) and ops aggregates.","operationId":"get_stats_v1_stats_get"}},"/v1/webhooks":{"get":{"tags":["webhooks"],"summary":"List the caller scope's registered webhook endpoints (secrets never included)","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Listwebhooks","additionalProperties":true}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"customer_id","schema":{"type":"string","title":"Customer Id","minLength":1},"required":true},{"in":"query","name":"org_id","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id"},"required":false}],"description":"List the caller scope's registered webhook endpoints. Secrets are NEVER included.\n\nM7: the org scope is the chain-derived control-plane org (server-derived); the ``org_id``\nquery param is honored only as the local/dev fall-back when no identity resolved. So a caller\ncannot list another tenant's endpoints by passing ``?org_id=org_VICTIM``.","operationId":"listWebhooks"},"post":{"tags":["webhooks"],"summary":"Register a per-org webhook endpoint","security":[{"HTTPBearer":[]}],"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"description":"Register a per-scope webhook endpoint.\n\nValidates the events are a non-empty subset of the terminal statuses, runs the URL\nthrough the **SSRF guard at registration time** (an internal/non-https URL is a 400 with\nerror_code ``unsafe_url``, not a stored endpoint discovered later), rejects a duplicate\n``(scope, url)`` or a registration past the per-scope cap (both **409**), encrypts the\nsigning secret, and inserts the row scoped to the caller's scope. Returns **201** with the\nendpoint (secret omitted). A missing/invalid ``WEBHOOK_ENCRYPTION_KEY`` is a **500**.","operationId":"registerWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookBody"}}},"required":true}}},"/v1/medicalnotes":{"get":{"tags":["medicalnotes"],"summary":"Look up notes jobs by your reference id","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeListResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"external_ticket_id","schema":{"type":"string","title":"External Ticket Id","minLength":1},"required":true}],"description":"Look up jobs by your own reference id (newest first).\n\nM7: results are filtered to the caller's control-plane org -- another org's job sharing\nthe same external_ticket_id never appears (an empty list, not a leak). A caller with no\ncontrol-plane org (local/test key) sees the unscoped result, as before.","operationId":"lookupMedicalNotesByTicket"},"post":{"tags":["medicalnotes"],"summary":"Submit a notes job","security":[{"HTTPBearer":[]}],"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MedicalNotesSubmitResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"description":"Submit a notes job. 202 + ``cod_`` ULID, or 413/422/429 on policy/backpressure.\n\nA retry carrying the SAME ``Idempotency-Key`` header + the SAME payload replays the ORIGINAL\njob (202, no new job, no second worker bill); the same key with a DIFFERENT payload is 409.\nThe header is threaded into the SHARED admission path, so REST + MCP dedupe identically.\n``is_test`` is the gateway-trusted ``X-Hank-Test-Mode`` flag (never a client claim); the\nshared admission path snapshots it onto the job so the worker meters it as test usage.","operationId":"submitMedicalNotes","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MedicalNotesSubmitRequest"}}},"required":true}}},"/v1/medicalnotes/{job_id}":{"get":{"tags":["medicalnotes"],"summary":"Get a notes job's status, progress, and (when completed) the result","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeStatusResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"job_id","schema":{"type":"string","title":"Job Id"},"required":true}],"description":"Status + progress + result for one job, with polling/caching semantics.\n\n* **running** -> 200 with status + ``progress`` and a ``Retry-After`` header so\n  pollers back off.\n* **completed** -> 200 with status + ``usage`` and the notes ``result`` (the\n  ``claim_full`` PHI body when ``STORE_PHI``, else the jitter-free projection;\n  ``None`` when no run row exists), plus a strong ``ETag``. A matching\n  ``If-None-Match`` -> **304 Not Modified** (no body).\n* **failed/canceled/timed_out** -> 200 with status + ``error_code`` /\n  ``error_detail`` (no result).","operationId":"getMedicalNotes"}},"/v1/webhooks/{webhook_id}":{"delete":{"tags":["webhooks"],"summary":"Delete one of the caller scope's webhook endpoints","security":[{"HTTPBearer":[]}],"responses":{"204":{"description":"Successful Response"},"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},{"in":"query","name":"customer_id","schema":{"type":"string","title":"Customer Id","minLength":1},"required":true},{"in":"query","name":"org_id","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id"},"required":false}],"description":"Delete one of the caller scope's webhook endpoints.\n\nResolved ``WHERE id = :id AND <scope>``: a cross-scope or unknown (or malformed) id is a\nbare **404** - the resolve fails before anything is deleted, so a cross-scope caller never\nlearns the id exists. On success, returns **204**.\n\nM7: ``<scope>`` is the chain-derived control-plane org (server-derived); the ``org_id`` query\nparam is the local/dev fall-back only. A caller cannot delete another tenant's endpoint by\npassing ``?org_id=org_VICTIM`` - the org comes from the identity, so the row simply won't\nmatch and the delete 404s (no existence oracle).","operationId":"deleteWebhook"}},"/v1/medicalnotes/{job_id}/cancel":{"post":{"tags":["medicalnotes"],"summary":"Request cancellation of a notes job","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"job_id","schema":{"type":"string","title":"Job Id"},"required":true}],"description":"Best-effort cancel. Queued -> ``canceled`` now; running -> flag for the worker.","operationId":"cancelMedicalNotes"}},"/v1/medicalnotes/{job_id}/events":{"get":{"tags":["medicalnotes"],"summary":"Get a notes job's progress/phase event log","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"job_id","schema":{"type":"string","title":"Job Id"},"required":true}],"description":"Progress/phase event log for one job (chronological).","operationId":"getMedicalNotesEvents"}},"/v1/medicalnotes/{job_id}/webhook/resend":{"post":{"tags":["medicalnotes"],"summary":"Re-fire the terminal webhook for a notes job","security":[{"HTTPBearer":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResendResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"job_id","schema":{"type":"string","title":"Job Id"},"required":true}],"description":"Re-enqueue the terminal webhook for ``job_id`` (service-key gated).\n\nDelivery is queued, not inline: this appends fresh **pending** delivery rows (one per\nactive registered endpoint for the job's scope + the job's inline callback override) and\nthe worker drainer sends them with the same SSRF-safe, signed, retried path. Reports how\nmany deliveries were enqueued -- delivery success/failure lands on the delivery rows,\nsurfaced via the admin/observability layer, not synchronously here.","operationId":"resendMedicalNotesWebhook"}}},"openapi":"3.1.0","servers":[{"url":"https://api.hank.ai/v1/medicalnotes"}],"components":{"schemas":{"Usage":{"type":"object","title":"Usage","properties":{"duration_s":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration S"},"unit_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unit Count"}},"description":"CUSTOMER-facing usage on a terminal job.\n\nCUSTOMER-BLINDNESS (M8): this carries the customer's billing basis -- the\n``unit_count`` the console PRICES into credits (multi-factor: PDF-vs-text + pages +\nspecialty) -- plus the wall-clock ``duration_s``. It NEVER carries the operator-only\nLLM provider $ (``provider_cost_usd``) or the per-model token breakdown: that is our\ncost-of-goods, surfaced only on the operator ``/cost`` console + the admin dashboard,\nnever as a customer charge on a ``/v1`` route. The customer's actual credit charge is\ncomputed console-side from ``unit_count`` (this app never prices it)."},"Progress":{"type":"object","title":"Progress","properties":{"phase":{"anyOf":[{"enum":["extracting","summarizing","generating","classifying","done"],"type":"string"},{"type":"null"}],"title":"Phase"},"elapsed_s":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed S"},"phase_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase Note"},"last_activity_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Activity At"}},"description":"Live progress while ``running``: the current coding phase + elapsed time."},"EventItem":{"type":"object","title":"EventItem","required":["type"],"properties":{"ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ts"},"type":{"type":"string","title":"Type"},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase"},"detail":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"title":"Detail"}},"description":"One ``coding_events`` row in the events log."},"CodeListItem":{"type":"object","title":"CodeListItem","required":["job_id","status"],"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"enum":["queued","running","completed","failed","timed_out","canceled"],"type":"string","title":"Status"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id"},"external_ticket_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ticket Id"}},"description":"One row in the ``external_ticket_id`` lookup result."},"MedicalRecord":{"type":"object","title":"MedicalRecord","required":["format","content"],"properties":{"format":{"enum":["text","pdf","json","fhir","hl7","cda"],"type":"string","title":"Format"},"content":{"type":"string","title":"Content"}},"description":"The medical record payload. ``content`` is opaque at the API boundary.\n\nFor ``pdf`` the content is base64; for ``text`` it is the raw note; for\n``json`` it is a JSON string of the notes-engine-native NoteEntities shape. The worker\ninterprets it -- the API only sizes and hashes it.","additionalProperties":false},"CancelResponse":{"type":"object","title":"CancelResponse","required":["job_id","status","cancel_requested"],"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"enum":["queued","running","completed","failed","timed_out","canceled"],"type":"string","title":"Status"},"cancel_requested":{"type":"boolean","title":"Cancel Requested"}},"description":"POST ``/v1/medicalnotes/{id}/cancel`` -- best-effort cancel acknowledgement."},"EventsResponse":{"type":"object","title":"EventsResponse","required":["job_id"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventItem"},"title":"Events"},"job_id":{"type":"string","title":"Job Id"}},"description":"GET ``/v1/medicalnotes/{id}/events``."},"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"}}},"CodeListResponse":{"type":"object","title":"CodeListResponse","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/CodeListItem"},"title":"Jobs"}},"description":"GET ``/v1/medicalnotes?external_ticket_id=`` lookup result."},"CodeStatusResponse":{"type":"object","title":"CodeStatusResponse","required":["job_id","status"],"properties":{"phase":{"anyOf":[{"enum":["extracting","summarizing","generating","classifying","done"],"type":"string"},{"type":"null"}],"title":"Phase"},"usage":{"anyOf":[{"$ref":"#/components/schemas/Usage"},{"type":"null"}]},"job_id":{"type":"string","title":"Job Id"},"result":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"array","items":{}},{"type":"string"},{"type":"null"}],"title":"Result"},"status":{"enum":["queued","running","completed","failed","timed_out","canceled"],"type":"string","title":"Status"},"job_type":{"anyOf":[{"enum":["summarize","generate","classify"],"type":"string"},{"type":"null"}],"title":"Job Type"},"progress":{"anyOf":[{"$ref":"#/components/schemas/Progress"},{"type":"null"}]},"note_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note Type"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Detail"},"output_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Sha256"},"external_ticket_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ticket Id"}},"description":"GET ``/v1/medicalnotes/{id}`` -- status, progress (while running), usage + result (terminal).\n\nA single shape covers the whole lifecycle: ``progress`` is present while\n``running``; ``usage`` populates on a terminal job; ``result`` carries the notes\nOUTPUT once ``completed`` (the deliverable summary/note text for summarize/generate,\nor the ``{note_type, dos}`` dict for classify -- the PHI body when ``STORE_PHI`` is\non, else the jitter-free projection; ``None`` when no run row exists);\n``error_code``/``error_detail`` populate on ``failed``/``timed_out``."},"HTTPValidationError":{"type":"object","title":"HTTPValidationError","properties":{"detail":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail"}}},"RegisterWebhookBody":{"type":"object","title":"RegisterWebhookBody","required":["customer_id","url","secret","events"],"properties":{"url":{"type":"string","title":"Url","minLength":1},"events":{"type":"array","items":{"type":"string"},"title":"Events","minItems":1},"org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id"},"secret":{"type":"string","title":"Secret","minLength":1},"customer_id":{"type":"string","title":"Customer Id","minLength":1}},"description":"Webhook registration body.\n\n``customer_id`` is the interim tenancy scope (required until the M7 edge identity lands);\n``org_id`` is the go-forward scope (optional now, authoritative when present). ``url`` +\n``secret`` required; ``events`` a non-empty subset of the valid terminal statuses."},"WebhookResendResponse":{"type":"object","title":"WebhookResendResponse","required":["job_id","status","enqueued"],"properties":{"job_id":{"type":"string","title":"Job Id","description":"The job whose terminal webhook was re-enqueued."},"status":{"type":"string","title":"Status","description":"The job's current lifecycle status (always terminal here)."},"enqueued":{"type":"integer","title":"Enqueued","description":"How many webhook deliveries were enqueued (one per active registered endpoint for the job's scope + the job's inline callback override). The worker drainer sends them asynchronously with the SSRF-safe, signed, retried delivery path."}},"description":"Result of re-enqueuing the terminal webhook for a job (POST .../webhook/resend)."},"MedicalNotesSubmitRequest":{"type":"object","title":"MedicalNotesSubmitRequest","required":["facility_id","customer_id","medical_record"],"properties":{"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id"},"job_type":{"enum":["summarize","generate","classify"],"type":"string","title":"Job Type","default":"summarize"},"note_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note Type"},"specialty":{"enum":["ANESTHESIA","RADIOLOGY"],"type":"string","title":"Specialty","default":"ANESTHESIA"},"customer_id":{"type":"string","title":"Customer Id","examples":["acme"],"description":"The tenant this job belongs to. Required. Scoping is per (customer_id, facility_id). Auto-created on first use."},"facility_id":{"type":"string","title":"Facility Id"},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url"},"max_cost_usd":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Max Cost Usd"},"medical_record":{"$ref":"#/components/schemas/MedicalRecord"},"callback_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Secret"},"current_settings":{"type":"object","title":"Current Settings","additionalProperties":true},"customer_guidance":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"title":"Customer Guidance"},"external_ticket_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ticket Id"}},"description":"POST ``/v1/medicalnotes`` body -- enqueue a notes job.\n\n``model``/``max_cost_usd`` are optional; the route fills defaults from\nsettings. ``current_settings`` / ``customer_guidance`` are opaque notes-config\nblobs the worker applies.","additionalProperties":false},"MedicalNotesSubmitResponse":{"type":"object","title":"MedicalNotesSubmitResponse","required":["job_id","status","created_at"],"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"enum":["queued","running","completed","failed","timed_out","canceled"],"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At","format":"date-time"}},"description":"202 response to a submit."},"Body_api_keys_create_admin_api_keys_create_post":{"type":"object","title":"Body_api_keys_create_admin_api_keys_create_post","properties":{"label":{"type":"string","title":"Label","default":""},"capabilities":{"type":"array","items":{"type":"string"},"title":"Capabilities","default":[]}}},"Body_playground_submit_admin_playground_submit_post":{"type":"object","title":"Body_playground_submit_admin_playground_submit_post","required":["facility_id"],"properties":{"model":{"type":"string","title":"Model","default":""},"pdf_b64":{"type":"string","title":"Pdf B64","default":""},"sample_id":{"type":"string","title":"Sample Id","default":""},"specialty":{"type":"string","title":"Specialty","default":""},"facility_id":{"type":"string","title":"Facility Id"},"record_format":{"type":"string","title":"Record Format","default":"text"},"record_content":{"type":"string","title":"Record Content","default":""},"current_settings":{"type":"string","title":"Current Settings","default":""}}}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}