# Phone calls, one API away.

Give your agent a task, get a call ID, and follow the result. Dialkit handles the phone connection and conversation. Use REST for direct integration or MCP for tool-capable agents.

Base URL: `https://app.dialkit.ai` · [OpenAPI specification](/openapi.json) · [Markdown guide](/docs.md) · [Agent index](/llms.txt)

## Quickstart

1. [Create an account](/auth), then open [Account](/account) to create a personal API token.
2. Verify your outgoing caller ID and add account credit. A call needs both before it can start.
3. Ask the user to confirm the recipient and purpose. This API reaches real people and uses account credit.
4. Create the call with a stable idempotency key, then poll its call ID for the result.

All examples use a fictional phone number. Replace it with the intended recipient only after the user authorizes the call. The examples are documentation; this page does not place calls.

```sh
export DIALKIT_BASE_URL="https://app.dialkit.ai"
export DIALKIT_API_TOKEN="YOUR_PERSONAL_API_TOKEN"
# Generate once for this confirmed task. Save this key before sending.
export DIALKIT_SUBMISSION_KEY="$(python3 -c 'import uuid; print(uuid.uuid4().hex)')"

curl --fail-with-body "$DIALKIT_BASE_URL/v1/calls" \
  -H "Authorization: Bearer $DIALKIT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $DIALKIT_SUBMISSION_KEY" \
  -d '{
    "to": "+14165550123",
    "purpose": "Confirm my appointment for tomorrow at 2 pm",
    "max_duration": 300
  }'
```

```json
{"call_id":"call_example","status":"queued"}
```

This response means the task was accepted. It does not mean the appointment was confirmed. Use the returned ID to read the result:

```sh
curl --fail-with-body "$DIALKIT_BASE_URL/v1/calls/call_example" \
  -H "Authorization: Bearer $DIALKIT_API_TOKEN"
```

## Authentication

Send `Authorization: Bearer <personal_api_token>` on every `/v1/` request. Obtain a token through the account flow; there is no anonymous call endpoint. Keep tokens in your agent's secret store and out of prompts, public repositories, URLs, and logs.

Calls and submission keys are scoped to the token's account. A call owned by another account returns `404`. `/health`, this guide, and the discovery documents are public. Browser account/session APIs are separate from the public REST API.

Connect can also issue a personalized API base URL and OpenAPI link. Those URLs contain an account credential and must remain private. Their schema covers call creation, results, keypad tones, capabilities, and submission recovery. They support the same `Idempotency-Key` contract. Use their supplied server URL exactly; the public `/openapi.json` instead describes the bearer-authenticated API.

