{"info":{"title":"HANK_CLAIMCLEANER Playground","version":"2026.8.12.dev0","description":"Medical claim validation and cleaning API and UI. Validates CPT/ICD codes and modifiers and applies NCCI/NLCD/PFS and other CMS edits.\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/claimcleaner`. Calling the backend host directly bypasses metering and the edge auth path."},"paths":{"/ready":{"get":{"tags":["system"],"summary":"Ready","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"description":"Readiness probe: 200 once app data is loaded; 503 while still loading.\n\nReplicates the legacy Flask /ready response shapes exactly:\n- status=\"ready\":   {status, version, load_seconds, uptime_seconds}\n- status=\"loading\": {status, version, loading_seconds}","operationId":"ready_ready_get"}},"/health":{"get":{"tags":["system"],"summary":"Health","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"description":"Pure liveness - always 200. Mirrors the Flask /health body exactly.","operationId":"health_health_get"}},"/version":{"get":{"tags":["system"],"summary":"Version","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"description":"Enriched build/deploy identity - mirrors the Flask /version dict.","operationId":"version_version_get"}},"/api/clean":{"post":{"tags":["clean"],"summary":"Validate and clean medical claims","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CleanResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"description":"Native typed twin of the legacy `/clean/<version>/<token>` endpoint. Validates CPT/ICD codes and modifiers and applies the selected CMS edits (NCCI/NLCD/PFS/frequency/crosswalk). Accepts either a single `claim` or a list of `claims`. Requires a Bearer access token. The response envelope (`ERRORS`/`METADATA`/`RESULTS`) is byte-identical to the legacy endpoint for the same input.","operationId":"clean_api_clean_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CleanRequest"}}},"required":true}}},"/api/auth/login":{"post":{"tags":["auth"],"summary":"Login","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Login Api Auth Login Post","additionalProperties":true}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"description":"Validate via the control plane (/auth/check) with the local-DB cutover\nfallback - the same path the middleware uses on every subsequent request.","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true}}},"/api/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Logout Api Auth Logout Post","additionalProperties":true}}},"description":"Successful Response"}},"description":"Clear the local hcc_token cookie + tell the browser where to go next.\n\nOn hank.ai-family hosts, send the browser to console.hank.ai/auth/signout so\nthe parent-domain SSO cookie also gets cleared. Deletion uses the SAME\nattributes the cookie was set with (Safari ignores mismatched deletions).","operationId":"logout_api_auth_logout_post"}},"/api/auth/whoami":{"get":{"tags":["auth"],"summary":"Whoami","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Whoami Api Auth Whoami Get","additionalProperties":true}}},"description":"Successful Response"}},"description":"Identity for the current request. The SPA calls this on load to drive the\nthree-state chrome (anonymous landing vs signed-in app). Soft-auth:\nAuthMiddleware keeps /api/auth/whoami in _SOFT_AUTH, so an anonymous caller\nreaches here with request.state.identity = None and gets a logged-out answer\n({\"authenticated\": false}) instead of a 401 - that's what lets the public\nshell render the sign-in landing.\n\nPayload is built by the shared ``build_auth_state`` (api/auth/middleware) --\nthe SAME builder the server-side shell bake (``api/app.py`` ``_index`` ->\n``window.__HANK_AUTH__``) uses, so a whoami fetch and the baked global can\nnever drift. `email` is the canonical user-visible identifier (onboarding\nplaybook #17); it falls back to `display_label` only for identities with no\nemail (env-token bootstrap admin, legacy tokens).","operationId":"whoami_api_auth_whoami_get"}},"/api/codes/search":{"get":{"tags":["codes"],"summary":"Search","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"q","schema":{"type":"string","title":"Q","default":"","description":"Free-text / code search"},"required":false,"description":"Free-text / code search"},{"in":"query","name":"types","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Types","description":"Comma-separated CPT,ICD,MOD,HCC filter"},"required":false,"description":"Comma-separated CPT,ICD,MOD,HCC filter"},{"in":"query","name":"limit","schema":{"type":"integer","title":"Limit","default":20,"description":"Max results (1-50)"},"required":false,"description":"Max results (1-50)"}],"description":"As-you-type lexical search over ``codesets.search_index``.\n\nReturns ``{\"results\": [{\"code\", \"code_type\", \"description\", \"snippet\"}]}``.\n``snippet`` is null on a pure code-fragment query.","operationId":"search_api_codes_search_get"}},"/api/codes/asa-list":{"get":{"tags":["codes"],"summary":"Asa List","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"description":"The ASA base-unit list: ``{\"codes\": [{\"code\", \"description\", \"base_units\"}]}``.","operationId":"asa_list_api_codes_asa_list_get"}},"/api/codes/modifiers":{"get":{"tags":["codes"],"summary":"Modifiers","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"description":"The modifier reference list: ``{\"modifiers\": [{\"code\", \"description\"}]}``.","operationId":"modifiers_api_codes_modifiers_get"}},"/api/codes/cpt/{code}":{"get":{"tags":["codes"],"summary":"Cpt Meta","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"path","name":"code","schema":{"type":"string","title":"Code"},"required":true}],"description":"Latest-year CPT metadata: description, work RVU, and ASA crosswalk detail.\n\n404 ``unknown CPT code`` when the code has no ``codesets.cpt`` row.","operationId":"cpt_meta_api_codes_cpt__code__get"}},"/api/admin/data-versions":{"get":{"tags":["admin"],"summary":"Per-group data freshness (operator only)","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","title":"Response Data Versions Api Admin Data Versions Get","additionalProperties":true}}},"description":"Successful Response"}},"description":"Operator-only. Returns the per-table rows of `codesets.codeset_meta` (`schema_name`=table, `version`, `updated_at`) - the breakdown the footer's single `MAX(updated_at)` freshness pill hides. Requires an operator (`is_admin`) session; non-operators get 403, anonymous callers 401. In memory mode or on any DB error it fails soft with `{\"data_versions\": [], \"available\": false}`.","operationId":"data_versions_api_admin_data_versions_get"}},"/api/codes/icd-suggestions":{"get":{"tags":["codes"],"summary":"Icd Suggestions","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"parameters":[{"in":"query","name":"cpt","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cpt","description":"Surgical CPT to score against"},"required":false,"description":"Surgical CPT to score against"},{"in":"query","name":"asa","schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asa","description":"Anesthesia code to score against"},"required":false,"description":"Anesthesia code to score against"},{"in":"query","name":"limit","schema":{"type":"integer","title":"Limit","default":10,"description":"Max suggestions (1-25)"},"required":false,"description":"Max suggestions (1-25)"}],"description":"Frequency-ranked ICD suggestions for a CPT/ASA line.\n\n400 when BOTH ``cpt`` and ``asa`` are missing/blank. Returns\n``{\"suggestions\": [{\"icd\", \"description\", \"likelihood\", \"match_type\"}]}``.","operationId":"icd_suggestions_api_codes_icd_suggestions_get"}}},"openapi":"3.1.0","servers":[{"url":"https://api.hank.ai/v1/claimcleaner","description":"Production - Hank edge (metered, token-validated)"},{"url":"/","description":"Local/dev (direct backend)"}],"components":{"schemas":{"Claim":{"type":"object","title":"Claim","properties":{"dos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dos","description":"Date of service, YYYY-MM-DD. Anchors every date-dependent edit: code vintages, coverage windows and fee-schedule years."},"pos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pos","description":"Place-of-service code. Drives the place-of-service vs modifier 26/TC advisory."},"payer":{"anyOf":[{"$ref":"#/components/schemas/PayerRef"},{}],"title":"Payer","description":"The claim's payer. Selects the payer rulebook and enables payer-aware findings."},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address","description":"Free-text service address. A ZIP code is extracted from it when zipcode is absent."},"claimid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimid","description":"Your claim identifier. Echoed in the response."},"zipcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zipcode","description":"Service-location ZIP code. Resolves the Medicare contractor (MAC) for the coverage checks. Prefer the facility object."},"anestype":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anestype","description":"Anesthesia type context, for example \"general\" or \"mac\"."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Fee-schedule category context for the PFS checks."},"emergent":{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"null"}],"title":"Emergent","description":"True when the anesthesia service was emergent. Gates the 99140 documentation advisory."},"facility":{"anyOf":[{"$ref":"#/components/schemas/FacilityRef"},{}],"title":"Facility","description":"Service-facility location. The preferred input for Medicare contractor (MAC) resolution."},"payor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payor Id","description":"Legacy payer identifier passthrough. Prefer the payer object."},"specialty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialty","description":"Specialty context, for example \"anesthesia\" or \"general\". Selects specialty-specific checks."},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"},"title":"Line Items","description":"The claim's service lines."},"patient_age":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"null"}],"title":"Patient Age","description":"Patient age in years. Turns on the demographic age edits."},"patient_sex":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Patient Sex","description":"Patient sex: M, F, male or female, case-insensitive. Turns on the demographic sex edits; any other value skips them."},"billing_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Billing Type","description":"Billing context: global, professional or technical. Drives the contrast and component-billing advisories."},"frequency_cutoff":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"null"}],"title":"Frequency Cutoff","description":"Threshold for the frequency (rare code-combination) checks."},"include_cured_claim":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Cured Claim","description":"Include the auto-corrected claim (CURED_CLAIM) in the response."}},"description":"A single claim.\n\n``line_items`` carries the service lines. Every other field is optional\ncontext; sending one turns on the checks that use it, and omitting it\nchanges nothing. Unknown fields pass through to the validation engine\nunchanged.","additionalProperties":true},"LineItem":{"type":"object","title":"LineItem","properties":{"cpt":{"type":"string","title":"Cpt","default":"","description":"Procedure code (CPT or HCPCS Level II) for this line."},"ndc":{"anyOf":[{"type":"string"},{}],"title":"Ndc","description":"National Drug Code for the drug billed on this line (5-4-2 or bare 11-digit). Any line that sends an NDC turns on the drug checks for the whole claim."},"icds":{"anyOf":[{"type":"array","items":{"type":"string"}},{}],"title":"Icds","description":"Diagnosis (ICD-10-CM) codes on this line."},"mods":{"anyOf":[{"type":"array","items":{"type":"string"}},{}],"title":"Mods","description":"Modifiers on this line, for example [\"AA\", \"59\"]."},"units":{"anyOf":[{"type":"integer"},{}],"title":"Units","description":"Units of service for this line. Default 1. Drives the NCCI MUE units validation."},"anescpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anescpt","description":"Anesthesia (ASA) procedure code, for anesthesia claims."},"ndc_qty":{"anyOf":[{"type":"number"},{"type":"string"},{}],"title":"Ndc Qty","description":"Drug quantity from the 837P CTP segment. A number greater than 0."},"ndc_uom":{"anyOf":[{"type":"string"},{}],"title":"Ndc Uom","description":"Drug unit of measure from the 837P CTP segment: UN, ML, GR, ME or F2."}},"description":"One service line of a claim.\n\nAll fields are optional. A field with a malformed type is reported through\nthe API's own error envelope, not a bare validation error.","additionalProperties":true},"PayerRef":{"type":"object","title":"PayerRef","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Your payer identifier. Carried for provenance; not used for resolution."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The payer name as written on the claim. When program is absent or not recognized, Claim Cleaner uses the name to resolve the program through the Hank payer directory."},"phone":{"anyOf":[{"type":"string"},{}],"title":"Phone","description":"The payer's claims-department phone number. Same role and same privacy handling as address."},"address":{"anyOf":[{"type":"object","additionalProperties":true},{}],"title":"Address","description":"The payer's claims-submission (lockbox) address, never the patient's or the facility's: street1 (a PO Box counts), street2, city, state (2-letter), zip. Used only to pick one payer when the name matches several. Request-scope: never logged, never retained, excluded from training exports."},"program":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Program","description":"Payer program: medicare, medicare_advantage, medicaid, commercial, workers_comp or self_pay. Common spellings (MCARE, MCAID, PRIVATE, PART C, WC, SELF PAY) are accepted, case-insensitively. An unrecognized value resolves to unknown; it is never a 422."}},"description":"The payer for this claim.\n\n``program`` selects the payer rulebook that Claim Cleaner applies. When\n``program`` is absent or not recognized, Claim Cleaner can resolve the\nprogram from ``name`` through the Hank payer directory. An unrecognized\n``program`` value is never an error: it resolves to ``unknown`` and the\nMedicare-derived rules run with their usual hedges.\n\n``address`` and ``phone`` are the payer's own claims-submission (lockbox)\ncontact details, never the patient's and never the facility's. They refine\nthe directory lookup when one name matches more than one payer. Both are\nrequest-scope inputs: Claim Cleaner does not log them, does not keep them,\nand removes them from every export.\n\nEvery field is optional. ``address`` and ``phone`` accept any shape; a\nvalue Claim Cleaner cannot read leaves the lookup on the payer name.","additionalProperties":true},"ErrorItem":{"type":"object","title":"ErrorItem","required":["type","code","message"],"properties":{"code":{"type":"string","title":"Code","description":"Machine code, e.g. 'JSON_SCHEMA_ERROR'"},"type":{"type":"string","title":"Type","description":"Error category, e.g. 'validation_error'"},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field","description":"Offending field path"},"cleaner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cleaner","description":"Cleaner that raised it"},"message":{"type":"string","title":"Message","description":"Human-readable description"}},"description":"One structured error in the ``ERRORS`` array.\n\nShape matches ``make_error_item`` (HANKCC_EXCEPTIONS.py): ``type``/``code``/\n``message`` always present; ``field``/``cleaner`` only when set."},"FacilityRef":{"type":"object","title":"FacilityRef","properties":{"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Two-letter state of the service facility."},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"County of the service facility. A real county name, never a city."},"zipcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zipcode","description":"ZIP code of the service facility."}},"description":"The location of the facility where the service was furnished.\n\nThis is the preferred input for Medicare contractor (MAC) resolution:\npayment locality follows where the provider furnishes the service. These\nfields outrank the legacy claim-level ``zipcode``/``county``/``state``,\nwhich stay accepted as the fallback tier.","additionalProperties":true},"CleanRequest":{"type":"object","title":"CleanRequest","properties":{"claim":{"anyOf":[{"$ref":"#/components/schemas/Claim"},{"type":"null"}],"description":"A single claim to validate. Alternative to claims."},"claims":{"type":"array","items":{"$ref":"#/components/schemas/Claim"},"title":"Claims","description":"Claims to validate."},"cleaners":{"type":"array","items":{"type":"string"},"title":"Cleaners","description":"Validation modules to run: acc, ncci, pfs, nlcd, frc, cwc, mue, dup, aoc, icx, drug, or \"all\"."}},"description":"The ``POST /api/clean`` request body.\n\nSend either ``claim`` (a single claim) or ``claims`` (a list). ``cleaners``\nselects which validation modules run; ``[\"all\"]``, the default, runs every\nmodule. Unrecognized top-level keys are accepted and echoed back in\n``METADATA.parameters``.","additionalProperties":true},"LoginRequest":{"type":"object","title":"LoginRequest","required":["token"],"properties":{"token":{"type":"string","title":"Token","maxLength":200,"minLength":1}}},"CleanResponse":{"type":"object","title":"CleanResponse","properties":{"ERRORS":{"type":"array","items":{"$ref":"#/components/schemas/ErrorItem"},"title":"Errors","default":[],"description":"Structured errors (empty tuple on full success)"},"RESULTS":{"type":"object","title":"Results","description":"{'claims': [ per-claim cleaner output ]}","additionalProperties":true},"METADATA":{"type":"object","title":"Metadata","description":"Request/timing metadata (see CleanMetadata)","additionalProperties":true}},"description":"The ``POST /api/clean`` response envelope (documentation model only).\n\n``RESULTS`` is an open dict (legacy: ``{\"claims\": [ <per-claim cleaner\noutput> ]}``) - the per-claim cleaner output is deeply dynamic (issues,\nCURED_CLAIM, AUDIT_TRAIL, ...) so we don't pin it to a model. The REAL bytes\ncome from BetterJSONEncoder; this is just the OpenAPI contract."},"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"}}},"HTTPValidationError":{"type":"object","title":"HTTPValidationError","properties":{"detail":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail"}}}}}}