# Adam Calling public API
> Send AI phone calls, poll for results, and read transcripts, extractions, and recordings over a REST API.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Overview
# Adam Calling public API
Drive Adam Calling programmatically: send a phone call with an org-scoped API key, poll until it completes, then read the transcript, summary, extraction, and recording. This guide gets you from zero to your first completed call; the [API reference](/api) is generated straight from the code and always current.
**Base URL:** `https://api.adamcalling.com/v1` (substitute your deployment's domain; paths in this guide are relative to it).
## Start here
Step 1
Get an API key
Mint an org-scoped key in the app and send it in the Authorization header.
Step 2
Send your first call
POST /calls with a phone number and a freeform task — one curl away.
Step 3
Poll for results
Transcript, summary, extraction, and the recording arrive together on completion.
Step 4
Use a pathway
Swap the prompt for a stored, versioned conversation flow built in the app.
## Explore the API
Calls
Send, list, and poll calls; stream recordings, watch the event trace, stop one or all active calls.
Pathways
List and read stored flows — or author them: create, import, validate, simulate, version, activate.
Analytics
Organization call metrics, per-pathway-version analytics, and tool analytics.
Identity
GET /me — inspect the organization and scopes behind your key.
## When something goes wrong
Every endpoint answers with one [error envelope](./errors-throttling-scopes.md), rate limits are per key (**60/min** sends, **300/min** reads), and scoped keys get a `403` outside their scopes — all detailed in [Errors, rate limits & scopes](./errors-throttling-scopes.md).
---
## Authentication
An organization Admin mints keys in the app under **Settings → API keys** ("Create API key"). The key is **shown exactly once** at creation — copy it immediately; the server stores only a hash and it can never be retrieved again. If you lose it, revoke it and mint a new one. (No Admin access? Ask your account contact.)
Send the key in the `Authorization` header. All three forms are accepted:
```
Authorization:
Authorization: Bearer
Authorization: Api-Key
```
A missing, malformed, revoked, or expired key gets a `401` — with one deliberately uniform message, so the API never reveals which of those it was.
## Scopes
A key minted with no scopes has **full access**. Scoped keys carry any of `calls:write` (send), `calls:read` (list/detail/recording), and `pathways:read` (list pathways); a request outside the key's scopes gets `403`. Use scoped keys for integrations that only need to read.
## Checking who you are
`GET /me` returns the organization and scopes behind the key you're holding — handy for verifying a freshly minted key before wiring it into an integration. See the [Identity reference](/api).
---
## Send your first call
`POST /calls` with a phone number (E.164) and a freeform `task` describing what the agent should do:
```bash
curl -X POST https://api.adamcalling.com/v1/calls \
-H "Authorization: $ADAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+48512345678",
"task": "You are calling on behalf of AutoParts sp. z o.o. to confirm the customer still wants the brake pads they asked about, and if so, arrange a pickup time this week.",
"language": "pl",
"name": "Jan",
"metadata": {"crm_id": "42"}
}'
```
```json
{"status": "success", "call_id": "8f4e…"}
```
`201` means the call was accepted and is being placed. Keep the `call_id` — it is the handle for everything that follows. `language` is `pl` (default) or `hr`; `metadata` is any JSON object you want echoed back on reads (e.g. your CRM's record id).
If the number has opted out of calls you get a `403` and no call is made.
Instead of a freeform `task`, you can send a `pathway_id` referencing a stored, versioned conversation flow — exactly one of the two, never both. See [Use a pathway](./pathways.md).
---
## Poll for results
`GET /calls/{call_id}` returns the full call object. While the call is in flight you'll see `call_status` of `pending` or `initiated` with `null` result fields; once it completes, the transcript, summary, and extraction arrive together:
```bash
curl https://api.adamcalling.com/v1/calls/$CALL_ID \
-H "Authorization: $ADAM_API_KEY"
```
```json
{
"status": "success",
"call_id": "8f4e…",
"phone_number": "+48512345678",
"call_status": "completed",
"outcome": "answered",
"duration_seconds": 73,
"summary": "Customer confirmed the order and asked for Thursday pickup…",
"transcript": "…full text…",
"transcripts": [{"speaker": "assistant", "text": "Dzień dobry…", "offset_s": 1.2}],
"extracted": {"pickup_day": "thursday"},
"extraction_status": "populated",
"recording_url": "https://…/v1/calls/8f4e…/recording",
"metadata": {"crm_id": "42"},
"cost": {"amount_usd": "0.4200"}
}
```
Poll every few seconds; most calls complete within a couple of minutes. A placement that failed shows `call_status: "failed"` with `error_message`. Fetch the audio from `recording_url` (same key auth) — it streams `audio/mpeg`; `null` means no recording exists.
## Listing calls
`GET /calls` lists your calls newest-first, with `status`, `outcome`, `pathway_id`, `phone_number`, `created_from`/`created_to` (dates), and `limit`/`offset` filters. See the [Calls reference](/api) for every field and filter.
---
## Use a pathway
# Use a pathway instead of a prompt
A pathway is a stored, versioned conversation flow built in the app. List the ones live for your organization, then send `pathway_id` **instead of** `task` (exactly one of the two — never both):
```bash
curl https://api.adamcalling.com/v1/pathways -H "Authorization: $ADAM_API_KEY"
# [{"id": "07a1…", "name": "Lead qualification PL", "language": "pl", "active_version_number": 3}]
curl -X POST https://api.adamcalling.com/v1/calls \
-H "Authorization: $ADAM_API_KEY" -H "Content-Type: application/json" \
-d '{"phone_number": "+48512345678", "pathway_id": "07a1…"}'
```
Only pathways with an active (published) version are listed and callable; a draft-only pathway gets a `400` with an explicit message.
## Authoring pathways over the API
Pathways aren't read-only: the public API also carries the full authoring surface — create a pathway, import a flow, validate and simulate it, test its webhooks, manage versions and post-call config, and activate a version. That's how `adam-cli` works with pathways. The [Pathways reference](/api) documents every endpoint.
---
## Stop calls & watch events
# Stop a call
See what's live, then end one call or all of them. Stopping is best-effort: the request goes to the voice provider and the call's final status arrives through the normal pipeline a moment later — poll `GET /calls/{call_id}` to confirm it wound down.
List active calls (queued or in progress) — same rows and envelope as `GET /calls`:
```bash
curl https://api.adamcalling.com/v1/calls/active -H "Authorization: $ADAM_API_KEY"
```
Stop one call:
```bash
curl -X POST https://api.adamcalling.com/v1/calls/$CALL_ID/stop \
-H "Authorization: $ADAM_API_KEY"
```
```json
{"status": "success", "message": "Call ended successfully."}
```
Stopping a call that already ended succeeds too (the stop is idempotent). A call that isn't active — already completed or failed, or still mid-placement with no line yet — returns `400`.
Stop every active call on your organization at once:
```bash
curl -X POST https://api.adamcalling.com/v1/calls/active/stop \
-H "Authorization: $ADAM_API_KEY"
```
```json
{"status": "success", "message": "Stopping active calls. This may take some time...", "num_calls": 3}
```
`num_calls` is how many active calls a hangup was issued for. Stopping needs the `calls:write` scope; listing active calls needs `calls:read`.
# Watch a call's events
`GET /calls/{call_id}/events` returns the call's append-only event trace — a chronological log of what happened during the call (status callbacks, the websocket connecting, per-node progress, a stop request, errors), oldest first. Use it to monitor a live call or diagnose one after the fact.
```bash
curl https://api.adamcalling.com/v1/calls/$CALL_ID/events \
-H "Authorization: $ADAM_API_KEY"
```
```json
{
"status": "success",
"count": 2,
"total_count": 2,
"events": [
{"type": "status_callback", "payload": {"call_status": "in-progress"}, "sequence": 0, "occurred_at": "2026-07-03T09:12:01Z"},
{"type": "ws_connected", "payload": {}, "sequence": 0, "occurred_at": "2026-07-03T09:12:03Z"}
]
}
```
Each event carries a `type` (Adam Calling's own event vocabulary), an arbitrary `payload` object, a `sequence` (tie-breaker for events recorded in the same instant), and `occurred_at`. Page with `limit`/`offset` like the other lists. Needs the `calls:read` scope.
---
## Errors, rate limits & scopes
# Errors
Every error uses one envelope:
```json
{"status": "error", "message": "Provide exactly one of 'task' or 'pathway_id'.", "errors": ["Provide exactly one of 'task' or 'pathway_id'."]}
```
`message` is the first human-readable problem; `errors` lists all of them (field errors as `"field: message"`).
| Status | Meaning |
| --- | --- |
| `400` | Invalid request body — bad phone format, both/neither of `task`/`pathway_id`, pathway with no active version; or a stop request for a call that isn't active. |
| `401` | Missing, malformed, revoked, or expired key (one message for all — the API never says which). |
| `403` | Valid key without the required scope, or the phone number has opted out. |
| `404` | Unknown `call_id`/`pathway_id`/`from_number_id` — including ids that belong to another organization. |
| `429` | Rate limit exceeded; retry after the `Retry-After` header. |
| `502` | The voice provider could not place the call; the attempt is recorded as failed. |
# Rate limits
Per key: **60 requests/min** on `POST /calls`, **300 requests/min** on all reads. `429` responses carry `Retry-After`.
# Scopes
A key minted with no scopes has **full access**. Scoped keys carry any of `calls:write` (send), `calls:read` (list/detail/recording), and `pathways:read` (list pathways); a request outside the key's scopes gets `403`. Use scoped keys for integrations that only need to read.