## Endpoints

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/health` | Check service health and deployed app version; no token needed. |
| GET | `/v1/capabilities` | Discover supported features, including `call_idempotency_version`. |
| POST | `/v1/calls` | Start a real outbound call. |
| GET | `/v1/calls/{call_id}` | Read status, transcript, outcome, and billing. |
| GET | `/v1/call-submissions/{submission_key}` | Recover a call ID after an uncertain submission. |
| POST | `/v1/calls/{call_id}/dtmf` | Send keypad tones to an active call. |
| GET | `/v1/calls/{call_id}/recording/info` | Check recording availability and retrieval cost. |
| GET | `/v1/calls/{call_id}/recording` | Download a recording as WAV audio. |

The [OpenAPI specification](/openapi.json) describes the public request and response schemas, parameter limits, and authentication scheme.

## Create a call

`POST /v1/calls` accepts a JSON object. Only `to` and `purpose` are required.

| Field | Type | Behavior |
| --- | --- | --- |
| `to` | string | Recipient in E.164 format, including `+` and country code, such as `+14165550123`. |
| `purpose` | string | What to accomplish; 5–2,000 characters. Include exact dates, times, timezone, and relevant details. |
| `instructions` | string or null | Conversation guidance; up to 4,000 characters. |
| `max_duration` | integer | Maximum call length in seconds; 10–600, default 300. |
| `from_number` | string or null | A verified caller ID on this account. If omitted, Dialkit uses the account's default or first verified ID. |
| `agent` | string or null | Saved agent ID or name. If omitted, the account's default agent is applied when available. |
| `voice` | string | `auto` by default, or a supported provider voice. |
| `language` | string or null | Language code such as `en` or `he`; maximum 8 characters. Omit to use the agent/default behavior. |
| `model` | string or null | `default`, `openai`, or `gemini`. Explicit provider selection requires the plan's model-selection feature. |
| `join_call` | string or null | An active call ID owned by this account. The new leg joins its conference bridge. |
| `loop_in_to` | string or null | E.164 number to use for owner escalation, overriding the agent default. |

Use a direct JSON object as shown in Quickstart. Do not send the internal `agent_id` field; `agent` is the public selector. If an agent has its own defaults, provide overrides deliberately.

## Retries and idempotency

A timeout does not prove that a call failed to start. Retrying call creation with a new key can reach the recipient twice.

Use `Idempotency-Key` for every REST call submission. A key contains 16–128 ASCII letters, digits, hyphens, or underscores. A UUID string is suitable. Persist the key and request body before the first POST. Keys are scoped to the account and retained with the durable submission; do not reuse a key for another task.

- Same key and identical body: returns the existing call ID and its current status without dispatching another call.
- Same key and different body: returns `409`. Do not modify the request while retrying it.
- Uncertain POST outcome: check the submission using the same account and key, or retry the exact same POST and key.
- Submission lookup returns `404`: no submission is stored at lookup time. If the earlier POST is still unresolved, reuse its key and body rather than creating a new task key.
- `409` because the stored call record is unavailable: retain the key and surface the conflict. Do not automatically create a replacement call.

```sh
curl --fail-with-body \
  "$DIALKIT_BASE_URL/v1/call-submissions/$DIALKIT_SUBMISSION_KEY" \
  -H "Authorization: Bearer $DIALKIT_API_TOKEN"
```

`GET /v1/capabilities` returns `{"call_idempotency_version":1}` when this contract is supported. Without an idempotency key, a repeated POST can create another call.

## Status and results

Poll `GET /v1/calls/{call_id}` with a bounded deadline, for example every 2–5 seconds. Increase the interval after transient failures. Stop on a terminal status or when your workflow's deadline expires. A local polling timeout does not cancel the phone call.

| Status | Meaning |
| --- | --- |
| `queued` | Accepted and preparing the call. |
| `ringing` | Dialing the recipient. |
| `in_progress` | The conversation is active. |
| `completed` | The call has finished. Read `outcome` and `transcript` to determine what was achieved. |
| `failed` | The call failed. |
| `busy` | The recipient's line was busy. |
| `no_answer` | The recipient did not answer. |

The last four statuses are terminal. Treat future unknown statuses as unknown, rather than success. Results may be incomplete while the call is active. `from` is the caller ID field in the REST response.

```json
{
  "call_id": "call_example",
  "status": "completed",
  "direction": "outbound",
  "from": "+14165550124",
  "to": "+14165550123",
  "purpose": "Confirm my appointment for tomorrow at 2 pm",
  "started_at": "2026-10-22T14:00:00Z",
  "ended_at": "2026-10-22T14:00:45Z",
  "duration_seconds": 45,
  "outcome": "The recipient confirmed the appointment.",
  "transcript": [
    {"role":"agent","text":"I'm calling to confirm the appointment.","ts":0.0},
    {"role":"caller","text":"Yes, that time works.","ts":3.2}
  ],
  "billable_minutes": 1,
  "billable_amount_usd": 0.18,
  "bridge_id": null
}
```

This is an illustrative response; billing depends on the account's current plan and actual usage. Use the returned billing fields. Timestamps use ISO 8601 with a timezone; optional fields may be `null`. Transcript `ts` values are seconds from call start. A call ending normally does not guarantee its purpose was fulfilled.

## MCP tools

For a client that supports Streamable HTTP and bearer headers, connect to `https://app.dialkit.ai/mcp`. This is a POST-based MCP transport, not an SSE URL. Your client handles MCP initialization, protocol headers, and sessions.

