---
title: "Place one policy-gated, idempotent outbound call"
method: POST
path: "/v1/calls"
---

# Place one policy-gated, idempotent outbound call

`POST /v1/calls`

## Request

- Base URL: `https://api.skysay.ai`
- URL: `https://api.skysay.ai/v1/calls`
- Auth: none declared

Select the saved hosted agent with agent_id; the call begins from that agent's stored STT, model, and TTS stack. agent_endpoint_id is a backwards-compatible alias, not a second stack selector. Supply Idempotency-Key (preferred) or idempotency_key. Replays with the same canonical request return the original call; reusing the key for a different request returns 409. call_context is a small, non-secret JSON object made available to the managed worker at call start, never a substitute for live customer data or a way to change the selected voice stack.

## Headers

- `Idempotency-Key` string

## Request body

- OutboundCall: recipient_type is optional and recorded, never enforced. Destination country is authorized against the org's active workspace capabilities (authorize_outbound), not a fixed platform allowlist. Blocked categories mirror carrier acceptable-use policy; debt collection is not a blocked category -- payment_reminder (first-party or third-party) is permitted with per-call authorization, subject to per-customer onboarding approval and the acceptable-use policy.
  - `agent_endpoint_id` string: Backwards-compatible alias for the selected agent endpoint; new integrations should send agent_id.
  - `agent_id` string: Canonical saved agent selection. It must belong to the authenticated organization and selected project; the call starts from its stored voice stack.
  - `call_context` object: Small bounded, non-secret facts valid at call start: at most 32 keys and 4 KB, lists of at most 16 items. Never put endpoint credentials, mutable case history, or a requested voice-stack override here. A conversation workflow read tool is served from this object: put its declared projection fields under case_snapshot, or as top-level keys; every declared field must be present and non-empty or the workflow takes its case-unavailable path. `navigation_hint` is reserved: plain text of at most 500 characters telling the agent what the phone menu it will meet expects, for example "Press 2 for English, then 1 for billing."; it reaches the agent as the built-in variable `call.navigation_hint`, and a longer or non-text hint refuses the call with `call_variables_invalid`. A variable you declared as a keypad field (`keypad` on the field, see PUT /v1/agents/{agent_id}/variables) is read from here too, but only to be keyed on a phone menu: it is never given to the agent as text, and a call whose texts name the field or carry its digits is refused.
  - `country` string: Optional. The destination country is derived from to_phone_number's calling code; a value sent here is used only when the destination cannot be attributed to a country, and never overrides it. Authorized per-organization by the active workspace capabilities.
  - `external_group_reference` string: Optional customer-owned batch/group reference.
  - `external_reference` string: Customer-owned stable case/reference ID. Skysay stores it but does not interpret it.
  - `first_message` string: Compatibility alias for runtime_config.first_message.
  - `from_phone_number_id` string, required
  - `idempotency_key` string: Optional body alternative to the Idempotency-Key header. Retries must reuse the same value with the same request.
  - `instructions` string: Compatibility alias for runtime_config.instructions.
  - `language` string: Compatibility alias for runtime_config.language. When both are supplied, runtime_config.language takes precedence. A selected multilingual hosted language chooses one allowed published profile before reservation. An engine whose agent-catalog row says has_language_axis false (a voice-to-voice engine) publishes no profile per locale; it accepts any language the platform catalogue runs and speaks the call in it.
  - `organization_id` string, required
  - `project_id` string, required
  - `purpose` string, required: Required, free-form outbound purpose declaration -- a non-empty purpose is the compliance paper trail; a novel purpose token is never refused for being novel. Documented catalog (pick one or send your own token): appointment, booking_confirmation, callback_requested, customer_outreach, delivery_notification, ops_alert, other, payment_reminder, personal_errand, reservation, status_check, support.
  - `recipient_type` string: Optional, never enforced or required -- outbound is not restricted to business recipients. Persisted verbatim on the call row if sent; SMS has no recipient_type column, so it is accepted but not stored there.
  - `runtime_config` OutboundCallRuntimeConfig: Bounded non-secret, per-call instructions. Clients cannot supply a frozen profile, credential ID, provider, model, or voice selection. runtime_config.language takes precedence over the top-level compatibility alias. For a multilingual hosted agent, language selects an allowed published profile before reserve and worker dispatch. An engine whose agent-catalog row says has_language_axis false (a voice-to-voice engine) publishes no profile per locale; it accepts any language the platform catalogue runs and speaks the call in it.
    - `first_message` string
    - `instructions` string
    - `language` string
  - `to_phone_number` string, required
  - `user_authorization` object

## Response `201`

Call

## Other responses

- `400`: Either a plain {error, message} (a malformed request), or, when the request is well formed but the selected agent's published configuration is one the voice worker could not build a call from: {error: 'realtime_config_not_runnable', reason: <code>, blockers: [{code, scope, detail}]}. Codes reuse the same admission vocabulary the Agents dashboard read returns (e.g. system_prompt_missing, voice_id_missing, first_message_missing, tts_not_runnable, language_not_runnable, delivery_profile_invalid). No call, wallet reservation or origination job is created; fix the agent's voice setup and retry.
- `409`: Idempotency key was reused for a different request, or the call can no longer be canceled.
- `429`: The bounded outbound queue is full. Includes Retry-After and retry_after_seconds.

---

[API](https://skmtc.dev/skysay/apis/skysay-api.md) · [All operations](https://skmtc.dev/skysay/apis/skysay-api/llms.txt) · [OpenAPI document](https://skmtc.dev/skysay/apis/skysay-api/revisions/95b8c66f99aa?raw)
