Hail Calls API
The calls API from Hail — 3 operation(s) for calls.
The calls API from Hail — 3 operation(s) for calls.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/hail-so:hail-so-calls-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hail Calls API
description: 'Give your AI agent a voice, a real phone number, and an inbox.
This file is the source of truth for the Go CLI. Regenerate it after
changing API routes — see docs/public/contributing.md.'
version: 0.1.0
servers:
- url: https://api.hail.so
description: Hail Cloud
tags:
- name: Calls
paths:
/v1/calls:
post:
tags:
- Calls
summary: Create Call
description: 'Place an outbound AI voice call.
The call is placed asynchronously — this returns as soon as the call is
queued, not when it completes. Poll GET /v1/calls/{call_id} or configure a
webhook to get the final status and transcript. Requires
recipient_consent=true on the request body; Hail does not verify lawful
basis 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'
components:
schemas:
CallCreate:
properties:
recipient_consent:
type: boolean
title: Recipient Consent
description: Attestation that you have obtained the lawful consent required to contact this recipient. Hail does not verify consent itself — you are responsible for a lawful basis under TCPA/ePrivacy/PECR/CAN-SPAM/GDPR as applicable. Rejected (422) if not true.
consent_source:
anyOf:
- type: string
- type: 'null'
title: Consent Source
description: Where/how consent was obtained (e.g. 'signup form', 'prior customer relationship'). Required (non-empty) when message_type is 'marketing'.
consent_obtained_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Consent Obtained At
description: When consent was obtained, if known.
message_type:
type: string
enum:
- marketing
- informational
title: Message Type
description: '''marketing'' additionally requires a non-empty consent_source. Use ''informational'' for transactional/service communications.'
default: informational
to:
type: string
title: To
description: Recipient phone number, E.164 format (e.g. +14155551234).
from:
anyOf:
- type: string
- type: 'null'
title: From
description: 'Caller-id phone number, E.164 format. Must be a number owned by the organization with the voice capability. Omitted: an active org-owned number is used if one exists, else a number is claimed from the shared pool.'
system_prompt:
anyOf:
- type: string
- type: 'null'
title: System Prompt
description: Task instructions for the agent, sent as its leading system message. At least one of system_prompt or llm is required; both together is also valid.
llm:
anyOf:
- $ref: '#/components/schemas/LLMConfig'
- type: 'null'
description: BYO LLM endpoint the call runs on instead of Hail's default model. At least one of system_prompt or llm is required; both together is also valid.
first_message:
anyOf:
- type: string
- type: 'null'
title: First Message
description: 'Opening line the agent speaks first. Omitted: the agent waits for the callee to speak first.'
recording_enabled:
anyOf:
- type: boolean
- type: 'null'
title: Recording Enabled
description: Best-effort audio recording for this call. true records and false does not, whatever the workspace setting. Null inherits the agent, then the workspace. Recording failure never prevents the call. The recording-enabled call rate applies regardless of capture outcome.
ai_disclosure:
type: boolean
title: Ai Disclosure
description: Speak the AI self-disclosure line ('Hi, this is an AI assistant calling on behalf of ...') as the first thing on the call. Enabled by default. Disable only if you have verified the disclosure is not required for this call — 47 CFR 64.1200(b)(1) requires identifying the initiating business at the start of artificial-voice calls in the US, and several jurisdictions have AI bot-disclosure laws. Hail does not verify this for you. The agent still identifies itself as an AI if asked.
default: true
voice_config:
$ref: '#/components/schemas/VoiceConfig'
description: TTS voice, VAD, turn-detection, and spoken-language settings for this call.
metadata:
additionalProperties: true
type: object
title: Metadata
description: Free-form JSON object attached to the call and echoed back on reads. Not interpreted by Hail. Keys starting with 'recording_' and the key 'failure_code' are reserved and rejected.
tools:
anyOf:
- items:
type: string
type: array
- type: 'null'
title: Tools
description: 'Agent tools to allow on this call. Omitted: every tool the organization''s configured channels support (new channels appear automatically). Empty list: no tools. Tool names are validated against the server''s registry.'
agent_id:
anyOf:
- type: string
format: uuid
- type: 'null'
title: Agent Id
description: Place the call with a saved agent (POST /agents). The agent supplies system_prompt, first_message, ai_disclosure, recording_enabled, voice_config, tools and max duration; any of those given explicitly on this request wins. Satisfies the system_prompt or llm requirement on its own. A paused agent is refused with 409.
additionalProperties: false
type: object
required:
- recipient_consent
- to
title: CallCreate
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
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
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
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
VoiceConfig:
properties:
tts:
type: string
const: cartesia
title: Tts
description: Text-to-speech provider. Currently only 'cartesia'.
default: cartesia
vad:
type: string
const: silero
title: Vad
description: Voice-activity-detection engine. Currently only 'silero'.
default: silero
turn_detection:
type: string
const: livekit
title: Turn Detection
description: Turn-detection engine. Currently only 'livekit'.
default: livekit
voice_id:
anyOf:
- type: string
- type: 'null'
title: Voice Id
description: 'Per-call TTS voice override, applied to whichever TTS provider serves the call. Omitted: the organization''s or environment''s default voice.'
language:
anyOf:
- type: string
enum:
- ar
- bg
- bn
- cs
- da
- de
- el
- en
- es
- fi
- fr
- gu
- he
- hi
- hr
- hu
- id
- it
- ja
- kn
- ko
- mr
- ms
- nl
- 'no'
- pl
- pt
- ro
- ru
- sk
- sv
- ta
- te
- th
- tl
- tr
- uk
- vi
- zh
- type: 'null'
title: Language
description: 'Spoken language for the call as a lowercase ISO 639-1 code (e.g. ''da''). One of the 39 supported codes — see docs/languages.md. Applied to STT, TTS, and turn detection. Omitted: the providers'' defaults (English).'
additionalProperties: false
type: object
title: VoiceConfig
LLMConfig:
properties:
base_url:
type: string
title: Base Url
description: Public HTTPS base URL of the BYO LLM endpoint the call runs on. Must resolve to a public address — private/internal hosts are rejected.
api_key:
type: string
title: Api Key
description: API key sent to the BYO LLM endpoint. Write-only — never echoed back.
model:
type: string
title: Model
description: Model name to request from the BYO LLM endpoint.
additionalProperties: false
type: object
required:
- base_url
- api_key
- model
title: LLMConfig