{"openapi":"3.1.0","info":{"title":"Hail","description":"Give your AI agent a voice, a real phone number, and an inbox.\n\nThis file is the source of truth for the Go CLI. Regenerate it after\nchanging API routes — see docs/public/contributing.md.\n","version":"0.1.0"},"servers":[{"url":"https://api.hail.so","description":"Hail Cloud"}],"paths":{"/v1/agents":{"post":{"tags":["agents"],"summary":"Create Agent","description":"Create an agent. Route numbers to it with PATCH /numbers/{id}, or\nplace calls with it via ``agent_id`` on POST /calls.","operationId":"create_agent_v1_agents_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"409":{"description":"An agent with this name already exists, or a handover contact changed while saving."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["agents"],"summary":"List Agents","description":"List the organization's agents, newest first.","operationId":"list_agents_v1_agents_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/agents/prompt-templates":{"get":{"tags":["agents"],"summary":"Get Prompt Templates","description":"The full prompt the model gets on each channel, with a marker where the\nagent's own instructions go. Hail's framing is fixed; only the\ninstructions are the agent's.","operationId":"get_prompt_templates_v1_agents_prompt_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPromptTemplates"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/agents/{agent_id}":{"get":{"tags":["agents"],"summary":"Get Agent","description":"Fetch one agent by id, including its instructions and voice settings.","operationId":"get_agent_v1_agents__agent_id__get","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Agent Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["agents"],"summary":"Update Agent","description":"Change some fields. Fields left out keep their value; ``null`` clears\nfirst_message, ai_disclosure_line, tools, max_duration_seconds or\nhandover_max_duration_seconds. Live\ncalls keep the settings they started with. ``handover_contacts`` replaces\nthe list; ``[]`` clears it and ``null`` leaves it.","operationId":"update_agent_v1_agents__agent_id__patch","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Agent Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"409":{"description":"An agent with this name already exists, or a handover contact changed while saving."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["agents"],"summary":"Delete Agent","description":"Delete an agent. Numbers that routed calls to it are unregistered\nfor inbound and ring out again; numbers that routed texts to it go back\nto webhooks only.","operationId":"delete_agent_v1_agents__agent_id__delete","parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Agent Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"502":{"description":"A number could not be taken off the inbound trunk; nothing was deleted."},"503":{"description":"A number is registered for inbound calls and this server is not configured for them; nothing was deleted."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calls":{"post":{"tags":["calls"],"summary":"Create Call","description":"Place an outbound AI voice call.\n\nThe call is placed asynchronously — this returns as soon as the call is\nqueued, not when it completes. Poll GET /v1/calls/{call_id} or configure a\nwebhook to get the final status and transcript. Requires\nrecipient_consent=true on the request body; Hail does not verify lawful\nbasis to contact the recipient, the caller warrants it.","operationId":"create_call_v1_calls_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallResponse"}}}},"429":{"description":"Rate limited. The agent-origin workspace exceeded a per-channel velocity cap, or the platform kill switch is on. Retry after the Retry-After header (seconds). Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"402":{"description":"insufficient credits; top up at https://hail.so/console/billing"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["calls"],"summary":"List Calls","description":"List calls for the caller's organization, newest first.\n\nCursor-paginated: pass the returned next_cursor to fetch the next page;\na null next_cursor means there are no more results. Filter by status or\ndestination number (to) to narrow the list.","operationId":"list_calls_v1_calls_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["queued","dialing","ringing","in_progress","completed","failed","busy","no_answer","canceled"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calls/{call_id}":{"get":{"tags":["calls"],"summary":"Get Call","description":"Fetch one call by id, including its current status and end reason.\n\nOrg-scoped: returns 404 for a call belonging to a different organization\n(not 403, to avoid confirming the id exists). Use this to poll for the\nfinal outcome of a call placed with POST /v1/calls.","operationId":"get_call_v1_calls__call_id__get","parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Call Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calls/{call_id}/recording":{"get":{"tags":["calls"],"summary":"Get Recording","description":"Redirect to a short-lived recording URL. Returns 404 if unavailable or expired.","operationId":"get_recording_v1_calls__call_id__recording_get","parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Call Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"302":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-attachments":{"post":{"tags":["email-attachments"],"summary":"Create Email Attachment","description":"Upload a file and get back a reusable attachment id.\n\nThe returned id can be referenced from attachment_ids on many later\nPOST /v1/emails calls until it is garbage-collected for being unused; it is\nnot deleted immediately after first use. Uploads are size-limited and\nscoped to the caller's organization.","operationId":"upload_email_attachment","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_email_attachment"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailAttachmentUploadResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails":{"post":{"tags":["emails"],"summary":"Create Email","description":"Send an outbound email through SES.\n\nSends synchronously. A 201 means SES accepted the message and the\nresponse status is 'sent'; no separate poll is needed for the happy path,\nthough a bounce or complaint can still arrive later as a webhook or\nGET /v1/emails/{email_id}/events entry. If the send fails, the response is\n502 'email send failed' with no email body; the email is kept with status\n'failed' (GET /v1/emails?status=failed) and an email.send_failed event is\nemitted. Requires recipient_consent=true on the request body; Hail does\nnot verify lawful basis to contact the recipient, the caller warrants it.","operationId":"create_email_v1_emails_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResponse"}}}},"429":{"description":"Rate limited. The agent-origin workspace exceeded a per-channel velocity cap, or the platform kill switch is on. Retry after the Retry-After header (seconds). Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"402":{"description":"insufficient credits; top up at https://hail.so/console/billing"},"502":{"description":"Email send failed: the provider rejected the send. The email is recorded with status 'failed'."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["emails"],"summary":"List Emails","description":"List emails for the caller's organization, newest first.\n\nCursor-paginated: pass the returned next_cursor to fetch the next page;\na null next_cursor means there are no more results. Filter by status or\ndirection (outbound/inbound). List entries omit body_text/body_html —\nfetch GET /v1/emails/{email_id} for the full body.","operationId":"list_emails_v1_emails_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["queued","sent","delivered","failed","bounced","complained","received"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"enum":["outbound","inbound"],"type":"string"},{"type":"null"}],"title":"Direction"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/{email_id}/events":{"get":{"tags":["emails"],"summary":"List Email Events","description":"Chronological lifecycle events for one email (org-scoped).\n\nCursor-paginated with the same forward-walk shape as ``GET /events``:\nstrictly-greater on ``(occurred_at, id)``, ascending.","operationId":"list_email_events_v1_emails__email_id__events_get","parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Email Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailEventListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/stats":{"get":{"tags":["emails"],"summary":"Get Email Stats","description":"Aggregate send/delivery/open/click/bounce counts and rates over a range.\n\nDefaults to the last 7 days, bucketed by day. bucket=hour is limited to\nan 8-day range; any bucket size is limited to a 92-day range. Registered\nabove GET /v1/emails/{email_id} so \"stats\" is not swallowed by the id path param.","operationId":"get_email_stats_v1_emails_stats_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"To"}},{"name":"bucket","in":"query","required":false,"schema":{"enum":["hour","day"],"type":"string","default":"day","title":"Bucket"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/suppressions":{"get":{"tags":["emails"],"summary":"List Email Suppressions","description":"List email addresses suppressed from receiving mail, newest first.\n\nCursor-paginated. A suppressed address blocks POST /v1/emails sends to it\nwith a 403 until removed via DELETE /v1/emails/suppressions/{address}.","operationId":"list_email_suppressions_v1_emails_suppressions_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/suppressions/{address}":{"delete":{"tags":["emails"],"summary":"Delete Email Suppression","description":"Remove one address from the email suppression list, re-allowing sends to it.\n\nThe address match ignores case. Does not itself constitute renewed\nconsent: the caller is responsible for having a lawful basis (e.g. a\nfresh opt-in) before sending again. Returns 404 if the address was not\nsuppressed.","operationId":"delete_email_suppression_v1_emails_suppressions__address__delete","parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","title":"Address"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/{email_id}":{"get":{"tags":["emails"],"summary":"Get Email","description":"Fetch one email by id, including attachments and last event time.\n\nOrg-scoped: returns 404 for an email belonging to a different\norganization. For an inbound email with a stored raw MIME, raw_url\npoints at GET /v1/emails/{email_id}/raw.","operationId":"get_email_v1_emails__email_id__get","parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Email Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/{email_id}/raw":{"get":{"tags":["emails"],"summary":"Get Email Raw","description":"302 → presigned S3 URL for the raw inbound MIME (404 for outbound).","operationId":"get_email_raw_v1_emails__email_id__raw_get","parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Email Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/emails/{email_id}/attachments/{attachment_id}":{"get":{"tags":["emails"],"summary":"Get Email Attachment","description":"302 → presigned S3 URL for one attachment.","operationId":"get_email_attachment_v1_emails__email_id__attachments__attachment_id__get","parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Email Id"}},{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Attachment Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/events":{"get":{"tags":["events"],"summary":"List Events","description":"Cursor-paginated forward stream of call, email, and SMS events.\n\nScoped to the caller's organization. Pass id (typed \"<type>:<uuid>\",\ne.g. \"call:<uuid>\") to narrow to one resource's events, or kind to\nnarrow to one event kind. Walks forward in time — pass the returned\nnext_cursor to continue tailing where you left off.","operationId":"list_events_v1_events_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventStreamResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-domains":{"post":{"tags":["email-domains"],"summary":"Create Email Domain","description":"Register a sender identity to send outbound email through.\n\nkind=\"hail_mail\" mints an address on the shared hail-mail domain and is\nimmediately verified — no DNS work needed. kind=\"custom\" registers your\nown domain with SES and returns DKIM records; the domain stays\nunverified (POST /v1/email-domains/{domain_id}/verify) until you publish\nthose DNS records and Hail confirms them.","operationId":"create_email_domain_v1_email_domains_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["email-domains"],"summary":"List Email Domains","description":"List sender domains/identities for the caller's organization.\n\nCursor-paginated, newest first. The response also includes\ndefault_from — the address a from-less POST /v1/emails would use right\nnow given the org's current verified senders.","operationId":"list_email_domains_v1_email_domains_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-domains/check-domain":{"get":{"tags":["email-domains"],"summary":"Check Domain","description":"Does this domain already receive mail? Drives apex-vs-prefix onboarding.","operationId":"check_domain_v1_email_domains_check_domain_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheckResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-domains/{domain_id}":{"get":{"tags":["email-domains"],"summary":"Get Email Domain","description":"Fetch one sender domain/identity by id, including its DNS records.\n\nOrg-scoped: returns 404 for a domain belonging to a different\norganization. For a pending custom domain, dns_records lists the DKIM\nrecords still to publish.","operationId":"get_email_domain_v1_email_domains__domain_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["email-domains"],"summary":"Patch Email Domain","description":"Edit hail-mail prefixes and/or inbound action settings.\n\nTwo modes, mutually compatible:\n\n* **Prefix edit** (``local_prefix_user`` / ``local_prefix_org``):\n  hail_mail rows only. The managed-cloud console writes here when\n  an org admin changes the visible hail-mail address.\n* **Inbound action edit** (``inbound_enabled`` / ``forward_to`` /\n  ``forward_rate_per_hour``): any row kind.","operationId":"patch_email_domain_v1_email_domains__domain_id__patch","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainResponse"}}}},"429":{"description":"The organization reached its daily confirm-mail limit for new forward addresses. Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["email-domains"],"summary":"Delete Email Domain","description":"Permanently remove a sender domain/identity.\n\nIrreversible — for a custom domain, also deletes the SES identity, so\nthe domain can no longer send until re-registered and re-verified.\nFails with 409 if any Email rows still reference this domain; delete\nor wait for those to age out first.","operationId":"delete_email_domain_v1_email_domains__domain_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-domains/{domain_id}/dns-check":{"get":{"tags":["email-domains"],"summary":"Dns Check Email Domain","description":"Guided DNS check: detect the tenant's DNS host and confirm published records.\n\nRead-only — runs live lookups against public DNS (no resolver\ndependency) and never touches verification_status; SES stays the\nauthority for that (see POST /{domain_id}/verify). Org-scoped: returns\n404 for a domain belonging to a different organization. kind='hail_mail'\nrows do no DNS lookups at all — the shared hail-mail domain has nothing\nfor the tenant to publish.","operationId":"dns_check_email_domain_v1_email_domains__domain_id__dns_check_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainDnsCheck"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-domains/{domain_id}/verify":{"post":{"tags":["email-domains"],"summary":"Verify Email Domain","description":"Re-poll the email provider for the current verification status.\n\nOn-demand only — there is no background poller in v1. Operators /\ntenants hit this after publishing DNS to flip the row to ``verified``.\nHail-mail rows are no-ops (they're already verified by construction).","operationId":"verify_email_domain_v1_email_domains__domain_id__verify_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/forward-targets":{"get":{"tags":["forward-targets"],"summary":"List Forward Targets","description":"List every forward address of the organization with its status.\n\nAn address appears here once it was ever put in a domain's\n``forward_to``. Only ``verified`` addresses receive forwards.","operationId":"list_forward_targets_v1_forward_targets_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardTargetListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/forward-targets/{address}/resend":{"post":{"tags":["forward-targets"],"summary":"Resend Forward Confirm","description":"Send a new confirm link to a pending or stopped forward address.\n\nUse this when the first mail got lost, or to restart forwarding after a\nspam complaint stopped it. One link per address every 10 minutes.","operationId":"resend_forward_confirm_v1_forward_targets__address__resend_post","parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","title":"Address"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardTargetResponse"}}}},"404":{"description":"No such forward address in this organization."},"409":{"description":"The address is already verified, or the organization has no sending identity."},"429":{"description":"A confirm link was sent less than 10 minutes ago, the organization reached its daily confirm-mail limit, or the address already received its daily confirm mails. Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/numbers":{"post":{"tags":["numbers"],"summary":"Acquire Number","description":"Buy a dedicated phone number for the caller's organization.\n\nThis purchases a real number at the carrier and starts a recurring\nmonthly fee immediately — it is not a reservation. quote_id is required:\nrequest live offers from POST /numbers/quotes first, then buy one. The\nnumber is usable for voice, SMS, or both, as the quote lists.\n\nA number that needs verification is bought with one of your approved\nverifications. With one, it is used. With more than one (several people\nor companies), pass `verification_id`: the `id` of one of the quote's\n`verifications`, which carry the person's or company's name.","operationId":"acquire_number_v1_numbers_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberAcquireRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"404":{"description":"The quote does not exist for this organization."},"422":{"description":"quote_id is missing (request one from POST /numbers/quotes), the quote does not match the country or type, the offer is by_request (ask support for it), or the verification to buy with is unclear or not usable: you have more than one approved verification for this number (pass verification_id), verification_id is not one of your approved verifications for it, or you are not verified as the subject_type you passed."},"402":{"description":"insufficient credits; top up at https://hail.so/console/billing. Also returned when the balance doesn't cover this number's full monthly price."},"409":{"description":"The quote expired, the number is taken, the price changed, expected_total_cents differs from the quoted total, or the carrier rejected or failed the order. In the last case the credit hold is refunded and the detail gives the reason. Also returned when the quote's number was released since it was bought."},"503":{"description":"The carrier lookup or number price is unavailable, or no inventory matched. Nothing was charged; retry shortly."}}},"get":{"tags":["numbers"],"summary":"List Numbers","description":"List dedicated numbers owned by the caller's organization.\n\nCursor-paginated, newest first. Only org-owned numbers are listed —\nshared pool numbers used for outbound calls never appear here. Failed\norders that were dismissed (DELETE) are not listed.","operationId":"list_numbers_v1_numbers_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/numbers/{number_id}":{"delete":{"tags":["numbers"],"summary":"Release Number","description":"Release a dedicated number. The monthly fee stops accruing after the\nrelease month; months already accrued stay owed (the rater bills late,\nnever forgives). A number that answered calls is taken off the inbound\ntrunk first.","operationId":"release_number_v1_numbers__number_id__delete","parameters":[{"name":"number_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"404":{"description":"The number does not exist for this organization."},"409":{"description":"The order is still pending (refresh its status first) or the number's carrier is unsupported. A failed order is dismissed with 204 and no carrier call."},"503":{"description":"The number's carrier is not configured on this server."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["numbers"],"summary":"Route Number","description":"Choose which agent answers this number.\n\nSetting ``voice_agent_id`` registers the number for inbound calls at the\ncarrier and on Hail's LiveKit inbound trunk; ``null`` unregisters it, and\ncalls ring out again. ``sms_agent_id`` needs the sms capability and sets the\nnumber up for SMS at the carrier if that has not happened yet. A field left\nout keeps its value.","operationId":"route_number_v1_numbers__number_id__patch","parameters":[{"name":"number_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberRoutingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"404":{"description":"The number or the agent does not exist for this organization."},"422":{"description":"The number lacks the capability the agent would answer on."},"502":{"description":"The carrier or LiveKit refused to register the number for inbound."},"503":{"description":"This server is not configured for inbound calls, or not on the number's carrier."}}},"get":{"tags":["numbers"],"summary":"Get Number","description":"Fetch one dedicated number by id, including its capabilities and state.\n\nOrg-scoped: returns 404 for a number belonging to a different\norganization. Read-only: a pending order is advanced by the background\nreconciler, never by this request.","operationId":"get_number_v1_numbers__number_id__get","parameters":[{"name":"number_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/numbers/regions":{"get":{"tags":["numbers"],"summary":"List Number Regions","description":"US states and Canadian provinces for the ``region`` field of\n``POST /numbers/quotes``. In the US and Canada a city search needs one:\ncity names repeat across states.","operationId":"list_number_regions_v1_numbers_regions_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberRegionsResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/numbers/{number_id}/enable-sms":{"post":{"tags":["numbers"],"summary":"Enable Sms","description":"Set up a dedicated number for SMS at its carrier.\n\nHail does this itself when an sms-capable number becomes active and when\na texts agent is assigned, so this call is only needed to repair a number\nwhose setup failed, or an older one whose service lacks the inbound\ntext settings. Idempotent — an already-enabled number gets those settings\nset again and returns its current state. A US long code is also put on\nthe org's approved US SMS registration. Fails with 422 for a released\nnumber or one that lacks sms capability.","operationId":"enable_sms_v1_numbers__number_id__enable_sms_post","parameters":[{"name":"number_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"404":{"description":"The number does not exist for this organization."},"502":{"description":"SMS could not be enabled on this number; the reason is in the server log."},"503":{"description":"SMS is not configured for this number's carrier on this server."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/numbers/quotes":{"post":{"tags":["numbers"],"summary":"Quote Numbers","description":"Compare live, org-specific offers. Prices include setup + monthly rent.\n\nRecommends a ready offer with the lowest monthly rent, then setup cost.\nBlocked offers sort by verification effort. SMS capability does not waive\nmessaging registration requirements. `provider` is ignored: Hail compares\nevery carrier.\n\nA number support orders by hand comes back as an offer with\n`by_request: true` and no `e164`, after the offers you can buy. It is never\nrecommended and POST /numbers refuses it: ask support. Its `quote_id`\nworks for GET /verifications/requirements and POST /verifications. Without\n`area_code` or `city` only.\n\nEach offer lists `subject_types` (who may verify for it: a person, a\nbusiness, or both) and the `verifications` you can buy it with, each\nwith the name of its person or company (`subject_name`), approved first:\nyours of an accepted subject type that are under review, and the\napproved ones the carrier still accepts for this offer. Pass an approved\none's `id` as `verification_id` to POST /numbers. An offer that needs\nverification is `ready` when one of them is approved.","operationId":"quote_numbers_v1_numbers_quotes_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberQuoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberQuotesResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks":{"post":{"tags":["webhooks"],"summary":"Create Subscription","description":"Create a webhook subscription for one or more event types.\n\nThe response includes the plaintext signing secret — this is the only\ntime it is ever returned; store it now. Every later GET omits it. Use\nPOST /v1/webhooks/{sub_id}/rotate-secret to get a new plaintext secret if\nit is lost or compromised.","operationId":"create_subscription_v1_webhooks_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["webhooks"],"summary":"List Subscriptions","description":"List webhook subscriptions for the caller's organization.\n\nCursor-paginated, newest first. The signing secret is never included —\nonly POST /v1/webhooks and POST /v1/webhooks/{sub_id}/rotate-secret return it.","operationId":"list_subscriptions_v1_webhooks_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/event-types":{"get":{"tags":["webhooks"],"summary":"List Event Types","description":"List event names, labels, and channels accepted by subscriptions.\n\nThis public catalog contains no organization data. It uses the same\nregistry as create and patch validation.","operationId":"list_event_types_v1_webhooks_event_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEventCatalogResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}}}}},"/v1/webhooks/{sub_id}":{"get":{"tags":["webhooks"],"summary":"Get Subscription","description":"Fetch one webhook subscription by id. The signing secret is omitted.","operationId":"get_subscription_v1_webhooks__sub_id__get","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["webhooks"],"summary":"Patch Subscription","description":"Update a webhook subscription's target_url, event_types, and/or status.\n\nOnly fields present in the request body are changed. Setting\nstatus=\"active\" resets the consecutive-failure counter, so a\nsubscription that was auto-disabled after 50 straight delivery\nfailures does not immediately re-disable itself.","operationId":"patch_subscription_v1_webhooks__sub_id__patch","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete Subscription","description":"Permanently remove a webhook subscription.\n\nIrreversible — no further events are delivered to it, and its\ndelivery history is deleted with it.","operationId":"delete_subscription_v1_webhooks__sub_id__delete","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{sub_id}/rotate-secret":{"post":{"tags":["webhooks"],"summary":"Rotate Secret","description":"Generate a new signing secret and immediately invalidate the old one.\n\nThe response includes the new plaintext secret — this is the only\ntime it is returned; update your verification code with it right away,\nsince the old secret stops validating new deliveries immediately.","operationId":"rotate_secret_v1_webhooks__sub_id__rotate_secret_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{sub_id}/deliveries":{"get":{"tags":["webhooks"],"summary":"List Deliveries","description":"List delivery attempts for one webhook subscription, newest first.\n\nCursor-paginated. Each entry shows the attempt count, response status,\nand response body Hail recorded — use POST /v1/webhooks/{sub_id}/\ndeliveries/{delivery_id}/redeliver to retry a failed one.","operationId":"list_deliveries_v1_webhooks__sub_id__deliveries_get","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{sub_id}/deliveries/{delivery_id}/redeliver":{"post":{"tags":["webhooks"],"summary":"Redeliver","description":"Retry one webhook delivery attempt.\n\nResets it to pending with a fresh attempt counter — the delivery\nworker picks it up and re-sends the same event payload to target_url\nshortly after. Useful after fixing an endpoint that was returning\nerrors.","operationId":"redeliver_v1_webhooks__sub_id__deliveries__delivery_id__redeliver_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sub Id"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Delivery Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/unsubscribe":{"post":{"tags":["unsubscribe"],"summary":"Unsubscribe","description":"One-click email opt-out (RFC 8058).\n\n``GET`` is a person clicking the link in a sent email. ``POST`` is the\nmailbox provider's unsubscribe button (Gmail, Yahoo), which sends the\nbody ``List-Unsubscribe=One-Click``. The body is optional.\n\nPublic and unauthenticated — the signed token query param is the sole\ncredential, proving the caller holds a link Hail sent to that address.\nOn success, the address is added to the org's suppression list and all\nfuture outbound email to it is blocked. Returns an HTML page, not JSON.","operationId":"unsubscribe_v1_unsubscribe_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"400":{"description":"The token is invalid or has expired.","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"List-Unsubscribe":{"type":"string","enum":["One-Click"]}}}}}}},"get":{"tags":["unsubscribe"],"summary":"Unsubscribe","description":"One-click email opt-out (RFC 8058).\n\n``GET`` is a person clicking the link in a sent email. ``POST`` is the\nmailbox provider's unsubscribe button (Gmail, Yahoo), which sends the\nbody ``List-Unsubscribe=One-Click``. The body is optional.\n\nPublic and unauthenticated — the signed token query param is the sole\ncredential, proving the caller holds a link Hail sent to that address.\nOn success, the address is added to the org's suppression list and all\nfuture outbound email to it is blocked. Returns an HTML page, not JSON.","operationId":"unsubscribe_v1_unsubscribe_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"400":{"description":"The token is invalid or has expired.","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sms":{"post":{"tags":["sms"],"summary":"Create Sms","description":"Send an outbound SMS.\n\nSends synchronously — the response reports the final status (sent or\nfailed), not a queued placeholder, though delivery confirmation from\nthe carrier can still arrive later as a webhook or GET /v1/sms/{sms_id}\nupdate. An explicit from resolves a dedicated number; otherwise Hail\npicks an alphanumeric sender ID where the destination corridor allows\nit, or requires a dedicated SMS-capable number otherwise. Requires\nrecipient_consent=true on the request body; Hail does not verify\nlawful basis to contact the recipient, the caller warrants it.","operationId":"create_sms_v1_sms_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsResponse"}}}},"429":{"description":"Rate limited. The agent-origin workspace exceeded a per-channel velocity cap, or the platform kill switch is on. Retry after the Retry-After header (seconds). Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"402":{"description":"insufficient credits; top up at https://hail.so/console/billing"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["sms"],"summary":"List Sms","description":"List SMS messages for the caller's organization, newest first.\n\nCursor-paginated: pass the returned next_cursor to fetch the next page;\na null next_cursor means there are no more results. Filter by status or\ndestination number (to) to narrow the list.","operationId":"list_sms_v1_sms_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["queued","sent","delivered","failed","undelivered","received"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sms/suppressions":{"get":{"tags":["sms"],"summary":"List Sms Suppressions","description":"List SMS numbers suppressed from receiving messages, newest first.\n\nCursor-paginated. A suppressed number blocks POST /v1/sms sends to it\nwith a 403 until removed via DELETE /v1/sms/suppressions/{number}.","operationId":"list_sms_suppressions_v1_sms_suppressions_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sms/suppressions/{number}":{"delete":{"tags":["sms"],"summary":"Delete Sms Suppression","description":"Remove one number from the SMS suppression list, re-allowing sends to it.\n\nDoes not itself constitute renewed consent — the caller is responsible\nfor having a lawful basis (e.g. a fresh opt-in) before sending again.\nReturns 404 if the number was not suppressed.","operationId":"delete_sms_suppression_v1_sms_suppressions__number__delete","parameters":[{"name":"number","in":"path","required":true,"schema":{"type":"string","title":"Number"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sms/sender-id":{"get":{"tags":["sms"],"summary":"Get Sender Id","description":"Get the org's custom alphanumeric SMS sender ID, if any is set.\n\neffective_default is the platform sender id used for\nalphanumeric-eligible corridors when custom_sender_id is null.","operationId":"get_sender_id_v1_sms_sender_id_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderIdResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["sms"],"summary":"Patch Sender Id","description":"Set or clear the org's custom alphanumeric SMS sender ID.\n\nPass custom_sender_id=null to clear it and fall back to the platform\ndefault sender id. Only affects alphanumeric-eligible corridors — a\ncorridor requiring a dedicated number is unaffected.","operationId":"patch_sender_id_v1_sms_sender_id_patch","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderIdPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderIdResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sms/{sms_id}":{"get":{"tags":["sms"],"summary":"Get Sms","description":"Fetch one SMS by id, including its current status.\n\nOrg-scoped: returns 404 for an SMS belonging to a different\norganization.","operationId":"get_sms_v1_sms__sms_id__get","parameters":[{"name":"sms_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Sms Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts":{"get":{"tags":["contacts"],"summary":"List Contacts","description":"List contacts for the caller's organization, newest first.\n\nMerges two sources into one list: org members (kind=\"member\", ids\nprefixed \"member:\", managed via membership — not editable here) and\nmanually-created contacts (kind=\"manual\", editable via PATCH/DELETE\n/contacts/{contact_id}). Cursor-paginated; q does a substring search\nover name/phone/email.","operationId":"list_contacts_v1_contacts_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["contacts"],"summary":"Create Contact","description":"Create a manual contact with a phone and/or an email.\n\nRequires at least one of phone_e164 or email. Fails with 409 if a\ncontact with the same phone or email already exists in this\norganization.","operationId":"create_contact_v1_contacts_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactEntry"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}":{"patch":{"tags":["contacts"],"summary":"Patch Contact","description":"Update a manual contact's fields. Only fields present in the body change.\n\nManual contacts only — a member: id (org members synced from\nmembership) returns 422; edit those via the membership APIs instead.\nThe contact must still have at least one of phone_e164 or email after\nthe update. Fails with 409 on a duplicate phone/email.","operationId":"patch_contact_v1_contacts__contact_id__patch","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactEntry"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Delete Contact","description":"Permanently remove a manual contact.\n\nManual contacts only — a member: id returns 422; org members are\nremoved via the membership APIs, not this route. Irreversible.","operationId":"delete_contact_v1_contacts__contact_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/members/{user_id}/phone":{"put":{"tags":["contacts"],"summary":"Put Member Phone","description":"Set an org member's phone number.\n\nPass user_id=\"me\" to set your own, or a member's user id — setting\nanother member's phone requires the caller to be an org owner or\nadmin. Returns 404 if the target is not a member of this organization.","operationId":"put_member_phone_v1_members__user_id__phone_put","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberPhonePut"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Put Member Phone V1 Members  User Id  Phone Put"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Delete Member Phone","description":"Clear an org member's phone number.\n\nPass user_id=\"me\" to clear your own, or a member's user id — clearing\nanother member's phone requires the caller to be an org owner or\nadmin. Returns 404 if the target is not a member of this organization.","operationId":"delete_member_phone_v1_members__user_id__phone_delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/whoami":{"get":{"tags":["whoami"],"summary":"Get Whoami","description":"Identify the caller: auth kind, organization, and (if resolvable) user.\n\nUseful for an agent that needs the human's address to put in\nReply-To, since the bearer token itself only carries an organization.\nuser_id/email/name come back null for a shared-key\n(HAIL_API_KEY) call, which has no individual user behind it.","operationId":"get_whoami_v1_whoami_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoamiResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers":{"get":{"tags":["providers"],"summary":"List Providers","description":"Every saved provider row for the caller's organization, all layers.","operationId":"list_providers","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigListResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers/{layer}":{"put":{"tags":["providers"],"summary":"Upsert Provider","description":"Save a provider for ``layer`` and make it the active one.\n\nA partial write: anything you omit keeps its saved value. Omitting\n``api_key`` keeps the stored key, ``params`` keys you don't send are\npreserved, and omitting ``fallback_enabled`` leaves the flag alone\n(``false`` on a new row). The merged result — not the partial input —\nis validated against the layer's schema (422 on a mismatch). 404 on an\nunknown layer.","operationId":"upsert_provider","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigUpsert"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigEntry"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers/{layer}/{provider}":{"delete":{"tags":["providers"],"summary":"Delete Provider","description":"Delete one provider row. Deleting the active row promotes the\nmost-recently-updated sibling. Idempotent: deleting a row that isn't\nthere is a 204 too. 404 on an unknown layer.","operationId":"delete_provider","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers/{layer}/activate":{"post":{"tags":["providers"],"summary":"Activate Provider","description":"Switch which saved provider is active for ``layer``. 404 when that\nprovider has no saved config.","operationId":"activate_provider","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderActivateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigEntry"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers/{layer}/validate":{"post":{"tags":["providers"],"summary":"Validate Provider","description":"Probe a provider key against the real provider.\n\nEmpty body tests the layer's active provider with its stored key; send\n``provider`` to test a specific saved row, or ``api_key`` (plus\n``provider``/``params``) to test a key before saving it.","operationId":"validate_provider","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderValidateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderValidateResult"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/verifications/requirements":{"get":{"tags":["verifications"],"summary":"Get Requirements","description":"What the carrier needs from you: the fields, the documents, and whether\nan address is needed. `purpose=number` (default) is for buying a number;\nit requires the offer's `quote_id` (422 without it) and returns the form\nfor that offer. `purpose=sms`\nis for registering to send SMS in the US; it uses the carrier of your US\nnumbers, or of the number passed as `number_id`. Build your form from it.\n`provider` is ignored.","operationId":"get_verification_requirements","parameters":[{"name":"country_code","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"title":"Country Code"}},{"name":"number_type","in":"query","required":true,"schema":{"type":"string","title":"Number Type"}},{"name":"subject_type","in":"query","required":true,"schema":{"enum":["person","business"],"type":"string","description":"Who is verifying: 'person' (you) or 'business' (your company). Required. Ask the user which one.","title":"Subject Type"},"description":"Who is verifying: 'person' (you) or 'business' (your company). Required. Ask the user which one."},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Deprecated and ignored. Hail picks the carrier from quote_id or number_id.","deprecated":true,"title":"Provider"},"description":"Deprecated and ignored. Hail picks the carrier from quote_id or number_id.","deprecated":true},{"name":"purpose","in":"query","required":false,"schema":{"type":"string","default":"number","title":"Purpose"}},{"name":"quote_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Required for purpose=number: quote_id of an offer from POST /numbers/quotes that needs verification. The carrier is read from it.","title":"Quote Id"},"description":"Required for purpose=number: quote_id of an offer from POST /numbers/quotes that needs verification. The carrier is read from it."},{"name":"number_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"For purpose=sms: id of one of your US numbers. The carrier is read from it. Needed when your US numbers are with more than one carrier.","title":"Number Id"},"description":"For purpose=sms: id of one of your US numbers. The carrier is read from it. Needed when your US numbers are with more than one carrier."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationRequirements"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/verifications":{"post":{"tags":["verifications"],"summary":"Create Verification","description":"Submit your details and documents (multipart/form-data).\n\nParts: `purpose` (`number`, the default, or `sms` to register to send SMS\nin the US; `sms` takes `country_code=US` and `number_type=local`; its fees\nare charged from credit per step, when the details are submitted and when\nthe carriers start their review, and 402 means there is not enough credit\nto file, also when a kept brand cannot be reused and the credit does not\ncover a new one), `country_code`, `number_type`, `subject_type` (required:\n`person` for you, `business` for your company; ask the user which),\n`quote_id` (required for `number`: the offer to verify for), `number_id`\n(optional, for `sms`: one of your US numbers; needed when your US numbers\nare with more than one carrier),\n`fields` (JSON object), `address` (JSON object, when required), `documents`\n(JSON object: slot name to `{\"option\": ..., \"fields\": {...}}`), and one file\npart per slot named `file.<slot>`. Get the slot and field names from\n`GET /verifications/requirements`.\n\nThe name of the person or company comes from the form you send (for\nexample the business name, or the first and last name) and is shown\nback as `subject_name`. For a number you may hold several verifications\nof each subject type (several people, several companies); a second live\none of the same subject type and the same name is a 409, and so is a\nsecond live one of the same subject type when the form holds no name.\nA 409 comes before your files are checked and before anything is filed.\n\nThe carrier checks the details right away. Problems come back as 422 with\nthe field named, and nothing is kept. When it passes, the verification is\nsubmitted to the carrier for its review; read it back to follow the state.\nA `provider` part is deprecated and ignored: Hail picks the carrier.","operationId":"create_verification","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["verifications"],"summary":"List Verifications","description":"Your organization's verifications, newest first. Pass `purpose`\n(`number` or `sms`) to list only that kind.","operationId":"list_verifications","parameters":[{"name":"purpose","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VerificationResponse"},"title":"Response List Verifications"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/verifications/{verification_id}":{"get":{"tags":["verifications"],"summary":"Get Verification","description":"One verification of your organization. Once submitted, its state is\nupdated from the carrier each time you read it.","operationId":"get_verification","parameters":[{"name":"verification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Verification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["verifications"],"summary":"Cancel Verification","description":"Withdraw a verification that has not been sent for review, or dismiss\na rejected one. The draft is discarded either way, except a person's\nconfirmed brand on a rejected US SMS registration: it stays at the\ncarrier for the next filing. One that is under review cannot be\ncancelled: wait for the result; if it is rejected, dismiss it.\n\nAn approved US SMS registration (`purpose=sms`) can be cancelled too:\nits numbers leave the campaign, the campaign is deleted at the carrier,\nand US-to-US texts from those numbers get 422 again. Its monthly fee is\ncharged through the month of the cancellation, then stops. Some carriers\nbill a minimum number of monthly fees from approval: a registration\ncancelled sooner is still charged for that many months (prices: see the\nconsole).","operationId":"cancel_verification","parameters":[{"name":"verification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Verification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/verifications/{verification_id}/confirm":{"post":{"tags":["verifications"],"summary":"Confirm Verification","description":"Enter the code the carrier texted to confirm a person's US texting\nregistration. Use it when the verification shows `confirmation: \"code\"`.\nOn success the campaign is filed at once; poll GET /verifications/{id}\nuntil `approved`. A wrong or expired code is 422 on `code`; after five\nwrong codes, 429 until the carrier sends a new code (once a day). 409\nwhen there is nothing to confirm.","operationId":"confirm_verification","parameters":[{"name":"verification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Verification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResponse"}}}},"429":{"description":"Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"The request ceiling for the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/healthz":{"get":{"summary":"Healthz","description":"Liveness check. Returns {\"status\": \"ok\"} with no auth required.","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthz Healthz Get"}}}}}}}},"components":{"schemas":{"AgentCreate":{"properties":{"name":{"type":"string","maxLength":80,"minLength":1,"title":"Name","description":"Display name, unique per organization."},"system_prompt":{"type":"string","minLength":1,"title":"System Prompt","description":"Task instructions, sent as the agent's leading system message."},"first_message":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"First Message","description":"Opening line spoken after the AI line. Omitted: the agent waits for the other side to speak first."},"recording_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Enabled","description":"Null inherits the workspace recording choice; true or false sets recording for this agent's calls, whatever the workspace setting."},"ai_disclosure":{"type":"boolean","title":"Ai Disclosure","description":"Speak the AI line first on every call. Enabled by default. Disable only if you have verified the disclosure is not required (47 CFR 64.1200(b)(1) and several AI bot-disclosure laws). Hail does not verify this for you.","default":true},"ai_disclosure_line":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Ai Disclosure Line","description":"The AI line this agent speaks. '{org}' is replaced by the organization's name. Omitted: the workspace line, else Hail's built-in line for the call's direction."},"voice_config":{"$ref":"#/components/schemas/VoiceConfig","description":"TTS voice and spoken language, same shape as on POST /calls."},"tools":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tools","description":"Agent tools to allow. Omitted: every tool the organization's channels support. Empty list: none."},"max_duration_seconds":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":60.0},{"type":"null"}],"title":"Max Duration Seconds","description":"Soft cap per call in seconds (60..3600). Omitted: the workspace limit."},"handover_max_duration_seconds":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":60.0},{"type":"null"}],"title":"Handover Max Duration Seconds","description":"Call limit in seconds (60..3600) once a handover contact answers, counted from the answer. Omitted: 1800 (30 minutes)."},"voice_enabled":{"type":"boolean","title":"Voice Enabled","description":"Answer calls on numbers that route calls to this agent.","default":true},"sms_enabled":{"type":"boolean","title":"Sms Enabled","description":"Answer texts on numbers that route texts to this agent.","default":true},"status":{"type":"string","enum":["live","paused"],"title":"Status","description":"'paused' agents do not answer; calls to their numbers fail with end_reason 'no_agent'.","default":"live"},"handover_contacts":{"items":{"$ref":"#/components/schemas/HandoverContactIn"},"type":"array","maxItems":10,"title":"Handover Contacts","description":"People the agent may hand a live call to, in order. The agent never sees their numbers."}},"additionalProperties":false,"type":"object","required":["name","system_prompt"],"title":"AgentCreate","description":"A saved agent: what a number answers with, and what POST /calls can\nplace calls with via ``agent_id``."},"AgentListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AgentResponse"},"type":"array","title":"Items","description":"Agents, newest first."}},"type":"object","required":["items"],"title":"AgentListResponse"},"AgentPromptTemplates":{"properties":{"instructions_placeholder":{"type":"string","title":"Instructions Placeholder","description":"The marker inside each template where the agent's instructions go."},"calls_in":{"type":"string","title":"Calls In","description":"Full prompt for a call the agent answers."},"calls_out":{"type":"string","title":"Calls Out","description":"Full prompt for a call the agent places."},"texts":{"type":"string","title":"Texts","description":"Full prompt for a text reply."}},"type":"object","required":["instructions_placeholder","calls_in","calls_out","texts"],"title":"AgentPromptTemplates","description":"What the model is told on each channel, read-only. Hail's framing leads\nand cannot be edited; ``instructions_placeholder`` marks where the agent's\nown instructions go."},"AgentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this agent."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that owns this agent."},"name":{"type":"string","title":"Name","description":"Display name, unique per organization."},"system_prompt":{"type":"string","title":"System Prompt","description":"Task instructions."},"first_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Message","description":"Opening line, or null to wait."},"recording_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Enabled","description":"Null inherits the workspace recording choice; true or false sets recording for this agent's calls, whatever the workspace setting."},"ai_disclosure":{"type":"boolean","title":"Ai Disclosure","description":"Whether the AI line is spoken first."},"ai_disclosure_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Disclosure Line","description":"This agent's AI line template, or null for the workspace line."},"voice_config":{"additionalProperties":true,"type":"object","title":"Voice Config","description":"TTS voice and language settings."},"tools":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tools","description":"Allowed tools; null means all."},"max_duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Duration Seconds","description":"Soft cap per call; null means the workspace limit."},"handover_max_duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Handover Max Duration Seconds","description":"Call limit after a handover contact answers; null means 1800 (30 minutes)."},"voice_enabled":{"type":"boolean","title":"Voice Enabled","description":"Whether the agent answers calls."},"sms_enabled":{"type":"boolean","title":"Sms Enabled","description":"Whether the agent answers texts."},"status":{"type":"string","enum":["live","paused"],"title":"Status","description":"'live' or 'paused'."},"handover_contacts":{"items":{"$ref":"#/components/schemas/HandoverContactOut"},"type":"array","title":"Handover Contacts","description":"People the agent may hand a live call to."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"ISO 8601 timestamp."}},"type":"object","required":["id","organization_id","name","system_prompt","first_message","ai_disclosure","ai_disclosure_line","voice_config","tools","max_duration_seconds","handover_max_duration_seconds","voice_enabled","sms_enabled","status","created_at","updated_at"],"title":"AgentResponse"},"AgentUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name."},"system_prompt":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"System Prompt","description":"New task instructions."},"first_message":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"First Message","description":"New opening line; null makes the agent wait for the other side."},"recording_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Enabled","description":"Null inherits workspace recording; true or false sets it for this agent's calls."},"ai_disclosure":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Disclosure","description":"Whether the AI line is spoken first."},"ai_disclosure_line":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Ai Disclosure Line","description":"New AI line template ('{org}' = organization name); null returns to the workspace line."},"voice_config":{"anyOf":[{"$ref":"#/components/schemas/VoiceConfig"},{"type":"null"}],"description":"New TTS voice and language settings."},"tools":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tools","description":"New allowed tools; null allows every tool."},"max_duration_seconds":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":60.0},{"type":"null"}],"title":"Max Duration Seconds","description":"New soft cap per call; null returns to the workspace limit."},"handover_max_duration_seconds":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":60.0},{"type":"null"}],"title":"Handover Max Duration Seconds","description":"New call limit after a handover contact answers; null returns to 1800 (30 minutes)."},"voice_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Voice Enabled","description":"Whether the agent answers calls."},"sms_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sms Enabled","description":"Whether the agent answers texts."},"status":{"anyOf":[{"type":"string","enum":["live","paused"]},{"type":"null"}],"title":"Status","description":"'live' or 'paused'."},"handover_contacts":{"anyOf":[{"items":{"$ref":"#/components/schemas/HandoverContactIn"},"type":"array","maxItems":10},{"type":"null"}],"title":"Handover Contacts","description":"New handover list; replaces the old one. [] removes all; null leaves it."}},"additionalProperties":false,"type":"object","title":"AgentUpdate","description":"PATCH /agents/{id}. Fields left out keep their value; ``null`` clears\nthe nullable ones (first_message, ai_disclosure_line, tools,\nmax_duration_seconds, handover_max_duration_seconds)."},"Body_upload_email_attachment":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The file to upload, as multipart/form-data. Size-limited; an oversize upload is rejected with 422."}},"type":"object","required":["file"],"title":"Body_upload_email_attachment"},"CallCreate":{"properties":{"recipient_consent":{"type":"boolean","title":"Recipient Consent","description":"Attestation that you have obtained the lawful consent required to contact this recipient. Hail does not verify consent itself — you are responsible for a lawful basis under TCPA/ePrivacy/PECR/CAN-SPAM/GDPR as applicable. Rejected (422) if not true."},"consent_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent Source","description":"Where/how consent was obtained (e.g. 'signup form', 'prior customer relationship'). Required (non-empty) when message_type is 'marketing'."},"consent_obtained_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consent Obtained At","description":"When consent was obtained, if known."},"message_type":{"type":"string","enum":["marketing","informational"],"title":"Message Type","description":"'marketing' additionally requires a non-empty consent_source. Use 'informational' for transactional/service communications.","default":"informational"},"to":{"type":"string","title":"To","description":"Recipient phone number, E.164 format (e.g. +14155551234)."},"from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From","description":"Caller-id phone number, E.164 format. Must be a number owned by the organization with the voice capability. Omitted: an active org-owned number is used if one exists, else a number is claimed from the shared pool."},"system_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System Prompt","description":"Task instructions for the agent, sent as its leading system message. At least one of system_prompt or llm is required; both together is also valid."},"llm":{"anyOf":[{"$ref":"#/components/schemas/LLMConfig"},{"type":"null"}],"description":"BYO LLM endpoint the call runs on instead of Hail's default model. At least one of system_prompt or llm is required; both together is also valid."},"first_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Message","description":"Opening line the agent speaks first. Omitted: the agent waits for the callee to speak first."},"recording_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Enabled","description":"Best-effort audio recording for this call. true records and false does not, whatever the workspace setting. Null inherits the agent, then the workspace. Recording failure never prevents the call. The recording-enabled call rate applies regardless of capture outcome."},"ai_disclosure":{"type":"boolean","title":"Ai Disclosure","description":"Speak the AI self-disclosure line ('Hi, this is an AI assistant calling on behalf of ...') as the first thing on the call. Enabled by default. Disable only if you have verified the disclosure is not required for this call — 47 CFR 64.1200(b)(1) requires identifying the initiating business at the start of artificial-voice calls in the US, and several jurisdictions have AI bot-disclosure laws. Hail does not verify this for you. The agent still identifies itself as an AI if asked.","default":true},"voice_config":{"$ref":"#/components/schemas/VoiceConfig","description":"TTS voice, VAD, turn-detection, and spoken-language settings for this call."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form JSON object attached to the call and echoed back on reads. Not interpreted by Hail. Keys starting with 'recording_' and the key 'failure_code' are reserved and rejected."},"tools":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tools","description":"Agent tools to allow on this call. Omitted: every tool the organization's configured channels support (new channels appear automatically). Empty list: no tools. Tool names are validated against the server's registry."},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Place the call with a saved agent (POST /agents). The agent supplies system_prompt, first_message, ai_disclosure, recording_enabled, voice_config, tools and max duration; any of those given explicitly on this request wins. Satisfies the system_prompt or llm requirement on its own. A paused agent is refused with 409."}},"additionalProperties":false,"type":"object","required":["recipient_consent","to"],"title":"CallCreate"},"CallListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CallResponse"},"type":"array","title":"Items","description":"Calls in this page, newest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"CallListResponse"},"CallResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this call."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that placed or received this call."},"from_e164":{"type":"string","title":"From E164","description":"Caller-id phone number used, E.164 format."},"to_e164":{"type":"string","title":"To E164","description":"Recipient phone number, E.164 format."},"direction":{"type":"string","enum":["outbound","inbound"],"title":"Direction","description":"'outbound' for calls Hail placed, 'inbound' for calls received."},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Agent that answered (inbound) or placed (outbound) this call. Null when the call was placed with an inline system_prompt."},"status":{"type":"string","enum":["queued","dialing","ringing","in_progress","completed","failed","busy","no_answer","canceled"],"title":"Status","description":"Current call-progress state: 'queued', 'dialing', 'ringing', 'in_progress', or one of the terminal states 'completed', 'failed', 'busy', 'no_answer', 'canceled'."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Machine-readable reason the call reached a terminal status (e.g. 'normal_hangup', 'user_rejected', 'sip_trunk_failure'). Null while the call is still in progress."},"provider_call_sid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Call Sid","description":"The telephony provider's identifier for this call leg, if assigned."},"livekit_room":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Livekit Room","description":"Name of the LiveKit room hosting this call's media session, if one was created."},"initial_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Prompt","description":"The system_prompt this call was created with, if any."},"recording_enabled":{"type":"boolean","title":"Recording Enabled","description":"Whether the recording-enabled call rate applies, regardless of capture outcome.","default":false},"recording_status":{"type":"string","title":"Recording Status","description":"disabled, requested, starting, recording, processing, ready, unavailable, failed, or erased.","default":"disabled"},"recording_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recording Duration Ms","description":"Duration of captured audio, independent of billed call duration."},"recording_s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording S3 Key","description":"Internal storage key for the call recording. Not a directly fetchable URL."},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"When the call was requested, ISO 8601 timestamp."},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"When dialing began, ISO 8601 timestamp. Null until the call starts."},"answered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Answered At","description":"When the callee answered, ISO 8601 timestamp. Null if never answered."},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At","description":"When the call ended, ISO 8601 timestamp. Null while still in progress."},"failure_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Message","description":"Human-readable explanation for a failed call. Null for other statuses."}},"type":"object","required":["id","organization_id","from_e164","to_e164","direction","status","end_reason","provider_call_sid","livekit_room","initial_prompt","recording_s3_key","requested_at","started_at","answered_at","ended_at"],"title":"CallResponse"},"ConfirmRequest":{"properties":{"code":{"type":"string","maxLength":10,"minLength":4,"title":"Code","description":"The code the carrier texted to the person's mobile."}},"type":"object","required":["code"],"title":"ConfirmRequest"},"ContactCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Display name for the contact."},"phone_e164":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone E164","description":"Phone number, E.164 format. At least one of phone_e164 or email is required."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email address, stored lowercased. At least one of phone_e164 or email is required."}},"additionalProperties":false,"type":"object","required":["name"],"title":"ContactCreate"},"ContactEntry":{"properties":{"id":{"type":"string","title":"Id","description":"'member:<user_id>' for an org member, or the contact row's UUID (as a string) for a manual contact."},"kind":{"type":"string","enum":["member","manual"],"title":"Kind","description":"'member' if this row is a member of the organization, 'manual' if it was added as a contact."},"name":{"type":"string","title":"Name","description":"Display name."},"phone_e164":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone E164","description":"Phone number, E.164 format. Null if none on file."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email address. Null if none on file."},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Organization role (e.g. 'owner', 'admin', 'member') for kind='member'. Always null for kind='manual'."}},"type":"object","required":["id","kind","name"],"title":"ContactEntry","description":"One row in the computed contacts union — an org member or a manual\ncontact. ``id`` is ``member:<user_id>`` for members, the contact row's\nUUID (as str) for manual rows."},"ContactListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ContactEntry"},"type":"array","title":"Items","description":"Contacts in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"ContactListResponse"},"ContactPatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name. Omit to leave unchanged; cannot be set to null."},"phone_e164":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone E164","description":"New phone number, E.164 format. Omit to leave unchanged; explicit null clears it. The contact must keep at least one of phone_e164 or email."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"New email address, stored lowercased. Omit to leave unchanged; explicit null clears it. The contact must keep at least one of phone_e164 or email."}},"additionalProperties":false,"type":"object","title":"ContactPatch"},"DmarcCheck":{"properties":{"present":{"type":"boolean","title":"Present","description":"True if a DMARC TXT record exists at _dmarc.<domain> or at _dmarc.<organizational domain> — the two names a receiver checks (RFC 7489 section 6.6.3). The organizational domain is one label under the public suffix: acme.co.uk for mail.acme.co.uk."},"suggested":{"anyOf":[{"$ref":"#/components/schemas/DnsRecordSchema"},{"type":"null"}],"description":"A minimal DMARC record to publish, named _dmarc.<zone>. Null when present is true."}},"type":"object","required":["present","suggested"],"title":"DmarcCheck","description":"Whether a DMARC record covers the domain."},"DnsProviderSchema":{"properties":{"id":{"type":"string","title":"Id","description":"Short identifier for the detected DNS host (e.g. 'cloudflare')."},"name":{"type":"string","title":"Name","description":"Human-readable name of the detected DNS host."},"dns_url":{"type":"string","title":"Dns Url","description":"URL to the DNS host's records-management UI."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Host-specific tip for publishing records correctly. Null when there is none."}},"type":"object","required":["id","name","dns_url","note"],"title":"DnsProviderSchema","description":"A DNS host detected from a domain's nameservers, with its records UI."},"DnsRecordSchema":{"properties":{"name":{"type":"string","title":"Name","description":"DNS record name/host to publish (e.g. a CNAME's subdomain)."},"value":{"type":"string","title":"Value","description":"DNS record value to publish (e.g. a CNAME target or TXT content)."},"type":{"type":"string","enum":["CNAME","MX","TXT"],"title":"Type","description":"DNS record type: 'CNAME' (DKIM), 'MX' (MAIL FROM), or 'TXT' (SPF).","default":"CNAME"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"MX priority. Only present for type='MX'; null otherwise."}},"additionalProperties":true,"type":"object","required":["name","value"],"title":"DnsRecordSchema","description":"One DNS record the tenant must publish for a sending domain.\n\nCovers DKIM CNAMEs, MAIL FROM MX, and SPF TXT records."},"DocumentOption":{"properties":{"key":{"type":"string","title":"Key","description":"Value to send as `option` for this document type."},"label":{"type":"string","title":"Label","description":"Label to show the customer, e.g. 'Passport'."},"file_required":{"type":"boolean","title":"File Required","description":"True when the customer must upload a file for this option.","default":true},"fields":{"items":{"$ref":"#/components/schemas/FieldSpec"},"type":"array","title":"Fields","description":"Extra values this document needs. Send them in the slot's `fields`.","default":[]},"copies":{"items":{"type":"string"},"type":"array","title":"Copies","description":"Names of the customer's own fields copied onto this document automatically.","default":[]},"needs_address":{"type":"boolean","title":"Needs Address","description":"True when this document refers to the address the customer gave.","default":false}},"type":"object","required":["key","label"],"title":"DocumentOption","description":"One accepted way to fill a document slot (e.g. passport or ID card)."},"DocumentSlot":{"properties":{"name":{"type":"string","title":"Name","description":"Key for this slot in `documents` and in the `file.<name>` part."},"label":{"type":"string","title":"Label","description":"Label to show the customer, e.g. 'Proof of identity'."},"help":{"type":"string","title":"Help","description":"Help text from the carrier.","default":""},"options":{"items":{"$ref":"#/components/schemas/DocumentOption"},"type":"array","title":"Options","description":"The accepted ways to fill this slot. The customer picks one."},"needs_input":{"type":"boolean","title":"Needs Input","description":"False when there is nothing to ask: one option, no file, no extra\nvalues. The UI hides such a slot; the plug-in still fills it.","readOnly":true}},"type":"object","required":["name","label","options","needs_input"],"title":"DocumentSlot","description":"A required proof (identity, address, registration) with its options."},"DomainCheckResponse":{"properties":{"domain":{"type":"string","title":"Domain","description":"The apex domain that was checked, lowercased."},"in_use":{"type":"boolean","title":"In Use","description":"True if the domain already has MX records — it receives mail elsewhere."},"existing_mx":{"items":{"type":"string"},"type":"array","title":"Existing Mx","description":"MX hostnames currently published for the domain. Empty when in_use is false."},"suggested_domain":{"type":"string","title":"Suggested Domain","description":"Domain to use for a custom sending identity: the apex domain if it is not already receiving mail, or an 'inbox.' subdomain if it is (so setup doesn't collide with existing mail)."}},"type":"object","required":["domain","in_use","existing_mx","suggested_domain"],"title":"DomainCheckResponse"},"EmailAttachmentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this attachment."},"filename":{"type":"string","title":"Filename","description":"Original filename of the attachment."},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the attachment."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Size of the attachment in bytes."},"content_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Id","description":"MIME Content-ID, present when this attachment is referenced inline (cid:) from the HTML body. Null otherwise."},"url":{"type":"string","title":"Url","description":"API endpoint that 302-redirects to a presigned download URL for this attachment."}},"type":"object","required":["id","filename","content_type","size_bytes","url"],"title":"EmailAttachmentResponse","description":"One inbound MIME attachment as exposed to API consumers.\n\n``url`` is the stable Hail API endpoint that 302-redirects to a\npresigned S3 URL on access — see GET /emails/{id}/attachments/{aid}."},"EmailAttachmentUploadResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Reusable attachment id — pass it in EmailCreate.attachment_ids to attach it to a send."},"filename":{"type":"string","title":"Filename","description":"Original filename of the uploaded file."},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the uploaded file."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Size of the uploaded file in bytes."}},"type":"object","required":["id","filename","content_type","size_bytes"],"title":"EmailAttachmentUploadResponse","description":"Returned by POST /email-attachments.\n\n``id`` is reusable across many ``POST /emails`` calls via\n``EmailCreate.attachment_ids`` until Hail garbage-collects it (24h\nif never referenced by a send)."},"EmailCreate":{"properties":{"recipient_consent":{"type":"boolean","title":"Recipient Consent","description":"Attestation that you have obtained the lawful consent required to contact this recipient. Hail does not verify consent itself — you are responsible for a lawful basis under TCPA/ePrivacy/PECR/CAN-SPAM/GDPR as applicable. Rejected (422) if not true."},"consent_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent Source","description":"Where/how consent was obtained (e.g. 'signup form', 'prior customer relationship'). Required (non-empty) when message_type is 'marketing'."},"consent_obtained_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consent Obtained At","description":"When consent was obtained, if known."},"message_type":{"type":"string","enum":["marketing","informational"],"title":"Message Type","description":"'marketing' additionally requires a non-empty consent_source. Use 'informational' for transactional/service communications.","default":"informational"},"from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From","description":"Sender email address. Must be a verified identity on the organization's email domains. Omitted: the org's resolved default sending address, if one exists."},"from_name":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"From Name","description":"Display name for the From: header (e.g. 'Acme Billing'). Omitted: no display name."},"to":{"items":{"type":"string"},"type":"array","minItems":1,"title":"To","description":"Recipient email addresses. At least one required."},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipient email addresses."},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipient email addresses."},"reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To","description":"Reply-To email address. Omitted: replies go to the From address."},"subject":{"type":"string","maxLength":998,"minLength":1,"title":"Subject","description":"Email subject line."},"body_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Text","description":"Plain-text body. Either body_text or body_html (or both) is required. A plain-text-only email cannot be tracked for opens or clicks; include body_html to get those events."},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html","description":"HTML body. Either body_text or body_html (or both) is required. Prefer including body_html: open and click tracking only works for emails with an HTML body. Plain-text-only emails still get sent, delivered, and bounce events, but opens and clicks are never tracked."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form JSON object attached to the email and echoed back on reads. Not interpreted by Hail. The keys 'forwarded_from', 'forward_headers' and 'system_kind' are reserved for Hail's own queue bookkeeping and are rejected."},"attachment_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Attachment Ids","description":"Ids returned by POST /email-attachments to attach to this send. Omitted: no attachments."}},"additionalProperties":false,"type":"object","required":["recipient_consent","to","subject"],"title":"EmailCreate"},"EmailDomainCreate":{"properties":{"kind":{"type":"string","enum":["hail_mail","custom"],"title":"Kind","description":"'hail_mail' for a Hail-hosted address (domain omitted, composed from the prefix fields), or 'custom' to send from your own domain (domain required, prefix fields omitted)."},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"DNS domain to send from (e.g. 'acme.com'). Required for kind='custom'; must be omitted for kind='hail_mail'."},"local_prefix_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix User","description":"User-chosen local-part prefix for a hail_mail address. One organization only may hold it; a taken prefix returns 409. Only valid for kind='hail_mail'. Falls back to HAIL_MAIL_DEFAULT_USER_PREFIX plus a per-organization token if omitted."},"local_prefix_org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix Org","description":"Org-chosen local-part prefix for a hail_mail address. Only valid for kind='hail_mail'. Falls back to a prefix derived from the organization id if omitted."}},"additionalProperties":false,"type":"object","required":["kind"],"title":"EmailDomainCreate","description":"Request body for POST /email-domains.\n\nFor ``kind='hail_mail'`` ``domain`` is omitted; the server composes\nthe full address as ``<local_prefix_user>+<local_prefix_org>@<base>``.\nBoth prefixes are optional in the body and fall back to the\n``HAIL_MAIL_DEFAULT_*_PREFIX`` env vars; the server returns 503 if\nneither is supplied. For ``kind='custom'`` ``domain`` is required and\nthe prefix fields must be omitted."},"EmailDomainDnsCheck":{"properties":{"kind":{"type":"string","enum":["hail_mail","custom"],"title":"Kind","description":"The domain's kind. 'hail_mail' rows run no lookups: the domain is Hail's, so there is nothing for the customer to publish."},"dns_provider":{"anyOf":[{"$ref":"#/components/schemas/DnsProviderSchema"},{"type":"null"}],"description":"The DNS host detected from the domain's nameservers. Null when no known host matched, when the domain's zone could not be resolved, or for kind='hail_mail' rows."},"zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone","description":"The DNS zone apex found for this domain: the nearest name at or above it with NS records, never above the organizational domain (a public suffix such as co.uk is never returned). Null when it could not be resolved, or for kind='hail_mail' rows."},"records":{"items":{"$ref":"#/components/schemas/ObservedDnsRecord"},"type":"array","title":"Records","description":"The domain's dns_records, each annotated with whether Hail currently observes it in public DNS. Empty for kind='hail_mail' rows."},"dmarc":{"$ref":"#/components/schemas/DmarcCheck","description":"Whether DMARC is published for this domain, and a suggested record if not."},"lookup_ok":{"type":"boolean","title":"Lookup Ok","description":"False when the zone or DMARC lookup failed or the check timed out — then dns_provider and zone are null, every record's observed is null, and dmarc is not reliable; none of it means the records are missing, so retry later instead of telling the customer to republish anything. True otherwise, whatever the lookups found. A single record whose own lookup failed has observed: null while lookup_ok stays true. Always true for kind='hail_mail' rows, which run no lookups."}},"type":"object","required":["kind","dns_provider","zone","records","dmarc","lookup_ok"],"title":"EmailDomainDnsCheck","description":"Response for GET /email-domains/{id}/dns-check."},"EmailDomainListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EmailDomainResponse"},"type":"array","title":"Items","description":"Email domains in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."},"default_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default From","description":"The From address used when a send omits 'from'. A verified custom domain wins over the hail-mail address. Null when no such default can be resolved (e.g. multiple verified custom domains, or none that can send yet). Computed across the whole organization, not just this page."}},"type":"object","required":["items"],"title":"EmailDomainListResponse"},"EmailDomainPatch":{"properties":{"local_prefix_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix User","description":"New user local-part prefix. One organization only may hold it; a taken prefix returns 409. Only valid on kind='hail_mail' rows. Omit to leave unchanged."},"local_prefix_org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix Org","description":"New org local-part prefix. Only valid on kind='hail_mail' rows. Omit to leave unchanged."},"inbound_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Inbound Enabled","description":"Whether to accept inbound mail on this domain. Requires forward_to (or an existing one) when true. Omit to leave unchanged."},"forward_to":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Forward To","description":"Email addresses to forward inbound mail to (at most 10). A new address that is not a verified member's login email gets a confirm link and receives nothing until it is clicked — see GET /forward-targets. Omit to leave unchanged."},"forward_rate_per_hour":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Forward Rate Per Hour","description":"Cap on forwarded messages per hour. Omit to leave unchanged."}},"additionalProperties":false,"type":"object","title":"EmailDomainPatch","description":"Body for PATCH /email-domains/{id}.\n\nTwo clusters of mutable fields:\n\n* Hail-mail addressing — the user/org prefix pair (only valid on\n  ``kind='hail_mail'`` rows; the handler returns 422 if a tenant tries\n  to PATCH these on a custom row).\n* Inbound action — ``inbound_enabled`` + ``forward_to`` + the optional\n  ``forward_rate_per_hour`` cap. These apply to either kind, but this\n  milestone routes inbound only to ``hail_mail`` rows; custom-domain\n  inbound (MX delegation) is the next milestone.\n\nEvery field is independently optional so ``PATCH`` semantics work the\nway callers expect: send only what you want to change. The route\nenforces the cross-field rules (the CHECK constraint on the table\nrequires an action when ``inbound_enabled`` is true) — we don't\nre-implement it here because the patch may merge with existing row\nstate to satisfy the invariant."},"EmailDomainResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this email domain."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that owns this domain."},"kind":{"type":"string","enum":["hail_mail","custom"],"title":"Kind","description":"'hail_mail' (Hail-hosted address) or 'custom' (your own domain)."},"domain":{"type":"string","title":"Domain","description":"The DNS domain mail is sent from."},"local_prefix_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix User","description":"User local-part prefix for a hail_mail address. Null for kind='custom'."},"local_prefix_org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Prefix Org","description":"Org local-part prefix for a hail_mail address. Null for kind='custom'."},"verification_status":{"type":"string","enum":["pending","verified","failed"],"title":"Verification Status","description":"'pending' (not yet verified), 'verified' (ready to send), or 'failed'."},"dns_records":{"items":{"$ref":"#/components/schemas/DnsRecordSchema"},"type":"array","title":"Dns Records","description":"DNS records (DKIM, MAIL FROM MX, SPF) the tenant must publish to verify this domain."},"mail_from_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mail From Domain","description":"Custom MAIL FROM domain, if configured. Null when using the provider default."},"mail_from_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mail From Status","description":"Verification status of the custom MAIL FROM domain, if one is configured. Secondary to verification_status."},"provider":{"type":"string","title":"Provider","description":"Email sending provider for this domain (currently always 'ses')."},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When the domain became verified, ISO 8601 timestamp. Null until it is."},"inbound_enabled":{"type":"boolean","title":"Inbound Enabled","description":"Whether this domain accepts and forwards inbound mail.","default":false},"forward_to":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Forward To","description":"Email addresses inbound mail is forwarded to, if inbound is enabled."},"forward_rate_per_hour":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Forward Rate Per Hour","description":"Configured cap on forwarded messages per hour, if set."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this domain was added, ISO 8601 timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this domain was last modified, ISO 8601 timestamp."},"receive_ready":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Receive Ready","description":"True when the domain's published MX points at Hail's inbound host. Only populated by POST /{id}/verify on custom domains; null everywhere else."}},"type":"object","required":["id","organization_id","kind","domain","local_prefix_user","local_prefix_org","verification_status","dns_records","mail_from_domain","provider","verified_at","created_at","updated_at"],"title":"EmailDomainResponse","description":"Read view for an email domain.\n\nThe inbound-action fields (``inbound_enabled``, ``forward_to``,\n``forward_rate_per_hour``) surface what the row has configured for\nincoming mail."},"EmailEventListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EmailEventResponse"},"type":"array","title":"Items","description":"Events for this email, oldest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"EmailEventListResponse"},"EmailEventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this event."},"email_id":{"type":"string","format":"uuid","title":"Email Id","description":"The email this event belongs to."},"kind":{"type":"string","enum":["sent","delivered","delivery_delayed","bounced","complained","rejected","opened","clicked"],"title":"Kind","description":"Event kind: 'sent', 'delivered', 'delivery_delayed', 'bounced', 'complained', 'rejected', 'opened', or 'clicked'. 'opened' and 'clicked' only occur for emails sent with an HTML body; plain-text-only emails never produce them."},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"Event-kind-specific detail, as a free-form JSON object."},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"When this event occurred, ISO 8601 timestamp."}},"type":"object","required":["id","email_id","kind","payload","occurred_at"],"title":"EmailEventResponse"},"EmailListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EmailSummary"},"type":"array","title":"Items","description":"Emails in this page, newest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"EmailListResponse"},"EmailResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this email."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that sent or received this email."},"email_domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Email Domain Id","description":"The sending domain used, if from_address belongs to one of the org's configured domains."},"direction":{"type":"string","enum":["outbound","inbound"],"title":"Direction","description":"'outbound' for emails Hail sent, 'inbound' for emails received.","default":"outbound"},"from_address":{"type":"string","title":"From Address","description":"Sender email address."},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","description":"Display name used on the From: header, when the sender supplied one. Always null on inbound rows."},"to_addresses":{"items":{"type":"string"},"type":"array","title":"To Addresses","description":"Recipient email addresses."},"cc_addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc Addresses","description":"CC recipient email addresses, if any."},"bcc_addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc Addresses","description":"BCC recipient email addresses, if any."},"reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To","description":"Reply-To email address, if set."},"subject":{"type":"string","title":"Subject","description":"Email subject line."},"status":{"type":"string","enum":["queued","sent","delivered","failed","bounced","complained","received"],"title":"Status","description":"Delivery status: 'queued', 'sent', 'delivered', 'failed', 'bounced', 'complained', or 'received' (inbound emails)."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Reason delivery failed or bounced, if applicable. Null on success or while pending."},"provider_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Message Id","description":"The email provider's identifier for this message, if assigned."},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"When the send was requested, ISO 8601 timestamp."},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At","description":"When the message was handed to the provider, ISO 8601 timestamp. Null until sent."},"failed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Failed At","description":"When the send failed, ISO 8601 timestamp. Null unless it failed."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form JSON object attached to the email, as sent on create. Not interpreted by Hail."},"body_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Text","description":"Plain-text body, if any."},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html","description":"HTML body, if any."},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"RFC 5322 Message-ID header. Inbound emails only; null on outbound rows."},"in_reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"In Reply To","description":"RFC 5322 In-Reply-To header. Inbound emails only; null on outbound rows."},"references_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"References Ids","description":"RFC 5322 References header, split into ids. Inbound emails only; null on outbound rows."},"spam_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Spam Verdict","description":"Provider spam-scan verdict (e.g. 'PASS'/'FAIL'). Inbound emails only; null on outbound rows."},"virus_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Virus Verdict","description":"Provider virus-scan verdict (e.g. 'PASS'/'FAIL'). Inbound emails only; null on outbound rows."},"dkim_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dkim Verdict","description":"Provider DKIM-authentication verdict (e.g. 'PASS'/'FAIL'). Inbound emails only; null on outbound rows."},"spf_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Spf Verdict","description":"Provider SPF-authentication verdict (e.g. 'PASS'/'FAIL'). Inbound emails only; null on outbound rows."},"dmarc_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dmarc Verdict","description":"Provider DMARC-authentication verdict (e.g. 'PASS'/'FAIL'). Inbound emails only; null on outbound rows."},"provider_received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Provider Received At","description":"When the provider received this email, ISO 8601 timestamp. Inbound emails only."},"raw_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Url","description":"API endpoint that redirects to the original MIME blob. Inbound emails only; null on outbound rows."},"attachments":{"items":{"$ref":"#/components/schemas/EmailAttachmentResponse"},"type":"array","title":"Attachments","description":"Inbound MIME attachments on this email. Empty on outbound rows.","default":[]},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At","description":"When the most recent delivery event for this email occurred, ISO 8601 timestamp."}},"type":"object","required":["id","organization_id","email_domain_id","from_address","to_addresses","cc_addresses","bcc_addresses","reply_to","subject","status","end_reason","provider_message_id","requested_at","sent_at","failed_at","body_text","body_html"],"title":"EmailResponse"},"EmailStatsBucket":{"properties":{"sent":{"type":"integer","title":"Sent","description":"Emails sent in the window.","default":0},"delivered":{"type":"integer","title":"Delivered","description":"Emails confirmed delivered in the window.","default":0},"delivery_delayed":{"type":"integer","title":"Delivery Delayed","description":"Emails with a delivery-delayed event in the window.","default":0},"bounced":{"type":"integer","title":"Bounced","description":"Emails bounced (soft or hard) in the window.","default":0},"bounced_hard":{"type":"integer","title":"Bounced Hard","description":"Emails hard-bounced in the window. Subset of bounced.","default":0},"complained":{"type":"integer","title":"Complained","description":"Emails that received a spam complaint in the window.","default":0},"rejected":{"type":"integer","title":"Rejected","description":"Emails rejected by the provider before sending, in the window.","default":0},"opened":{"type":"integer","title":"Opened","description":"Total open events in the window, including repeat opens by the same recipient. HTML emails only; plain-text-only emails are never tracked for opens.","default":0},"clicked":{"type":"integer","title":"Clicked","description":"Total click events in the window, including repeat clicks by the same recipient. HTML emails only; plain-text-only emails are never tracked for clicks.","default":0},"unique_opened":{"type":"integer","title":"Unique Opened","description":"Distinct emails opened at least once in the window (HTML emails only).","default":0},"unique_clicked":{"type":"integer","title":"Unique Clicked","description":"Distinct emails clicked at least once in the window (HTML emails only).","default":0},"bucket_start":{"type":"string","format":"date-time","title":"Bucket Start","description":"Start of this bucket, ISO 8601 timestamp."}},"type":"object","required":["bucket_start"],"title":"EmailStatsBucket"},"EmailStatsCounts":{"properties":{"sent":{"type":"integer","title":"Sent","description":"Emails sent in the window.","default":0},"delivered":{"type":"integer","title":"Delivered","description":"Emails confirmed delivered in the window.","default":0},"delivery_delayed":{"type":"integer","title":"Delivery Delayed","description":"Emails with a delivery-delayed event in the window.","default":0},"bounced":{"type":"integer","title":"Bounced","description":"Emails bounced (soft or hard) in the window.","default":0},"bounced_hard":{"type":"integer","title":"Bounced Hard","description":"Emails hard-bounced in the window. Subset of bounced.","default":0},"complained":{"type":"integer","title":"Complained","description":"Emails that received a spam complaint in the window.","default":0},"rejected":{"type":"integer","title":"Rejected","description":"Emails rejected by the provider before sending, in the window.","default":0},"opened":{"type":"integer","title":"Opened","description":"Total open events in the window, including repeat opens by the same recipient. HTML emails only; plain-text-only emails are never tracked for opens.","default":0},"clicked":{"type":"integer","title":"Clicked","description":"Total click events in the window, including repeat clicks by the same recipient. HTML emails only; plain-text-only emails are never tracked for clicks.","default":0},"unique_opened":{"type":"integer","title":"Unique Opened","description":"Distinct emails opened at least once in the window (HTML emails only).","default":0},"unique_clicked":{"type":"integer","title":"Unique Clicked","description":"Distinct emails clicked at least once in the window (HTML emails only).","default":0}},"type":"object","title":"EmailStatsCounts"},"EmailStatsRates":{"properties":{"delivery":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delivery","description":"delivered / sent for the window. Null when sent == 0."},"bounce":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce","description":"bounced_hard / sent for the window. Null when sent == 0."},"complaint":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Complaint","description":"complained / sent for the window. Null when sent == 0."},"open":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open","description":"unique_opened / sent for the window. Null when sent == 0. Only HTML emails can be opened-tracked, so plain-text sends lower this rate."},"click":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Click","description":"unique_clicked / sent for the window. Null when sent == 0. Only HTML emails can be click-tracked, so plain-text sends lower this rate."}},"type":"object","title":"EmailStatsRates","description":"All None when sent == 0 in the window."},"EmailStatsResponse":{"properties":{"from":{"type":"string","format":"date-time","title":"From","description":"Start of the queried window, ISO 8601 timestamp (inclusive)."},"to":{"type":"string","format":"date-time","title":"To","description":"End of the queried window, ISO 8601 timestamp (exclusive)."},"bucket":{"type":"string","enum":["hour","day"],"title":"Bucket","description":"Time-bucket size used for the series."},"totals":{"$ref":"#/components/schemas/EmailStatsCounts","description":"Event counts summed across the whole window."},"rates":{"$ref":"#/components/schemas/EmailStatsRates","description":"Derived rates (delivery, bounce, complaint, open, click) for the whole window. Each individual rate is null when sent == 0."},"series":{"items":{"$ref":"#/components/schemas/EmailStatsBucket"},"type":"array","title":"Series","description":"Per-bucket event counts across the window, in chronological order."}},"type":"object","required":["from","to","bucket","totals","rates","series"],"title":"EmailStatsResponse"},"EmailSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this email."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that sent or received this email."},"email_domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Email Domain Id","description":"The sending domain used, if from_address belongs to one of the org's configured domains."},"direction":{"type":"string","enum":["outbound","inbound"],"title":"Direction","description":"'outbound' for emails Hail sent, 'inbound' for emails received.","default":"outbound"},"from_address":{"type":"string","title":"From Address","description":"Sender email address."},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","description":"Display name used on the From: header, when the sender supplied one. Always null on inbound rows."},"to_addresses":{"items":{"type":"string"},"type":"array","title":"To Addresses","description":"Recipient email addresses."},"cc_addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc Addresses","description":"CC recipient email addresses, if any."},"bcc_addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc Addresses","description":"BCC recipient email addresses, if any."},"reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To","description":"Reply-To email address, if set."},"subject":{"type":"string","title":"Subject","description":"Email subject line."},"status":{"type":"string","enum":["queued","sent","delivered","failed","bounced","complained","received"],"title":"Status","description":"Delivery status: 'queued', 'sent', 'delivered', 'failed', 'bounced', 'complained', or 'received' (inbound emails)."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Reason delivery failed or bounced, if applicable. Null on success or while pending."},"provider_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Message Id","description":"The email provider's identifier for this message, if assigned."},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"When the send was requested, ISO 8601 timestamp."},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At","description":"When the message was handed to the provider, ISO 8601 timestamp. Null until sent."},"failed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Failed At","description":"When the send failed, ISO 8601 timestamp. Null unless it failed."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form JSON object attached to the email, as sent on create. Not interpreted by Hail."}},"type":"object","required":["id","organization_id","email_domain_id","from_address","to_addresses","cc_addresses","bcc_addresses","reply_to","subject","status","end_reason","provider_message_id","requested_at","sent_at","failed_at"],"title":"EmailSummary","description":"Trimmed view for list endpoints — drops the message bodies.\n\nBodies can be large and contain PII; paging through a year of mail\nshouldn't return every byte of every message just to render a list.\nUse ``EmailResponse`` (via ``GET /emails/{id}``) for the full row."},"EventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this event."},"source":{"type":"string","enum":["call","email","sms"],"title":"Source","description":"Which channel this event belongs to: 'call', 'email', or 'sms'."},"call_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Call Id","description":"The call this event belongs to. Set only when source='call'."},"email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Email Id","description":"The email this event belongs to. Set only when source='email'."},"sms_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Sms Id","description":"The message this event belongs to. Set only when source='sms'."},"kind":{"type":"string","title":"Kind","description":"Event kind within the source (e.g. 'queued', 'delivered', 'bounced'). Vocabulary differs per source."},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"Event-kind-specific detail, as a free-form JSON object."},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"When this event occurred, ISO 8601 timestamp."}},"type":"object","required":["id","source","kind","payload","occurred_at"],"title":"EventResponse","description":"One event on the unified GET /events stream (call, email, or SMS)."},"EventStreamResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EventResponse"},"type":"array","title":"Items","description":"Events in this page, oldest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."},"call_status":{"anyOf":[{"type":"string","enum":["queued","dialing","ringing","in_progress","completed","failed","busy","no_answer","canceled"]},{"type":"null"}],"title":"Call Status","description":"Current status of the call named by the id filter (e.g. id=call:<uuid>). Null for org-wide tails and non-call filters, since there is no single call to report a status for."}},"type":"object","required":["items"],"title":"EventStreamResponse"},"FieldOption":{"properties":{"key":{"type":"string","title":"Key","description":"Value to send."},"label":{"type":"string","title":"Label","description":"Label to show the customer."}},"type":"object","required":["key","label"],"title":"FieldOption","description":"One allowed value for a field with a fixed set of answers."},"FieldSpec":{"properties":{"name":{"type":"string","title":"Name","description":"Key to send this value under in `fields`."},"label":{"type":"string","title":"Label","description":"Label to show the customer."},"kind":{"type":"string","enum":["text","email","phone","url","choices"],"title":"Kind","description":"Input kind: 'text', 'email', 'phone', 'url', or 'choices' (pick several of `options`; send their keys joined by commas).","default":"text"},"help":{"type":"string","title":"Help","description":"Help text from the carrier.","default":""},"pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pattern","description":"Regular expression the value must match, when the carrier gives one."},"options":{"anyOf":[{"items":{"$ref":"#/components/schemas/FieldOption"},"type":"array"},{"type":"null"}],"title":"Options","description":"Allowed values, when the field is a choice. Show a select, not a text box."},"required":{"type":"boolean","title":"Required","description":"False when the field is optional.","default":true},"min_choices":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Choices","description":"For kind 'choices': the fewest options to pick."},"max_choices":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Choices","description":"For kind 'choices': the most options to pick."},"max_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Length","description":"The most characters the value may hold, when known, counted in UTF-16 code units (an emoji counts as 2). Enforced: a longer value gets 422. A form can show a field allowed more than about 100 characters as a box that wraps."}},"type":"object","required":["name","label"],"title":"FieldSpec","description":"One value the customer types in."},"ForwardTargetListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ForwardTargetResponse"},"type":"array","title":"Items","description":"Every forward address ever configured for this organization."}},"type":"object","required":["items"],"title":"ForwardTargetListResponse"},"ForwardTargetResponse":{"properties":{"address":{"type":"string","title":"Address","description":"Normalized (lower-cased) email address."},"status":{"type":"string","enum":["pending","verified","stopped"],"title":"Status","description":"'verified' receives forwards. 'pending' was sent a confirm link that is not yet clicked. 'stopped' got a spam complaint and needs a new confirm link (POST /forward-targets/{address}/resend)."},"pending_reason":{"anyOf":[{"type":"string","enum":["confirm_link","account_unverified"]},{"type":"null"}],"title":"Pending Reason","description":"Why a 'pending' address is not live. 'confirm_link': a confirm mail was sent and not yet clicked. 'account_unverified': the address is a member's login email whose account email is not verified yet; it goes live on the account verify click, no confirm mail. Null otherwise."},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When the address was verified, ISO 8601."},"token_sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Sent At","description":"When the latest confirm link was sent, ISO 8601. Null once verified."},"stopped_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Stopped At","description":"When forwarding was stopped, ISO 8601."},"stopped_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stopped Reason","description":"Why forwarding was stopped, e.g. 'complaint'."}},"type":"object","required":["address","status"],"title":"ForwardTargetResponse","description":"One forward address of the organization and whether it may receive\nforwards. Rows come from ``email_forward_targets``; see\n``hailhq.core.forward_targets``."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail","description":"List of validation errors: each entry gives the field location (loc), the problem (msg), and the error type (type)."}},"type":"object","title":"HTTPValidationError"},"HandoverContactIn":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"A person with a phone number, as GET /contacts returns it: a contact's id, or `member:<user id>` for a team member.","examples":["member:6f1c2a9e-3b7d-4c51-9a8e-2d4f6b1e0c37"]},"note":{"type":"string","maxLength":200,"minLength":1,"title":"Note","description":"When the agent should hand over to this person, e.g. 'billing questions'. Read by the agent."}},"additionalProperties":false,"type":"object","required":["contact_id","note"],"title":"HandoverContactIn"},"HandoverContactOut":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"The person: a contact's id, or `member:<user id>` for a team member."},"name":{"type":"string","title":"Name","description":"Name, as the agent says it."},"phone_e164":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone E164","description":"Number Hail dials. Null when the contact lost its number; it is then skipped."},"note":{"type":"string","title":"Note","description":"When the agent hands over to this person."}},"type":"object","required":["contact_id","name","phone_e164","note"],"title":"HandoverContactOut"},"LLMConfig":{"properties":{"base_url":{"type":"string","title":"Base Url","description":"Public HTTPS base URL of the BYO LLM endpoint the call runs on. Must resolve to a public address — private/internal hosts are rejected."},"api_key":{"type":"string","title":"Api Key","description":"API key sent to the BYO LLM endpoint. Write-only — never echoed back."},"model":{"type":"string","title":"Model","description":"Model name to request from the BYO LLM endpoint."}},"additionalProperties":false,"type":"object","required":["base_url","api_key","model"],"title":"LLMConfig"},"MemberPhonePut":{"properties":{"phone_e164":{"type":"string","title":"Phone E164","description":"Phone number to save for the caller, E.164 format."}},"additionalProperties":false,"type":"object","required":["phone_e164"],"title":"MemberPhonePut"},"NumberAcquireRequest":{"properties":{"quote_id":{"type":"string","format":"uuid","title":"Quote Id","description":"Unexpired organization-bound quote from POST /numbers/quotes. Required: without it the request is a 422; get a quote first."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Deprecated and ignored. Hail buys the quoted number from the carrier it picked.","deprecated":true},"country_code":{"type":"string","maxLength":2,"minLength":2,"title":"Country Code","description":"ISO alpha-2 country code to acquire a number in (e.g. 'US'). Case-insensitive."},"number_type":{"type":"string","enum":["local","mobile","toll_free","national"],"title":"Number Type","description":"Kind of number to acquire: 'local', 'mobile', 'toll_free', or 'national'.","default":"local"},"expected_total_cents":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Expected Total Cents","description":"The total the caller expects to pay now: the quoted monthly price plus the setup price, in cents. When set, the order is refused with 409 if the quoted total differs."},"subject_type":{"anyOf":[{"type":"string","enum":["person","business"]},{"type":"null"}],"title":"Subject Type","description":"Buy with an approved verification of this kind only: 'person' (you) or 'business' (your company). With `verification_id` it must match that verification. Ignored when the number needs no verification."},"verification_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Verification Id","description":"Hail id of the approved verification to buy with, from the quote's `verifications`. Needed when you have more than one approved verification for this number (422 without it); with one it is used. 422 when it is not one of your approved verifications for this number. Ignored when the number needs no verification."}},"additionalProperties":false,"type":"object","required":["quote_id","country_code"],"title":"NumberAcquireRequest"},"NumberQuoteRequest":{"properties":{"country_code":{"type":"string","maxLength":2,"minLength":2,"title":"Country Code","description":"ISO alpha-2 country code to search. Case-insensitive."},"number_type":{"anyOf":[{"type":"string","enum":["local","mobile","toll_free","national"]},{"type":"null"}],"title":"Number Type","description":"Restrict number type; omit to compare all supported types."},"capabilities":{"items":{"type":"string","enum":["voice","sms"]},"type":"array","maxItems":2,"minItems":1,"title":"Capabilities","description":"Required channels; every returned offer must support all requested capabilities."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Deprecated and ignored. Hail compares every carrier by readiness, remaining verification effort, and rental and setup costs.","deprecated":true},"area_code":{"anyOf":[{"type":"string","pattern":"^[0-9]{1,6}$"},{"type":"null"}],"title":"Area Code","description":"Area code or first digits of the number, as dialed inside the country: 020 or 20 (London), 480 (Arizona). Any country; every returned offer starts with these digits."},"city":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"City","description":"City name as the carriers list it, such as London or Phoenix. Each offer's locality and region tell same-named cities apart."},"region":{"anyOf":[{"type":"string","pattern":"^[A-Za-z]{2}$"},{"type":"null"}],"title":"Region","description":"Optional, with city: a US state or Canadian province code, such as AZ. GET /numbers/regions lists the codes."}},"additionalProperties":false,"type":"object","required":["country_code","capabilities"],"title":"NumberQuoteRequest"},"NumberQuotesResponse":{"properties":{"offers":{"items":{"$ref":"#/components/schemas/QuoteOffer"},"type":"array","title":"Offers","description":"Live offers ordered by readiness, remaining verification effort, monthly price and setup price."},"recommended_quote_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Recommended Quote Id","description":"Recommended ready offer, or null if none qualifies."},"incomplete":{"type":"boolean","title":"Incomplete","description":"True when some carriers did not answer, so the offers may not be complete. Search again later for a full comparison."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"UTC expiry of these persisted quotes; request fresh offers afterward."}},"type":"object","required":["offers","recommended_quote_id","incomplete","expires_at"],"title":"NumberQuotesResponse"},"NumberRegion":{"properties":{"country_code":{"type":"string","title":"Country Code","description":"ISO alpha-2 country code: US or CA."},"code":{"type":"string","title":"Code","description":"Two-letter state or province code, such as AZ. Send it as region."},"name":{"type":"string","title":"Name","description":"State or province name, such as Arizona."}},"type":"object","required":["country_code","code","name"],"title":"NumberRegion"},"NumberRegionsResponse":{"properties":{"regions":{"items":{"$ref":"#/components/schemas/NumberRegion"},"type":"array","title":"Regions","description":"Every state and province the number search accepts as region."}},"type":"object","required":["regions"],"title":"NumberRegionsResponse"},"ObservedDnsRecord":{"properties":{"name":{"type":"string","title":"Name","description":"DNS record name/host to publish (e.g. a CNAME's subdomain)."},"value":{"type":"string","title":"Value","description":"DNS record value to publish (e.g. a CNAME target or TXT content)."},"type":{"type":"string","enum":["CNAME","MX","TXT"],"title":"Type","description":"DNS record type: 'CNAME' (DKIM), 'MX' (MAIL FROM), or 'TXT' (SPF).","default":"CNAME"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"MX priority. Only present for type='MX'; null otherwise."},"observed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Observed","description":"True if Hail saw this record in public DNS, false if it looked and did not. Null when the lookup for this record failed or lookup_ok is false — null means 'could not check', never 'not published'. This does not change verification_status on the domain — SES stays the authority for that."}},"additionalProperties":true,"type":"object","required":["name","value","observed"],"title":"ObservedDnsRecord","description":"One record from EmailDomainResponse.dns_records, with a live DNS observation."},"OfferVerification":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Hail verification id. Read it with GET /v1/verifications/{verification_id}."},"state":{"type":"string","title":"State","description":"State of that verification, such as draft, submitted or approved."},"subject_type":{"type":"string","enum":["person","business"],"title":"Subject Type","description":"Who that verification is for: 'person' (you) or 'business' (your company)."},"subject_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Name","description":"Name of the person or company that verification is for, as typed in the verification form. Null when unknown."}},"type":"object","required":["id","state","subject_type"],"title":"OfferVerification"},"PhoneNumberListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PhoneNumberResponse"},"type":"array","title":"Items","description":"Numbers in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"PhoneNumberListResponse"},"PhoneNumberResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this number."},"e164":{"type":"string","title":"E164","description":"The phone number, E.164 format."},"country_code":{"type":"string","title":"Country Code","description":"ISO alpha-2 country code this number belongs to."},"number_type":{"type":"string","title":"Number Type","description":"Kind of number: 'local', 'mobile', 'toll_free', or 'national'."},"capabilities":{"items":{"type":"string"},"type":"array","title":"Capabilities","description":"Channels this number supports, e.g. ['voice'], ['sms'], or both."},"provisioning_state":{"type":"string","title":"Provisioning State","description":"'pending', 'active', 'failed', or 'released'."},"is_dedicated":{"type":"boolean","title":"Is Dedicated","description":"True if this number is owned by the organization. False for shared-pool numbers."},"voice_agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Voice Agent Id","description":"Agent that answers calls to this number. Null: calls ring out."},"sms_agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Sms Agent Id","description":"Agent that answers texts to this number. Null: texts only reach your webhooks."},"inbound_registered":{"type":"boolean","title":"Inbound Registered","description":"True while the number is attached for inbound calls at the carrier and on Hail's LiveKit inbound trunk. Set when voice_agent_id is set.","default":false},"subject_type":{"anyOf":[{"type":"string","enum":["person","business"]},{"type":"null"}],"title":"Subject Type","description":"Who the verification used to buy this number is for: 'person' or 'business'. Null when no verification was used."},"verification_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Verification Id","description":"Hail id of the verification used to buy this number. Null when no verification was used."},"subject_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Name","description":"Name of the person or company the verification used to buy this number is for. Null when no verification was used or the name is unknown."}},"type":"object","required":["id","e164","country_code","number_type","capabilities","provisioning_state","is_dedicated"],"title":"PhoneNumberResponse"},"PhoneNumberRoutingUpdate":{"properties":{"voice_agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Voice Agent Id","description":"Agent that answers calls to this number. Setting it registers the number for inbound calls at the carrier and at LiveKit; null unregisters it. The number needs the voice capability."},"sms_agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Sms Agent Id","description":"Agent that answers texts to this number. The number needs the sms capability. Null: texts only reach your webhooks."}},"additionalProperties":false,"type":"object","title":"PhoneNumberRoutingUpdate","description":"PATCH /numbers/{id}: which agent answers. A field left out keeps its\nvalue; ``null`` detaches."},"ProviderActivateRequest":{"properties":{"provider":{"type":"string","title":"Provider","description":"Previously saved provider to make active for this layer."}},"additionalProperties":false,"type":"object","required":["provider"],"title":"ProviderActivateRequest","description":"Body of ``POST /providers/{layer}/activate``."},"ProviderConfigEntry":{"properties":{"layer":{"type":"string","enum":["llm","tts","stt"],"title":"Layer","description":"Voice-pipeline layer this config applies to."},"provider":{"type":"string","title":"Provider","description":"Provider name (e.g. 'openai', 'cartesia')."},"key_last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Last4","description":"Last 4 characters of the saved API key, for display. Null if no key is saved."},"key_set_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Set At","description":"When the API key was last set, ISO 8601 timestamp. Null if no key is saved."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Saved provider-specific config for this row."},"fallback_enabled":{"type":"boolean","title":"Fallback Enabled","description":"Whether Hail's default provider is used as a fallback if this one fails."},"is_active":{"type":"boolean","title":"Is Active","description":"True if this is the row currently used by calls on this layer."}},"type":"object","required":["layer","provider","key_last4","key_set_at","params","fallback_enabled","is_active"],"title":"ProviderConfigEntry","description":"One saved provider row. Write-only key: ``key_last4`` and\n``key_set_at`` are the only key-derived fields that leave the API.\n\nEvery field is required (``key_last4``/``key_set_at`` nullable): the\nserializer always emits all of them, and required response fields\ngenerate plain values instead of pointers in the Go CLI's client."},"ProviderConfigListResponse":{"properties":{"providers":{"items":{"$ref":"#/components/schemas/ProviderConfigEntry"},"type":"array","title":"Providers","description":"Every saved provider row for the organization, across all layers."}},"type":"object","required":["providers"],"title":"ProviderConfigListResponse"},"ProviderConfigUpsert":{"properties":{"provider":{"type":"string","title":"Provider","description":"Provider name to save/activate for this layer (e.g. 'openai', 'cartesia')."},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key","description":"API key for this provider. Omit to edit params without resending the key. Write-only — never echoed back."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Provider-specific config, validated against the layer's schema (LLMParams/TTSParams/STTParams). Only the keys you send are changed; other saved keys are kept."},"fallback_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Fallback Enabled","description":"Whether to fall back to Hail's default provider on failure. Omit to leave unchanged; defaults to false on a new row."}},"additionalProperties":false,"type":"object","required":["provider"],"title":"ProviderConfigUpsert","description":"Body of ``PUT /providers/{layer}`` — save and activate one provider.\n\nA partial write. Fields you omit keep the value already saved for this\n``(layer, provider)`` pair: omit ``api_key`` to edit params without\nresending the key, send only the ``params`` keys you want to change,\nand omit ``fallback_enabled`` to leave the flag as it is. The merged\nresult is what gets validated against the layer's schema (422 on a\nmismatch), so a partial write can never leave an invalid config behind.\n\nRows are keyed by ``(organization, layer, provider)``, so writing a\n*different* provider for the same layer starts from scratch rather than\ninheriting the previous provider's params. On a brand-new row\n``fallback_enabled`` defaults to ``false``.\n\nKeys are write-only: no response ever echoes one back."},"ProviderValidateRequest":{"properties":{"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key","description":"Key to test instead of the stored one. Not persisted."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Provider to test. Omitted: the layer's currently active provider."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Provider-specific config to test alongside the key."}},"additionalProperties":false,"type":"object","title":"ProviderValidateRequest","description":"Body of ``POST /providers/{layer}/validate`` — a live key probe.\n\nAll fields optional: with an empty body the layer's active provider and\nits stored key are tested. ``provider`` tests that provider's stored key\ninstead. ``api_key`` tests a key that has not been saved yet."},"ProviderValidateResult":{"properties":{"status":{"type":"string","title":"Status","description":"Probe outcome: 'valid', 'invalid', or 'indeterminate' (the provider could not be reached)."},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Human-readable detail about the outcome. 'ok' on success, an error description otherwise."}},"type":"object","required":["status","message"],"title":"ProviderValidateResult","description":"Outcome of a live provider-key probe."},"QuoteOffer":{"properties":{"e164":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"E164","description":"Available phone number in E.164 format. Null for a by-request offer: support picks the number when it orders it."},"country_code":{"type":"string","title":"Country Code","description":"ISO alpha-2 country code of the number."},"number_type":{"type":"string","enum":["local","mobile","toll_free","national"],"title":"Number Type","description":"Local, mobile, national, or toll-free number type."},"capabilities":{"items":{"type":"string"},"type":"array","title":"Capabilities","description":"Voice/SMS capabilities reported by live carrier inventory; SMS registration may still be required."},"monthly_cents":{"type":"integer","minimum":1.0,"title":"Monthly Cents","description":"Monthly number rental in USD cents, charged from organization credits."},"setup_cents":{"type":"integer","minimum":0.0,"title":"Setup Cents","description":"One-time setup charge in USD cents, payable with the first month."},"currency":{"type":"string","const":"USD","title":"Currency","description":"Currency of the quoted rental and setup amounts.","default":"USD"},"readiness":{"type":"string","enum":["ready","verification_required"],"title":"Readiness","description":"Whether regulatory preflight permits purchase for this organization; rechecked at purchase."},"regulatory_friction":{"type":"string","enum":["none","information","documents","unknown"],"title":"Regulatory Friction","description":"Remaining verification effort derived from live requirements: none, information/address entry, document uploads, or unknown. This does not establish legal eligibility.","default":"unknown"},"requirements":{"items":{"type":"string"},"type":"array","title":"Requirements","description":"Carrier regulatory requirement labels associated with this offer."},"locality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locality","description":"City or area the carrier lists for this number, such as Phoenix or London; null when unknown."},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"State, province or region the carrier lists, such as AZ or England; null when unknown."},"quote_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Quote Id","description":"Organization-bound quote identifier to pass to POST /numbers before expiry."},"subject_types":{"items":{"type":"string","enum":["person","business"]},"type":"array","title":"Subject Types","description":"Who can verify for this number: 'person' (you), 'business' (your company), or both. Empty when no verification is needed."},"verifications":{"items":{"$ref":"#/components/schemas/OfferVerification"},"type":"array","title":"Verifications","description":"The verifications you can buy this offer with, approved first, then newest first: yours of a subject type in `subject_types` that are under review, and the approved ones the carrier still accepts for this offer. Once one is approved, pass its `id` as `verification_id` to POST /numbers to buy with it."},"verification":{"anyOf":[{"$ref":"#/components/schemas/OfferVerification"},{"type":"null"}],"description":"Deprecated: read `verifications`. The verification a purchase without verification_id would use: the one approved verification in `verifications`, else the newest one there; null when there is none."},"by_request":{"type":"boolean","title":"By Request","description":"True when support orders this number by hand: it cannot be bought with POST /numbers. Ask support for it. Its quote_id still works for verification.","default":false}},"type":"object","required":["e164","country_code","number_type","capabilities","monthly_cents","setup_cents","readiness"],"title":"QuoteOffer","description":"One number offer as the public API shows it. It never names the\ncarrier or carries a carrier-issued id: Hail picks the carrier."},"SenderIdPatch":{"properties":{"custom_sender_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Sender Id","description":"Alphanumeric sender id (2-11 characters, letters/digits only) to use on alphanumeric-eligible corridors instead of a phone number. Explicit null clears it, reverting to the platform default."}},"additionalProperties":false,"type":"object","title":"SenderIdPatch"},"SenderIdResponse":{"properties":{"custom_sender_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Sender Id","description":"The organization's configured alphanumeric sender id. Null if none is set."},"effective_default":{"type":"string","title":"Effective Default","description":"The platform's alphanumeric sender id, used on eligible corridors when custom_sender_id is null.","default":"HAIL"}},"type":"object","required":["custom_sender_id"],"title":"SenderIdResponse"},"SmsCreate":{"properties":{"recipient_consent":{"type":"boolean","title":"Recipient Consent","description":"Attestation that you have obtained the lawful consent required to contact this recipient. Hail does not verify consent itself — you are responsible for a lawful basis under TCPA/ePrivacy/PECR/CAN-SPAM/GDPR as applicable. Rejected (422) if not true."},"consent_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent Source","description":"Where/how consent was obtained (e.g. 'signup form', 'prior customer relationship'). Required (non-empty) when message_type is 'marketing'."},"consent_obtained_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consent Obtained At","description":"When consent was obtained, if known."},"message_type":{"type":"string","enum":["marketing","informational"],"title":"Message Type","description":"'marketing' additionally requires a non-empty consent_source. Use 'informational' for transactional/service communications.","default":"informational"},"to":{"type":"string","title":"To","description":"Recipient phone number, E.164 format (e.g. +14155551234)."},"from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From","description":"Sender phone number, E.164 format. Must be an active number owned by the organization with the SMS capability. Omitted: destinations where the sender-ID carrier delivers a dynamic alphanumeric sender ID unchanged (about 110 countries: most of Europe, Africa, the Middle East, Asia-Pacific and the Caribbean; one table per carrier in core/hailhq/core/sender_id_corridors.py) use the organization's sender ID, or the platform default 'HAIL' when none is set; Australia (+61) may always use 'HAIL', depending on the carrier. Every other destination, including the US and Canada, uses the organization's oldest active SMS-capable number; if none exists the request fails with 422. SMS never uses the shared pool."},"body":{"type":"string","maxLength":1600,"minLength":1,"title":"Body","description":"Message text. Long bodies are split into multiple carrier segments."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Free-form JSON object attached to the message and echoed back on reads. Not interpreted by Hail."}},"additionalProperties":false,"type":"object","required":["recipient_consent","to","body"],"title":"SmsCreate"},"SmsListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SmsResponse"},"type":"array","title":"Items","description":"Messages in this page, newest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"SmsListResponse"},"SmsResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this message."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that sent or received this message."},"from_e164":{"type":"string","title":"From E164","description":"Sender phone number, E.164 format."},"to_e164":{"type":"string","title":"To E164","description":"Recipient phone number, E.164 format."},"direction":{"type":"string","enum":["outbound","inbound"],"title":"Direction","description":"'outbound' for messages Hail sent, 'inbound' for messages received."},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"The agent this text belongs to: the number's text agent on inbound texts, the agent that wrote it on outbound texts. Null when no agent is involved."},"status":{"type":"string","enum":["queued","sent","delivered","failed","undelivered","received"],"title":"Status","description":"Delivery status: 'queued', 'sent', 'delivered', 'failed', 'undelivered', or 'received' (inbound messages)."},"body":{"type":"string","title":"Body","description":"Message text."},"provider_message_sid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Message Sid","description":"The carrier/provider's identifier for this message, if assigned."},"segment_count":{"type":"integer","title":"Segment Count","description":"Number of carrier SMS segments the body was split into."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Carrier error code if delivery failed. Null on success or while pending."},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"When the send was requested, ISO 8601 timestamp."},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At","description":"When the message was handed to the carrier, ISO 8601 timestamp. Null until sent."}},"type":"object","required":["id","organization_id","from_e164","to_e164","direction","status","body","provider_message_sid","segment_count","error_code","requested_at","sent_at"],"title":"SmsResponse"},"SuppressionListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SuppressionResponse"},"type":"array","title":"Items","description":"Suppressed recipients in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"SuppressionListResponse"},"SuppressionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this suppression entry."},"recipient":{"type":"string","title":"Recipient","description":"The suppressed recipient — E.164 phone number for voice/sms, lowercased email address for email."},"channel":{"type":"string","title":"Channel","description":"Channel this entry blocks sends on: 'voice', 'email', 'sms', or 'all' (every channel)."},"reason":{"type":"string","title":"Reason","description":"Why the recipient was suppressed (e.g. an unsubscribe or a bounce)."},"source":{"type":"string","title":"Source","description":"How this entry was created: 'unsubscribe_link' (email unsubscribe link) or 'stop_keyword' (recipient replied STOP by SMS). 'manual' (operator action) and 'bounce' (bounce handler) are reserved; nothing writes them yet."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this entry was created, ISO 8601 timestamp."}},"type":"object","required":["id","recipient","channel","reason","source","created_at"],"title":"SuppressionResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location","description":"Path to the invalid field within the request, as a list of keys/indices (e.g. ['body', 'to'])."},"msg":{"type":"string","title":"Message","description":"Human-readable description of the validation failure."},"type":{"type":"string","title":"Error Type","description":"Machine-readable error type code (e.g. 'missing', 'string_type')."},"input":{"title":"Input","description":"The value that was actually provided and failed validation."},"ctx":{"type":"object","title":"Context","description":"Additional machine-readable context for the error, when the error type provides one."}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerificationRequirements":{"properties":{"country_code":{"type":"string","title":"Country Code","description":"ISO alpha-2 country code."},"number_type":{"type":"string","title":"Number Type","description":"'local', 'mobile', 'toll_free', or 'national'."},"subject_type":{"type":"string","enum":["person","business"],"title":"Subject Type","description":"'person' or 'business'."},"required":{"type":"boolean","title":"Required","description":"False when the carrier needs no verification for this combination."},"fields":{"items":{"$ref":"#/components/schemas/FieldSpec"},"type":"array","title":"Fields","description":"Values to collect about the person or business.","default":[]},"documents":{"items":{"$ref":"#/components/schemas/DocumentSlot"},"type":"array","title":"Documents","description":"Documents to collect. Skip slots with `needs_input` false.","default":[]},"address_required":{"type":"boolean","title":"Address Required","description":"True when an address must be collected.","default":false},"subject_types":{"items":{"type":"string","enum":["person","business"]},"type":"array","title":"Subject Types","description":"Subject types the carrier accepts here, so you can offer the choice.","default":[]}},"type":"object","required":["country_code","number_type","subject_type","required"],"title":"VerificationRequirements","description":"What the carrier needs for one (country, number type, subject type),\nas the public API shows it: without the carrier's name."},"VerificationResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this verification."},"country_code":{"type":"string","title":"Country Code","description":"ISO alpha-2 country code."},"number_type":{"type":"string","title":"Number Type","description":"Kind of number: 'local', 'mobile', 'toll_free', or 'national'."},"subject_type":{"type":"string","title":"Subject Type","description":"'person' or 'business'."},"subject_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Name","description":"Name of the person or company this verification is for, taken from the form (for example the business name, or the first and last name). Null when unknown."},"purpose":{"type":"string","title":"Purpose","description":"'number': permission to buy a number in this country. 'sms': registration to text US numbers from a US number."},"state":{"type":"string","title":"State","description":"'awaiting_review', 'submitting', 'submitted', 'approved', 'rejected', or 'cancelled'."},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason","description":"Why it was rejected. Null unless rejected."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the verification was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When it last changed."},"submitted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Submitted At","description":"When it was submitted to the carrier."},"approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Approved At","description":"When the carrier approved it."},"confirmation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation","description":"For a person's US texting registration: 'code' when you must enter the code the carrier texted (POST /verifications/{id}/confirm), 'reply' when you must answer the carrier's text, null when nothing is pending."},"step":{"anyOf":[{"type":"string","enum":["confirm_identity","filing","carrier_review"]},{"type":"null"}],"title":"Step","description":"For a US texting registration that is not yet approved or rejected: 'confirm_identity' while the person must confirm (see `confirmation`), 'filing' while Hail files the details with the carriers, 'carrier_review' once the carriers are reviewing them (1 to 7 business days). Null otherwise."},"identity_confirmed":{"type":"boolean","title":"Identity Confirmed","description":"True on a person's newest US texting registration when it is cancelled, or rejected and not yet dismissed, and the person stays confirmed: the next filing with the same first name, last name, email and mobile needs no code and has no submission fee. Dismiss a rejected registration first. Ignore it on older registrations.","default":false}},"type":"object","required":["id","country_code","number_type","subject_type","purpose","state","created_at","updated_at"],"title":"VerificationResponse","description":"A carrier verification: its state and the name of the person or\ncompany it is for. No other personal data."},"VoiceConfig":{"properties":{"tts":{"type":"string","const":"cartesia","title":"Tts","description":"Text-to-speech provider. Currently only 'cartesia'.","default":"cartesia"},"vad":{"type":"string","const":"silero","title":"Vad","description":"Voice-activity-detection engine. Currently only 'silero'.","default":"silero"},"turn_detection":{"type":"string","const":"livekit","title":"Turn Detection","description":"Turn-detection engine. Currently only 'livekit'.","default":"livekit"},"voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Id","description":"Per-call TTS voice override, applied to whichever TTS provider serves the call. Omitted: the organization's or environment's default voice."},"language":{"anyOf":[{"type":"string","enum":["ar","bg","bn","cs","da","de","el","en","es","fi","fr","gu","he","hi","hr","hu","id","it","ja","kn","ko","mr","ms","nl","no","pl","pt","ro","ru","sk","sv","ta","te","th","tl","tr","uk","vi","zh"]},{"type":"null"}],"title":"Language","description":"Spoken language for the call as a lowercase ISO 639-1 code (e.g. 'da'). One of the 39 supported codes — see docs/languages.md. Applied to STT, TTS, and turn detection. Omitted: the providers' defaults (English)."}},"additionalProperties":false,"type":"object","title":"VoiceConfig"},"WebhookDeliveryListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDeliveryResponse"},"type":"array","title":"Items","description":"Delivery attempts in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"WebhookDeliveryListResponse"},"WebhookDeliveryResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this delivery attempt."},"subscription_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Subscription Id","description":"Subscription this delivery belongs to."},"email_domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Email Domain Id","description":"Email domain the triggering event relates to, if any. Informational only (surfaced as the X-Hail-Email-Domain header) — not a routing target."},"event_type":{"type":"string","title":"Event Type","description":"The event type being delivered (e.g. 'call.completed')."},"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"Identifier of the underlying event that triggered this delivery."},"attempt":{"type":"integer","title":"Attempt","description":"Number of delivery attempts made so far for this event, starting at 0."},"status":{"type":"string","enum":["pending","succeeded","failed","dead"],"title":"Status","description":"'pending' (queued/retrying), 'succeeded', 'failed' (will retry), or 'dead' (retries exhausted)."},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Status","description":"HTTP status code returned by the target URL on the last attempt. Null before any attempt."},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body","description":"Response body returned by the target URL on the last attempt, if any. Null before any attempt."},"next_attempt_at":{"type":"string","format":"date-time","title":"Next Attempt At","description":"When the next delivery attempt is scheduled, ISO 8601 timestamp."},"succeeded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Succeeded At","description":"When this delivery succeeded, ISO 8601 timestamp. Null until it does."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this delivery was queued, ISO 8601 timestamp."}},"type":"object","required":["id","subscription_id","email_domain_id","event_type","event_id","attempt","status","next_attempt_at","created_at"],"title":"WebhookDeliveryResponse"},"WebhookEventCatalogResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookEventDefinition"},"type":"array","title":"Items","description":"Supported webhook events."}},"type":"object","required":["items"],"title":"WebhookEventCatalogResponse"},"WebhookEventDefinition":{"properties":{"type":{"$ref":"#/components/schemas/WebhookEventType","description":"Event identifier used in subscriptions."},"label":{"type":"string","title":"Label","description":"Short display label for this event."},"channel":{"type":"string","title":"Channel","description":"Display group, such as Email, Calls, or SMS."}},"type":"object","required":["type","label","channel"],"title":"WebhookEventDefinition"},"WebhookEventType":{"type":"string","enum":["email.received","email.delivered","email.delivery_delayed","email.bounced","email.complained","email.opened","email.clicked","email.received.suppressed","email.send_failed","sms.received","sms.delivered","sms.undelivered","sms.failed","call.received","call.answered","call.completed","call.failed","call.busy","call.no_answer","call.transferred"],"title":"WebhookEventType"},"WebhookSubscriptionCreate":{"properties":{"target_url":{"type":"string","minLength":1,"title":"Target Url","description":"HTTPS URL Hail POSTs event payloads to."},"event_types":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array","minItems":1,"title":"Event Types","description":"Event types to subscribe to (e.g. 'call.completed', 'email.bounced'). At least one required."}},"type":"object","required":["target_url","event_types"],"title":"WebhookSubscriptionCreate"},"WebhookSubscriptionListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"},"type":"array","title":"Items","description":"Subscriptions in this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page. Null when there are no more results."}},"type":"object","required":["items"],"title":"WebhookSubscriptionListResponse"},"WebhookSubscriptionPatch":{"properties":{"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url","description":"New delivery URL. Omit to leave unchanged."},"event_types":{"anyOf":[{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},{"type":"null"}],"title":"Event Types","description":"New set of subscribed event types. Omit to leave unchanged."},"status":{"anyOf":[{"type":"string","enum":["active","disabled"]},{"type":"null"}],"title":"Status","description":"Set to 'disabled' to pause deliveries, or 'active' to resume. Omit to leave unchanged."}},"type":"object","title":"WebhookSubscriptionPatch"},"WebhookSubscriptionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for this subscription."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization that owns this subscription."},"target_url":{"type":"string","title":"Target Url","description":"HTTPS URL event payloads are POSTed to."},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Event types this subscription receives."},"status":{"type":"string","enum":["active","disabled"],"title":"Status","description":"'active' (delivering) or 'disabled' (paused)."},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Consecutive failed delivery attempts since the last success. Resets to 0 on success."},"last_success_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Success At","description":"When a delivery last succeeded, ISO 8601 timestamp. Null if never."},"last_failure_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Failure At","description":"When a delivery last failed, ISO 8601 timestamp. Null if never."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this subscription was created, ISO 8601 timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this subscription was last modified, ISO 8601 timestamp."},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"Plaintext signing secret for verifying delivery payloads. Only present in the create and rotate-secret responses; every later read returns null."}},"type":"object","required":["id","organization_id","target_url","event_types","status","consecutive_failures","created_at","updated_at"],"title":"WebhookSubscriptionResponse","description":"Subscription as returned by the API.\n\n``secret`` is populated **only** by create + rotate-secret responses;\nlater GETs return ``None`` so the plaintext never round-trips."},"WhoamiResponse":{"properties":{"auth_kind":{"type":"string","enum":["apikey","jwt","shared"],"title":"Auth Kind","description":"How the caller authenticated: 'apikey' (org API key), 'jwt' (logged-in user session), or 'shared' (the shared HAIL_API_KEY, which carries no human identity)."},"organization_id":{"type":"string","format":"uuid","title":"Organization Id","description":"Organization the caller belongs to."},"user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id","description":"The authenticated user's id. Null for 'shared' callers."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"The authenticated user's email. Null for 'shared' callers."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The authenticated user's display name. Null for 'shared' callers."},"superadmin":{"type":"boolean","title":"Superadmin","description":"True for a Hail staff console session acting on this organization.","default":false}},"type":"object","required":["auth_kind","organization_id"],"title":"WhoamiResponse","description":"Who the caller is — the answer ``GET /whoami`` gives.\n\n``user_id``/``email``/``name`` are ``None`` for shared-key\n(``HAIL_API_KEY``) callers: that key carries no human identity. An\nagent that wants to put the operator's address in ``Reply-To`` reads\n``email`` and skips the header when it is ``None``."}}}}