openapi: 3.1.0 info: title: Hail description: "Give your AI agent a voice, a real phone number, and an inbox.\n\n\ This file is the source of truth for the Go CLI. Regenerate it after\nchanging\ \ API routes \u2014 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 place 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/{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 first_message, ai_disclosure_line, tools, max_duration_seconds or handover_max_duration_seconds. Live calls keep the settings they started with. ``handover_contacts`` replaces the 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 for inbound and ring out again; numbers that routed texts to it go back to 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\ \ \u2014 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. Cursor-paginated: pass the returned next_cursor to fetch the next page; a null next_cursor means there are no more results. Filter by status or destination 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. Org-scoped: returns 404 for a call belonging to a different organization (not 403, to avoid confirming the id exists). Use this to poll for the final 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. The returned id can be referenced from attachment_ids on many later POST /v1/emails calls until it is garbage-collected for being unused; it is not deleted immediately after first use. Uploads are size-limited and scoped 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/uploads: post: tags: - uploads summary: Create File Upload description: "Make a one-time link to upload one file with a plain HTTP PUT. For callers whose shell has no Hail key (an agent using the MCP). Send the raw bytes to `upload_url` with `PUT` and no Authorization header, once, before `expires_at` (15 minutes): ``` curl -sS -X PUT --data-binary @passport.jpg '' ``` The PUT answers with `size_bytes` and `sha256` of what was stored. An organization may hold at most 50 unused, unexpired links." operationId: create_file_upload 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/FileUploadCreate" responses: "201": description: Successful Response content: application/json: schema: $ref: "#/components/schemas/FileUploadLinkResponse" "429": description: "Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds). Or: the organization already holds 50 unused, unexpired upload links. Use or let some expire." 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 "503": description: File uploads are not configured on this server. "422": description: Validation Error content: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" /v1/uploads/{upload_id}: put: tags: - uploads summary: Put File Upload description: "Send the file's bytes to an upload link. No Authorization header. Works once. A link already expired when the PUT starts is 410. The body must arrive before the link expires (and within 10 minutes), or 408. Storing it may end after the expiry, up to 15 minutes later. Unknown id, wrong token, a used link, and a file that could not be stored at the end are all 404 with one message: make a new link if the file is not stored. A second upload of the same file is harmless. Empty body: 422. A verification document whose first bytes are not the declared JPEG, PNG or PDF: 422." operationId: put_file_upload parameters: - name: upload_id in: path required: true schema: type: string format: uuid title: Upload Id - name: token in: query required: true schema: type: string description: The token from `upload_url`. The only credential. title: Token description: The token from `upload_url`. The only credential. responses: "200": description: Successful Response content: application/json: schema: $ref: "#/components/schemas/FileUploadResponse" "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: "Unknown link, wrong token, or already used. Also when the file could not be stored at the end: another PUT used the link first, the link was removed, or storing ended more than 15 minutes after the expiry. If your file is not stored, make a new link and send it again." "408": description: The body did not arrive before the link expired, or within 10 minutes. The link is not used up. "410": description: The link had expired when the PUT started. Make a new link. "413": description: The file is larger than the link's `max_bytes`. "503": description: File uploads are not configured on this server, storage failed, or too many uploads are running, in all or for this organization (`Retry-After`). The link is not used up. "422": description: Validation Error content: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" security: [] requestBody: required: true description: "The raw file bytes. The `Content-Type` header is ignored: the type given when the link was made is stored." content: application/octet-stream: schema: type: string format: binary /v1/emails: post: tags: - emails summary: Create Email description: "Send an outbound email through SES. Sends synchronously. A 201 means SES accepted the message and the response status is 'sent'; no separate poll is needed for the happy path, though a bounce or complaint can still arrive later as a webhook or GET /v1/emails/{email_id}/events entry. If the send fails, the response is 502 'email send failed' with no email body; the email is kept with status 'failed' (GET /v1/emails?status=failed) and an email.send_failed event is emitted. Requires recipient_consent=true on the request body; Hail does not 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 \u2014\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). Cursor-paginated with the same forward-walk shape as ``GET /events``: strictly-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. Defaults to the last 7 days, bucketed by day. bucket=hour is limited to an 8-day range; any bucket size is limited to a 92-day range. Registered above 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. Cursor-paginated. A suppressed address blocks POST /v1/emails sends to it with 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. The address match ignores case. Does not itself constitute renewed consent: the caller is responsible for having a lawful basis (e.g. a fresh opt-in) before sending again. Returns 404 if the address was not suppressed." 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. Org-scoped: returns 404 for an email belonging to a different organization. For an inbound email with a stored raw MIME, raw_url points 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 \u2192 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 \u2192 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 \":\",\n\ e.g. \"call:\") to narrow to one resource's events, or kind to\nnarrow\ \ to one event kind. Walks forward in time \u2014 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\n\ kind=\"hail_mail\" mints an address on the shared hail-mail domain and is\n\ immediately verified \u2014 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\ \ \u2014 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. Org-scoped: returns 404 for a domain belonging to a different organization. For a pending custom domain, dns_records lists the DKIM records 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 \u2014\ \ 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 \u2014 runs live lookups against public DNS (no resolver\n\ dependency) 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 \u2014 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 \u2014 there is no background poller in v1. Operators /\n\ tenants 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. An address appears here once it was ever put in a domain's ``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. Use this when the first mail got lost, or to restart forwarding after a spam 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\n\ This purchases a real number at the carrier and starts a recurring\nmonthly\ \ fee immediately \u2014 it is not a reservation. quote_id is required:\n\ request 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, plus the credit the verification you buy with set aside, doesn't cover this number's setup and the first month. "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\n\ Cursor-paginated, newest first. Only org-owned numbers are listed \u2014\n\ shared 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 release month; months already accrued stay owed (the rater bills late, never forgives). A number that answered calls is taken off the inbound trunk 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. Setting ``voice_agent_id`` registers the number for inbound calls at the carrier and on Hail's LiveKit inbound trunk; ``null`` unregisters it, and calls ring out again. ``sms_agent_id`` needs the sms capability and sets the number up for SMS at the carrier if that has not happened yet. A field left out 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. Org-scoped: returns 404 for a number belonging to a different organization. Read-only: a pending order is advanced by the background reconciler, 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 ``POST /numbers/quotes``. In the US and Canada a city search needs one: city 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/options: get: tags: - numbers summary: Get Number Options description: "The number types sold in a country, one row each. For a person and for a business it gives the best option: price, capabilities, the papers needed, and whether this organization already has them verified. Reads the bundled catalog and your verifications. It calls no carrier, so prices are catalog prices and the live quote may differ slightly." operationId: get_number_options_v1_numbers_options_get parameters: - name: country_code in: query required: true schema: type: string minLength: 2 maxLength: 2 pattern: ^[A-Za-z]{2}$ description: ISO alpha-2 country code, such as CH. title: Country Code description: ISO alpha-2 country code, such as CH. - name: capabilities in: query required: false schema: anyOf: - type: array items: enum: - voice - sms type: string maxItems: 2 - type: "null" description: "Optional. Keep only options that do all these jobs. Without it, each number type shows its best option in this order: buy now before order by hand, then ready now, then options that do every job the type offers (even when a number that does fewer jobs costs less), then lower price, then fewer papers." title: Capabilities description: "Optional. Keep only options that do all these jobs. Without it, each number type shows its best option in this order: buy now before order by hand, then ready now, then options that do every job the type offers (even when a number that does fewer jobs costs less), then lower price, then fewer papers." - 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/NumberOptionsResponse" "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 \u2014 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. Recommends a ready offer with the lowest monthly rent, then setup cost. Blocked offers sort by verification effort. SMS capability does not waive messaging registration requirements. `provider` is ignored: Hail compares every carrier. A number support orders by hand comes back as an offer with `by_request: true` and no `e164`, after the offers you can buy. It is never recommended and POST /numbers refuses it: ask support. Its `quote_id` works for GET /verifications/requirements and POST /verifications. Without `area_code` or `city` only. Each offer lists `subject_types` (who may verify for it: a person, a business, or both) and the `verifications` you can buy it with, each with the name of its person or company (`subject_name`), approved first: yours of an accepted subject type that are under review, and the approved ones the carrier still accepts for this offer. Pass an approved one's `id` as `verification_id` to POST /numbers. An offer that needs verification is `ready` when one of them is approved. Send `subject_type` (`person` or `business`) to get offers for that subject only: offers that do not accept it are left out, the rest come best first in the same order as GET /numbers/options, and each lists the `paperwork` that subject must give. Without it, nothing changes." 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\n\ The response includes the plaintext signing secret \u2014 this is the only\n\ time 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\n\ Cursor-paginated, newest first. The signing secret is never included \u2014\ \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. This public catalog contains no organization data. It uses the same registry 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. Only fields present in the request body are changed. Setting status="active" resets the consecutive-failure counter, so a subscription that was auto-disabled after 50 straight delivery failures 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 \u2014\ \ 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 \u2014 this is the\ \ only\ntime it is returned; update your verification code with it right away,\n\ since 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,\n\ and response body Hail recorded \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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\n\ recipient_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. Cursor-paginated: pass the returned next_cursor to fetch the next page; a null next_cursor means there are no more results. Filter by status or destination 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. Cursor-paginated. A suppressed number blocks POST /v1/sms sends to it with 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 \u2014 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. effective_default is the platform sender id used for alphanumeric-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\n\ Pass custom_sender_id=null to clear it and fall back to the platform\ndefault\ \ sender id. Only affects alphanumeric-eligible corridors \u2014 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. Org-scoped: returns 404 for an SMS belonging to a different organization." 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\n\ Merges two sources into one list: org members (kind=\"member\", ids\nprefixed\ \ \"member:\", managed via membership \u2014 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. Requires at least one of phone_e164 or email. Fails with 409 if a contact with the same phone or email already exists in this organization." 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 \u2014 a member: id (org members synced\ \ from\nmembership) returns 422; edit those via the membership APIs instead.\n\ The 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 \u2014\ \ 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 \u2014 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 \u2014 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. Useful for an agent that needs the human's address to put in Reply-To, since the bearer token itself only carries an organization. user_id/email/name come back null for a shared-key (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\n\ A 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 \u2014 not the partial input \u2014\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 most-recently-updated sibling. Idempotent: deleting a row that isn't there 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 provider 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. Empty body tests the layer's active provider with its stored key; send ``provider`` to test a specific saved row, or ``api_key`` (plus ``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/sms/evidence: post: tags: - verifications summary: Upload Sms Evidence description: "Upload a consent screenshot and get back a public link. Put the returned `url` in an SMS registration field that asks for an evidence URL (for example `website_evidence_url`). Carrier reviewers open it without signing in, so anyone with the link can see the file. Remove personal information first. Pasting your own public link still works." operationId: upload_sms_evidence 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/EvidenceUploadResponse" "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: The account keeps the most files allowed. "422": description: Validation Error content: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" requestBody: required: true content: multipart/form-data: schema: type: object required: - file properties: file: type: string format: binary description: A PNG, JPG or PDF of 10 MB or less. /v1/verifications/sms/evidence/{organization_id}/{token}: get: tags: - verifications summary: Get Sms Evidence description: "Public: the file behind a link from `POST /verifications/sms/evidence`. No sign-in. Carrier reviewers open this link. It redirects to a link that works for 10 minutes; open this link again for a new one." operationId: get_sms_evidence parameters: - name: organization_id in: path required: true schema: type: string format: uuid title: Organization Id - name: token in: path required: true schema: type: string title: Token responses: "302": description: Redirect to a short-lived link to the file. "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 link is malformed. "422": description: Validation Error content: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" delete: tags: - verifications summary: Delete Sms Evidence description: "Delete an uploaded file. Its link then leads to a storage error page. A registration that already names the link keeps the link in its text, and carrier reviewers can no longer see the file. Upload a new file and amend the registration if it is still under review." operationId: delete_sms_evidence parameters: - name: organization_id in: path required: true schema: type: string format: uuid title: Organization Id - name: token in: path required: true schema: type: string title: Token - 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: No file of yours has this link. "503": description: This server does not store files. "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 an address is needed. `purpose=number` (default) is for buying a number; it requires the offer's `quote_id` (422 without it) and returns the form for that offer. `purpose=sms` is for registering to send SMS in the US; it uses the carrier of your US numbers, or of the number passed as `number_id`. Build your form from it. `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/sms/preview: post: tags: - verifications summary: Preview Sms Application description: "Compile the exact campaign text without creating or charging a filing. Checks answers locally. This does not establish policy accessibility, carrier acceptance, identity ownership or runtime messaging readiness." operationId: preview_sms_application 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/SmsPreviewRequest" responses: "200": description: Successful Response content: application/json: schema: type: object additionalProperties: true title: Response Preview Sms Application "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). Parts: `purpose` (`number`, the default, or `sms` to register to send SMS in the US; `sms` takes `country_code=US` and `number_type=local`; its fees are charged from credit per step, when the details are submitted and when the carriers start their review, and 402 means there is not enough credit to file, also when a kept brand cannot be reused and the credit does not cover a new one), `country_code`, `number_type`, `subject_type` (required: `person` for you, `business` for your company; ask the user which), `quote_id` (required for `number`: the offer to verify for), `number_id` (optional, for `sms`: one of your US numbers; needed when your US numbers are with more than one carrier), `fields` (JSON object), `address` (JSON object, when required), `documents` (JSON object: slot name to `{"option": ..., "fields": {...}}`), and for each slot either a file part named `file.` or a text part named `upload.` holding the id of a `verification_document` uploaded with `POST /uploads` less than 1 hour ago (not both). Files are JPG, PNG or PDF, 10 MB each at most. Get the slot and field names from `GET /verifications/requirements`. Used uploads are deleted once the verification is saved; after a failed submit they stay for a retry. The name of the person or company comes from the form you send (for example the business name, or the first and last name) and is shown back as `subject_name`. For a number you may hold several verifications of each subject type (several people, several companies); a second live one of the same subject type and the same name is a 409, and so is a second live one of the same subject type when the form holds no name. A 409 comes before your files are checked and before anything is filed. A number verification needs credit for the offer''s setup and first month (`setup_cents` + `monthly_cents` of the quote); without it, 402 and nothing is filed. That amount is set aside from your balance while the carrier checks your papers. It pays for setup and the first month when you buy the number with this verification, and comes back if the verification is rejected or cancelled, or if no number is bought with it within 30 days of approval. A by-request offer sets nothing aside. The carrier checks the details right away. Problems come back as 422 with the field named, and nothing is kept. When it passes, the verification is submitted to the carrier for its review; read it back to follow the state. A `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 "402": description: Not enough credit. A number verification needs the offer's setup and first month (`setup_cents` + `monthly_cents` of the quote) and sets that amount aside until you buy the number. A US SMS registration needs its filing fee. Nothing is filed. "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` (`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/check: post: tags: - verifications summary: Check Verification description: "Check a verification form without filing anything (multipart/form-data). Takes the same parts as `POST /verifications` and runs every check Hail can run before filing: the purpose rules, the credit check (402), the carrier's form (a read), the duplicate check (409), field lengths, the files (`file.` and `upload.`: type, size, owner, age), and the form's own rules (required fields, documents, address; for `sms` the US texting application). Errors are the same codes and shapes submit gives. Nothing is filed at the carrier, saved, held or charged, and uploads are not used up. The carrier runs its own checks when you submit." operationId: check_verification 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/VerificationCheckResponse" "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 "402": description: Not enough credit to submit this form. Same rule as `POST /verifications`. "409": description: Submit would be a duplicate. Same rule as submit. "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 updated 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 a rejected one. The draft is discarded either way, except a person's confirmed brand on a rejected US SMS registration: it stays at the carrier for the next filing. One that is under review cannot be cancelled: wait for the result; if it is rejected, dismiss it. An accepted campaign waiting on runtime setup can be cancelled, as can an approved US SMS registration (`purpose=sms`): its numbers leave the campaign, the campaign is deleted at the carrier, and US-to-US texts from those numbers get 422 again. Its monthly fee is charged through the month of the cancellation, then stops. Some carriers bill a minimum number of monthly fees from approval: a registration cancelled sooner is still charged for that many months (prices: see the console)." 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}/application: get: tags: - verifications summary: Get Verification Application description: "Read your organization's saved SMS answers, including after rejection. Answers of a cancelled or rejected registration are kept for 30 days after it ended; after that this returns 404. Older applications may recover their verified sender identity through read-only provider calls; a 404 asks for original details and support when these cannot be recovered. The ordinary list never contains application fields or addresses. No records are filed or charged here." operationId: get_verification_application 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/SmsApplicationResponse" "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: - verifications summary: Amend Sms Application description: "Repair missing evidence on the same sender, brand and registration. No paid provider action occurs here. Only the campaign answers change; carrier review resumes on a later poll. Original sender identity/address must match stored or authoritative provider details. An identical retry is safe even after the campaign has subsequently been filed." operationId: amend_sms_application 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/SmsApplicationAmendRequest" 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 registration. Use it when the verification shows `confirmation: "code"`. On success the campaign is usually filed at once. It waits when your credit does not cover its fee (`waiting_for_credit` is true until you add credit; `credit_needed_cents` says how much). It also waits while another check of it is running or recently failed, or another process holds it; a later check files it. Poll GET /verifications/{id} until `approved`. A wrong or expired code is 422 on `code`; after five wrong codes, 429 until the carrier sends a new code (once a day). 409 when 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: Address: properties: customer_name: type: string title: Customer Name description: Name associated with the address. street: type: string title: Street description: Street address. city: type: string title: City description: City or locality. region: type: string title: Region description: State, province or region. postal_code: type: string title: Postal Code description: Postal or ZIP code. country_code: type: string title: Country Code description: ISO alpha-2 address country. type: object required: - customer_name - street - city - region - postal_code - country_code title: Address 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 place 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 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 the nullable ones (first_message, ai_disclosure_line, tools, max_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 \u2014\ \ 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 \u2014 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:' 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 \u2014 an org member or\ \ a manual\ncontact. ``id`` is ``member:`` 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. or at\ \ _dmarc. \u2014 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.. 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. Covers 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.` 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 values. 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 \u2014 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 \u2014 see GET /emails/{id}/attachments/{aid}." EmailAttachmentUploadResponse: properties: id: type: string format: uuid title: Id description: "Reusable attachment id \u2014 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. ``id`` is reusable across many ``POST /emails`` calls via ``EmailCreate.attachment_ids`` until Hail garbage-collects it (24h if 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 \u2014\ \ 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. For ``kind='hail_mail'`` ``domain`` is omitted; the server composes the full address as ``+@``. Both prefixes are optional in the body and fall back to the ``HAIL_MAIL_DEFAULT_*_PREFIX`` env vars; the server returns 503 if neither is supplied. For ``kind='custom'`` ``domain`` is required and the 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 \u2014 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 \u2014 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 \u2014 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 \u2014 ``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\n\ Every 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) \u2014 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. The inbound-action fields (``inbound_enabled``, ``forward_to``, ``forward_rate_per_hour``) surface what the row has configured for incoming 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 \u2014 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:). 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 EvidenceUploadResponse: properties: url: type: string title: Url description: Public link to the file. Put it in an `*_evidence_url` field. content_type: type: string title: Content Type description: "`image/png`, `image/jpeg` or `application/pdf`, read from the file." size_bytes: type: integer title: Size Bytes description: File size in bytes. type: object required: - url - content_type - size_bytes title: EvidenceUploadResponse 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. FileUploadCreate: properties: purpose: type: string enum: - email_attachment - verification_document title: Purpose description: "What the file is for. `email_attachment`: the id works in `POST /v1/emails` `attachment_ids`. `verification_document`: the id works as the `upload.` part of `POST /v1/verifications` and `POST /v1/verifications/check`." filename: type: string maxLength: 255 minLength: 1 title: Filename description: The file's name, 1 to 255 chars, no control characters. content_type: type: string title: Content Type description: MIME type, `type/subtype` with no parameters. A `verification_document` takes `image/jpeg`, `image/png` or `application/pdf` only. This type is stored; the PUT request's own `Content-Type` header is not used. type: object required: - purpose - filename - content_type title: FileUploadCreate description: "Body of ``POST /uploads``: what the file is for, and its name and type." FileUploadLinkResponse: properties: id: type: string format: uuid title: Id description: The upload's id. purpose: type: string enum: - email_attachment - verification_document title: Purpose description: What the file is for. upload_url: type: string title: Upload Url description: "Send the raw file bytes here with `PUT`. No Authorization header: the `token` in the URL is the only credential. Works once, until `expires_at`. Treat it like a password." method: type: string const: PUT title: Method description: The HTTP method for `upload_url`. content_type: type: string title: Content Type description: The type the file is stored with. max_bytes: type: integer title: Max Bytes description: The largest file the link takes, in bytes. expires_at: type: string format: date-time title: Expires At description: When the link stops working. type: object required: - id - purpose - upload_url - method - content_type - max_bytes - expires_at title: FileUploadLinkResponse description: "Returned by ``POST /uploads``: a one-time link to send the bytes to." FileUploadResponse: properties: id: type: string format: uuid title: Id description: The upload's id. purpose: type: string enum: - email_attachment - verification_document title: Purpose description: What the file is for. filename: type: string title: Filename description: The name given when the link was made. content_type: type: string title: Content Type description: The type given when the link was made. size_bytes: type: integer title: Size Bytes description: Bytes stored. Compare with the local file. sha256: type: string title: Sha256 description: sha256 of the bytes stored, hex. Compare with the local file. type: object required: - id - purpose - filename - content_type - size_bytes - sha256 title: FileUploadResponse description: "Returned by ``PUT /uploads/{id}``: what Hail stored." 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 forwards. Rows come from ``email_forward_targets``; see ``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:` 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:` 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 \u2014 private/internal hosts are\ \ rejected." api_key: type: string title: Api Key description: "API key sent to the BYO LLM endpoint. Write-only \u2014 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 NumberOptionsResponse: properties: country_code: type: string title: Country Code description: ISO alpha-2 country code. needs_paperwork: type: boolean title: Needs Paperwork description: False when no type needs papers for anyone. types: items: $ref: "#/components/schemas/TypeOptions" type: array title: Types description: One row per number type. type: object required: - country_code - needs_paperwork - types title: NumberOptionsResponse 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." subject_type: anyOf: - type: string enum: - person - business - type: "null" title: Subject Type description: "Who the number is for: 'person' (you) or 'business' (your company). Offers that do not accept that subject are left out, the rest are ranked for it, and each offer lists its `paperwork`. Omit to get every offer as before." 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 \u2014 null means 'could not check', never 'not published'.\ \ This does not change verification_status on the domain \u2014 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. held_cents: type: integer title: Held Cents description: "Credit, in USD cents, set aside from your balance for this verification: setup and the first month of the offer it was started for. A purchase with this verification counts it toward the price. 0 when nothing is set aside." default: 0 type: object required: - id - state - subject_type title: OfferVerification OptionVerification: properties: id: type: string format: uuid title: Id description: Hail verification id. subject_type: type: string enum: - person - business title: Subject Type description: Who that verification is for. A by-request option can be ready through the other subject's verification, so this can differ from the subject of the option. subject_name: anyOf: - type: string - type: "null" title: Subject Name description: Name of the person or company, when known. state: type: string title: State description: State of that verification. type: object required: - id - subject_type - state title: OptionVerification PaperworkItem: properties: key: type: string title: Key description: Fixed paper category key, such as id_document. label: type: string title: Label description: Short text to show for that category. type: object required: - key - label title: PaperworkItem 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 value; ``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 ``key_set_at`` are the only key-derived fields that leave the API. Every field is required (``key_last4``/``key_set_at`` nullable): the serializer always emits all of them, and required response fields generate 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 \u2014 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}`` \u2014 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\n\ resending 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`` \u2014 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 paperwork: anyOf: - items: $ref: "#/components/schemas/PaperworkItem" type: array - type: "null" title: Paperwork description: The papers the chosen `subject_type` must give for this offer, from the catalog. Null when the request had no `subject_type`. An empty list means no papers are needed. 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 carrier 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 SmsApplicationAmendRequest: properties: fields: additionalProperties: type: string type: object title: Fields description: Complete current form answers. The legal sender must remain unchanged. address: anyOf: - $ref: "#/components/schemas/Address" - type: "null" description: Original sender address; changing it requires support. additionalProperties: false type: object required: - fields title: SmsApplicationAmendRequest description: Complete replacement answers for an existing sender's missing evidence. SmsApplicationResponse: properties: schema_version: type: integer title: Schema Version description: Version of this saved-answer response format. default: 1 subject_type: type: string enum: - person - business title: Subject Type description: "Original sender route: person or business." fields: additionalProperties: type: string type: object title: Fields description: Saved application answers; legacy responses may contain only verified sender details. address: anyOf: - $ref: "#/components/schemas/Address" - type: "null" description: Original sender address, when available. type: object required: - subject_type - fields title: SmsApplicationResponse description: Private submitted SMS answers, or verified sender details for an old filing. 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 \u2014\ \ 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 SmsPreviewRequest: properties: subject_type: type: string enum: - person - business title: Subject Type description: "Sender route to preview: person or business." number_id: anyOf: - type: string format: uuid - type: "null" title: Number Id description: Owned US local number selecting the carrier, required when ambiguous. fields: additionalProperties: type: string type: object title: Fields description: Current registration form answers. address: anyOf: - $ref: "#/components/schemas/Address" - type: "null" description: Sender address required by the registration form. type: object required: - subject_type - fields title: SmsPreviewRequest 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 SubjectOption: properties: allowed: type: boolean title: Allowed description: False when no option lets this subject have this number type. The other fields then describe the other subject's best option. reason: anyOf: - type: string enum: - company_only - person_only - type: "null" title: Reason description: Why allowed is false, else null. how: type: string enum: - buy - by_request title: How description: "'buy' in the console, or 'by_request' (support orders it)." monthly_cents: type: integer title: Monthly Cents description: Catalog monthly price in USD cents. setup_cents: type: integer title: Setup Cents description: Catalog one-time setup price in USD cents. capabilities: items: type: string enum: - voice - sms type: array title: Capabilities description: What this option can do. paperwork: items: $ref: "#/components/schemas/PaperworkItem" type: array title: Paperwork description: Paper categories this subject needs. Empty when none, or when unknown. paperwork_known: type: boolean title: Paperwork Known description: False when the list is not known yet. The list is then empty. status: type: string enum: - ready - in_review - needs_check title: Status description: "ready: no papers needed, or a verification covers it. in_review: a verification is under review. needs_check: papers are needed." verification: anyOf: - $ref: "#/components/schemas/OptionVerification" - type: "null" description: The verification behind ready or in_review, else null. more_options: type: integer title: More Options description: How many other options this subject has for this type. type: object required: - allowed - how - monthly_cents - setup_cents - capabilities - paperwork - paperwork_known - status - more_options title: SubjectOption description: The best way for one subject (person or business) to get one type. 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 \u2014 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 TypeOptions: properties: number_type: type: string title: Number Type description: local, mobile, national or toll_free. for: additionalProperties: $ref: "#/components/schemas/SubjectOption" propertyNames: enum: - person - business type: object title: For description: The best option for each subject. type: object required: - number_type - for title: TypeOptions 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 VerificationCheckFile: properties: slot: type: string title: Slot description: The document slot. upload_id: anyOf: - type: string format: uuid - type: "null" title: Upload Id description: The upload used for the slot, or null for a `file.` part. size_bytes: type: integer title: Size Bytes description: Size of the file in bytes. sha256: type: string title: Sha256 description: sha256 of the file, hex. type: object required: - slot - upload_id - size_bytes - sha256 title: VerificationCheckFile description: One file a verification check read. VerificationCheckResponse: properties: ok: type: boolean const: true title: Ok description: "Always true: a failed check is a 4xx." files: items: $ref: "#/components/schemas/VerificationCheckFile" type: array title: Files description: The files the check read, one per slot. note: type: string title: Note description: What the check does not cover. type: object required: - ok - files - note title: VerificationCheckResponse description: Returned by ``POST /verifications/check`` when every local check passes. 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), as 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 - runtime_setup - needs_information - 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), or runtime_setup when accepted but required messaging responses still need configuration; needs_information when the existing sender needs current campaign evidence before filing. Null otherwise." campaign_accepted: type: boolean title: Campaign Accepted description: The carrier accepted the SMS campaign; this alone does not mean a number is ready to send. default: false runtime_ready: anyOf: - type: boolean - type: "null" title: Runtime Ready description: True when required runtime configuration was verified, false while setup retries, null when not independently tracked. cancellable: type: boolean title: Cancellable description: True when `DELETE /v1/verifications/{id}` accepts this verification now. default: false 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 waiting_for_credit: type: boolean title: Waiting For Credit description: "True when a US texting registration waits for credit: its next step, filing the campaign, has a carrier fee your balance does not cover. Nothing is filed until it does. Add credit; Hail goes on at its next check." default: false credit_needed_cents: anyOf: - type: integer - type: "null" title: Credit Needed Cents description: "The credit this registration still needs before Hail files its campaign, in US cents; null when `waiting_for_credit` is false. Per registration, not a running total: the first waiting registration shows the carrier's campaign fee less the credit left after the registrations already covered (more when the balance is short), each later one its whole fee. Add the sum over every registration with `waiting_for_credit` true, and one top-up lets them all go. It follows the balance, so read it again before you pay." 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 company 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 \u2014 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) \u2014 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. ``secret`` is populated **only** by create + rotate-secret responses; later 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 \u2014 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``."