Use [Connect](/connect) for client-specific setup or an account-authorized connect URL. A connect URL can contain an account credential; keep it private. For direct bearer-token setup, adapt this template to your client's configuration and secret storage:

```json
{
  "mcpServers": {
    "dialkit": {
      "url": "https://app.dialkit.ai/mcp",
      "headers": {"Authorization": "Bearer YOUR_PERSONAL_API_TOKEN"}
    }
  }
}
```

The tools are `make_call`, `get_call`, and `send_dtmf`. Discover their current input schemas through MCP `tools/list`. `make_call` takes `to` and `purpose`, with optional instructions, caller ID, duration, agent, model, language, and conferencing options. `get_call(call_id)` returns the call result. `send_dtmf(call_id, digits)` sends keypad input.

Require authorization before calling real people. If an IVR asks for keypad input, use `send_dtmf`. The MCP `make_call` tool does **not** expose the REST `Idempotency-Key` contract: do not automatically retry an uncertain tool execution. Use REST when durable submission recovery is required.

## Keypad tones and recordings

Send `POST /v1/calls/{call_id}/dtmf` with JSON such as `{"digits":"123#"}`. Digits may contain `0–9`, `A–D`, `*`, and `#`, up to 64 characters. Optional timing fields are `before_ms` (default 0), `between_ms` (120), `duration_ms` (250), and `after_ms` (0). The call must be active and owned by the account. `409` means it is not currently accepting tones. Repeating this POST can send the digits again.

Recordings exist only when recording was enabled for the call. First read `/v1/calls/{call_id}/recording/info`, which returns `storage_class`, `size_bytes`, `is_cold`, and `retrieval_fee_usd`. Obtain the user's approval for any cold retrieval fee before downloading.

`GET /v1/calls/{call_id}/recording?format=stereo` returns `audio/wav`; use `format=mono` for a mono mix. Downloading a cold recording charges its retrieval fee to the account. Do not fetch a recording just to check availability. `404` means the call or recording is unavailable to this account.

## Errors and recovery

HTTP errors use `{"detail":"message"}`. Request validation may instead return a `detail` array containing `loc`, `msg`, and `type`. The `x-trace-id` response header can help support locate the request; never include your token in a support report.

| Status | Next step |
| --- | --- |
| `400` | Correct the rejected caller ID or call setup request. |
| `401` | Supply a valid personal API token. |
| `402` | Add account credit or renew the required subscription. |
| `403` | Check caller ID verification and plan restrictions. |
| `404` | Check the identifier and account; another account's resources are not visible. |
| `409` | Resolve the idempotency conflict, or wait until an active call accepts tones. |
| `422` | Correct invalid JSON, phone numbers, parameter limits, or key format. |
| `429` | Back off and wait for concurrent call capacity. |
| `5xx` / network timeout | Call creation may already have been accepted. Keep the same key and body when recovering. |

There is no public REST cancellation endpoint in this API version. Do not invent a hangup route or treat a stopped poller as a canceled call. Set `max_duration` before creating the call.

## Discovery for agents

Start with [llms.txt](/llms.txt), then follow the [Markdown guide](/docs.md) or [OpenAPI specification](/openapi.json). The [API catalog](/.well-known/api-catalog) exposes standard `service-desc`, `service-doc`, and health links. HTTP `Link` headers and the site's navigation point to these resources too.

[robots.txt](/robots.txt) allows public documentation to be indexed and excludes account, authentication, and operational endpoints from crawler indexing. It does not grant API authorization. [sitemap.xml](/sitemap.xml) lists the public pages.
