{"openapi":"3.1.0","info":{"title":"Signal API","description":"Read your practice's call data — who called, when, for how long, the\ntranscript and the AI summary — so it can be logged in your EMR or any other\nsystem. Authenticate with an API key created by an owner or admin under\n**Settings → API keys** at admin.letssignal.com, sent as\n`Authorization: Bearer sig_live_…`.\n\nPoll `GET /v1/calls` with the `next_cursor` from your previous response. Each\ncall is returned exactly once, oldest first, about 10–15 minutes after it\nends, once its transcript is final.\n","version":"1.0.0"},"paths":{"/v1/whoami":{"get":{"tags":["Account"],"summary":"Whoami","description":"Check that a key works, and see which practice it belongs to.","operationId":"whoami_api_v1_whoami_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoAmI"}}}},"401":{"description":"Missing, invalid, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Key lacks the scope, or the account is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/calls":{"get":{"tags":["Calls"],"summary":"List Calls","description":"Completed calls, oldest first, from `cursor` onward. Each call is\nreturned once, about 10–15 minutes after it ends.\n\nStore `next_cursor` and pass it back on the next poll; an empty page\nwith the same cursor means \"nothing new yet\". `since` (a date or\ndatetime, UTC) only applies to a first poll with no cursor — it is how\na new integration skips history it does not want.","operationId":"list_calls_api_v1_calls_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string","description":"The `next_cursor` from your last poll. Omit on the very first poll.","default":"","title":"Cursor"},"description":"The `next_cursor` from your last poll. Omit on the very first poll."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Page size, 1–200.","default":50,"title":"Limit"},"description":"Page size, 1–200."},{"name":"since","in":"query","required":false,"schema":{"type":"string","description":"First poll only: skip calls that started before this UTC date or datetime, e.g. `2026-10-01`.","default":"","title":"Since"},"description":"First poll only: skip calls that started before this UTC date or datetime, e.g. `2026-10-01`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallPage"}}}},"401":{"description":"Missing, invalid, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Key lacks the scope, or the account is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"400":{"description":"Bad cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calls/{call_id}":{"get":{"tags":["Calls"],"summary":"Get Call","description":"One call by its `id`, in the same shape as an item from the list.","operationId":"get_call_api_v1_calls__call_id__get","parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Call"}}}},"401":{"description":"Missing, invalid, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Key lacks the scope, or the account is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such call on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ApiError":{"properties":{"detail":{"type":"string","title":"Detail","description":"A sentence saying what went wrong."}},"type":"object","required":["detail"],"title":"ApiError"},"Call":{"properties":{"id":{"type":"string","title":"Id","description":"Stable id for the call. Store it to avoid logging a call twice.","examples":["1729260011.42"]},"direction":{"type":"string","enum":["inbound","outbound","internal"],"title":"Direction","description":"`internal` is extension-to-extension; most chart integrations skip it."},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"When the call began, UTC, ISO 8601.","examples":["2026-10-18T14:00:00Z"]},"answered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answered At","description":"When it was answered, UTC. Null if never answered."},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At","description":"When it ended, UTC."},"duration_seconds":{"type":"integer","title":"Duration Seconds","description":"Total length, including ringing."},"talk_seconds":{"type":"integer","title":"Talk Seconds","description":"Time connected after answer."},"disposition":{"type":"string","title":"Disposition","description":"`answered`, `no_answer`, `busy` or `failed`.","examples":["answered"]},"from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From","description":"Calling number (E.164 where possible) or extension."},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"Called number (E.164 where possible) or extension."},"external_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Number","description":"The party outside the practice — the patient — in E.164. Match patients on this. Null for internal calls and purged records.","examples":["+13475550101"]},"caller_id_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caller Id Name","description":"Caller ID name the carrier delivered, if any."},"extension":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extension","description":"The staff extension that answered, if any."},"contact":{"anyOf":[{"$ref":"#/components/schemas/ContactRef"},{"type":"null"}],"description":"The patient this number belongs to, when Signal already knows it."},"purged":{"type":"boolean","title":"Purged","description":"True when retention has removed the phone numbers from this record. Timing and duration are kept."},"transcript_status":{"type":"string","enum":["ready","none","purged"],"title":"Transcript Status","description":"`ready`: `transcript` holds the text. `none`: not recorded or not transcribed. `purged`: deleted under the practice's retention policy (the summary is kept)."},"transcript":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript","description":"Full transcript, when `transcript_status` is `ready`."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"One-line AI summary of the call."},"action_items":{"items":{"type":"string"},"type":"array","title":"Action Items","description":"Follow-ups the AI review found."},"sentiment":{"anyOf":[{"type":"string","enum":["positive","neutral","negative"]},{"type":"null"}],"title":"Sentiment","description":"Caller sentiment, when classified."},"red_flag":{"type":"boolean","title":"Red Flag","description":"True when the AI review flagged the call for human attention."},"red_flag_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Red Flag Reason","description":"Why it was flagged."},"has_recording":{"type":"boolean","title":"Has Recording","description":"Whether audio was recorded for this call."}},"type":"object","required":["id","direction","started_at","answered_at","ended_at","duration_seconds","talk_seconds","disposition","from","to","external_number","caller_id_name","extension","contact","purged","transcript_status","transcript","summary","action_items","sentiment","red_flag","red_flag_reason","has_recording"],"title":"Call"},"CallPage":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Call"},"type":"array","title":"Data","description":"Calls, oldest first."},"next_cursor":{"type":"string","title":"Next Cursor","description":"Pass back as `cursor` on your next poll. Unchanged when nothing new has arrived."},"has_more":{"type":"boolean","title":"Has More","description":"True when a full page came back — poll again right away."}},"type":"object","required":["data","next_cursor","has_more"],"title":"CallPage"},"ContactRef":{"properties":{"id":{"type":"integer","title":"Id","description":"Signal's id for the patient record."},"external_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ref","description":"The id your own system gave this patient, if we hold one."}},"type":"object","required":["id","external_ref"],"title":"ContactRef"},"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"},"WhoAmI":{"properties":{"organization":{"type":"string","title":"Organization","description":"The practice this key belongs to."},"key_name":{"type":"string","title":"Key Name","description":"The name given to the key when it was created."},"key_prefix":{"type":"string","title":"Key Prefix","description":"The visible start of the key, as shown in Settings."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"What the key may do, e.g. `calls:read`."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"When the key stops working, ISO 8601."}},"type":"object","required":["organization","key_name","key_prefix","scopes","expires_at"],"title":"WhoAmI"}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key from Settings → API keys."}}},"servers":[{"url":"https://api.letssignal.com"}],"security":[{"apiKey":[]}]}