{"openapi":"3.1.0","info":{"title":"Humsana API","description":"Swan checks voice messages, voicemails and call recordings for signals associated with AI-generated or synthetic speech before you act on them. It reports authenticity, evidence quality and a recommended action, and it never claims to identify a speaker.\n\nSend at least 10 seconds of speech, as a multipart upload in the `file` field, as a URL, or as a media token from a previous upload. A shorter recording returns INCONCLUSIVE rather than an error, so a caller can relay the result instead of failing.\n\nFor audio that is still arriving, open a WebSocket at /v1/voice/stream, declare the encoding and the sample rate, and send frames as you capture them. A window is analysed every few seconds and the answer may only escalate: once a session has found synthetic speech, nothing later in it can return the session to a cleaner verdict. Audio is held in memory for the length of the window and discarded as it is replaced, and a live session writes one receipt, exactly as a recording does. Every response states the limits it was judged under. Audio is never stored, in any format, and a metadata receipt is kept for 30 days.\n\nAuthentication is optional. A request that carries no credential at all is answered, at 600 requests per hour from one address, which is what someone using Swan through an assistant or a platform will do. A key, sent as `Authorization: Bearer <key>` is answered from its own bucket, and is the only way to fetch an earlier result by its id. Keys are issued instantly at https://humsana.com/swankey.html, with no account, email or payment.\n\nAn agent reaches the same service over MCP at /mcp, which exposes one tool, screen_voice, and the card a platform reads sits at /.well-known/mcp-server-card. It is the same screening behind both doors, with the same limits and the same envelope.\n\nAn autonomous actor reaches the authorization boundary at /v1/authorize/challenge, /v1/authorize/verify and /v1/authorize/effectuate. It answers who is acting, on whose behalf and under what authority, and every answer carries a swan.authz/1 receipt stating what Swan established, what a caller asserted and what a model interpreted.","version":"1.0.0"},"servers":[{"url":"https://api.humsana.com","description":"Production"}],"paths":{"/v1/health":{"get":{"summary":"Health","description":"Service status, the detector in production, the policy thresholds and whether audio is stored. Open, with no credential required, so an uptime check does not need a key.","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[]}},"/v1/voice/screen":{"post":{"summary":"Screen a recording by URL or media token","operationId":"screen_json_v1_voice_screen_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"description":"JSON body carrying exactly one of `audio_url` (fetched by the service) or `media_token` (from /v1/voice/media), plus the same optional context fields.","security":[{},{"bearerAuth":[]}]}},"/v1/voice/screen/upload":{"post":{"summary":"Screen an uploaded recording","operationId":"screen_upload_v1_voice_screen_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_screen_upload_v1_voice_screen_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"Multipart upload. The audio goes in the `file` field. The context fields `source`, `claimed_identity`, `requested_action`, `urgency`, `relationship` and `transmission_context` are optional, and context can only raise the severity of a result, never lower it. Send at least 10 seconds of speech for a verdict.","security":[{},{"bearerAuth":[]}]}},"/v1/voice/media":{"post":{"summary":"Upload once, screen many times","description":"Holds the decoded audio in memory for a short window and returns a `media_token`, so a caller screening the same recording repeatedly does not resend it. Nothing is written to disk.","operationId":"upload_media_v1_voice_media_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_media_v1_voice_media_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"bearerAuth":[]}]}},"/v1/voice/screen/{analysis_id}":{"get":{"summary":"Fetch a previous decision","operationId":"get_result_v1_voice_screen__analysis_id__get","parameters":[{"name":"analysis_id","in":"path","required":true,"schema":{"type":"string","title":"Analysis Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"The receipt for an earlier analysis: verdict, evidence quality and action. Metadata only. No audio is kept, so it cannot be re-analysed."}},"/v1/keys":{"post":{"summary":"Get an API key","operationId":"issue_key_v1_keys_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"description":"Issues a key to a caller who has none. No credential is required, and neither is an account. The key is returned once and only its hash is kept, so it cannot be shown again. The service limits how many keys one address may take and how many it issues in a day. A key is not required to screen a recording: it raises the hourly limit from 20 to 120.","security":[]}},"/v1/authorize/challenge":{"post":{"summary":"Authorize Challenge","description":"Ask what Swan requires for an action of a declared consequence class.\n\nNo policy is evaluated and nothing is stored: the answer states the questions Swan asks,\nwhich the caller has already answered, which evidence Swan will look for, and what it\ncannot check. A credential sent to this route is never echoed.","operationId":"authorize_challenge_v1_authorize_challenge_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/authorize/verify":{"post":{"summary":"Authorize Verify","description":"Submit an actor's reply and receive an authorization receipt.\n\nOne policy evaluation, one receipt, one stored binding. The receipt is the answer: it\nstates the decision, the rules that fired, the evidence in four buckets, what a provider\ninterpreted, and every gap with its reason.","operationId":"authorize_verify_v1_authorize_verify_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/authorize/confirm/request":{"post":{"summary":"Authorize Confirmation Request","description":"Ask for a confirmation of one authorization, from a party this service can authenticate.\n\nThe answer is an artifact: the authorization it is about, the exact action, the principal, the\nwindow, and the challenge a confirming party signs. Issuing it decides nothing. A service with no\nconfigured verifier refuses to issue one at all, because a request nobody can answer is worse than\na clear refusal, and a decision that does not ask for a confirmation cannot be confirmed:\na HOLD or a DENY is not lifted by a human signature.","operationId":"authorize_confirmation_request_v1_authorize_confirm_request_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/authorize/confirm":{"post":{"summary":"Authorize Confirm","description":"Answer a confirmation request, and turn a verified answer into a grant.\n\nThe order here is the safety property. The request is consumed whatever the outcome, so a wrong\nsignature burns the request rather than being retried against it, and a grant is built only from a\nverification that named a subject. A broken adapter is a refusal, never an exception, because the\ncaller's request must not be able to turn a failed integration into a 500 that reads like a grant.","operationId":"authorize_confirm_v1_authorize_confirm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/authorize/effectuate":{"post":{"summary":"Authorize Effectuate","description":"Re-check the exact action about to happen, and spend the authorization once.\n\nA match returns ALLOW and consumes the receipt, so the same authorization can never stand\nfor a second action. Everything else leaves the record exactly as it was: drift, an expired\nor unknown receipt, a digest that does not match, and a receipt belonging to another caller\nall come back with the store's own code, because a wrong request must not turn into a\ndenial of service for the right one.","operationId":"authorize_effectuate_v1_authorize_effectuate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_screen_upload_v1_voice_screen_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"claimed_identity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimed Identity"},"requested_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Action"},"urgency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Urgency"},"relationship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relationship"},"transmission_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transmission Context"},"declared_origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Declared Origin"},"attestations":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attestations"}},"type":"object","required":["file"],"title":"Body_screen_upload_v1_voice_screen_upload_post"},"Body_upload_media_v1_voice_media_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_media_v1_voice_media_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"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":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key issued for this service, sent as `Authorization: Bearer <key>`."}}},"security":[{"bearerAuth":[]}]}