> ## Documentation Index
> Fetch the complete documentation index at: https://cntillion.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Developer API

> Workspace API keys, streaming chat, agents, sources, conversations, customer data, and WhatsApp templates

The developer API uses workspace keys from **Workspace settings → API keys**.
Its base URL is `https://app.cntillion.com/api/v2`. Owners and members can create
keys. Each key can access every agent in its workspace, with **Full access**,
**Monitor** or **Read only** permissions and an optional expiration date.
**Monitor** reads like **Read only** and can also use
[real-time monitoring](https://cntillion.com/docs/api-reference/realtime-monitoring.md).

Send the key as `Authorization: Bearer cl_api_...`. A client that keeps that
header for its own gateway can send `X-API-Key: cl_api_...` instead.

Copy the key when it is created. Centillion stores only a hash and cannot show
it again. Revoking or expiring a key stops new requests; a request already
running may finish. Keep keys in server environment variables, never browser
code. The workspace may have 20 active keys. These keys are separate from
agent-specific Zapier connections.

## First request

Copy an Agent ID from its General settings or the API keys page.

```bash theme={null}
curl 'https://app.cntillion.com/api/v2/agents/YOUR_AGENT_ID/chat' \
  -H "Authorization: Bearer $CENTILLION_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"message":"Hello!","userId":"customer-123","stream":false}'
```

The response contains `data.id`, `data.role`, `data.parts`, and `data.metadata`.
Text parts have `type: "text"` and `text`. Metadata includes `conversationId`,
`userMessageId`, `userId`, `finishReason`, and `usage`.

Send `conversationId` with the next message to continue. Centillion loads the
stored history. Only API conversations can continue through this endpoint.
`userId` is optional; when provided on a follow-up, it must match the stored user.
The first request can include `conversation_initiation_client_data` with typed
`dynamic_variables` and permitted agent overrides. Later requests omit it; voice
and duration overrides are rejected for chat. This does not call an initiation
webhook. See [Personalization](https://cntillion.com/docs/agents/personalization.md#developer-api-chat) for the
body and override permissions.

Omit `conversationId` to start over. A UUID `requestId` can identify retries of
the same chat request. Read-only keys cannot send chat messages.

## Streaming

`stream` defaults to `true`. Read SSE events from the response body:

```text theme={null}
data: {"type":"text-delta","id":"message-id","delta":"Hello"}

data: {"type":"finish","messageMetadata":{"conversationId":"conversation-id"}}

data: [DONE]
```

Append `text-delta.delta` to display the answer. Read the conversation ID from
`finish.messageMetadata`. Failures emit an `error` event with `code` and
`errorText`, followed by `[DONE]`. The API returns answer text. Internal tool
arguments, tool results, and attachment storage paths are not exported.

## Agents and settings

| Method | Path | Behavior |
| - | - | - |
| GET | `/agents` | Paginated agents in this workspace |
| POST | `/agents` | Create an agent |
| GET | `/agents/{agentId}` | Read agent configuration and training status |
| PUT | `/agents/{agentId}` | Update submitted configuration fields |
| DELETE | `/agents/{agentId}` | Permanently delete the agent and its data, including its [Twilio numbers](https://cntillion.com/docs/channels/phone.md) |
| PUT | `/agents/{agentId}/styles` | Update widget appearance |
| POST | `/agents/{agentId}/clone` | Copy every active branch's current version |
| POST | `/agents/{agentId}/chat` | Send a message |
| POST | `/agents/{agentId}/train` | Queue or retry knowledge training |

Create accepts `name` and optional `role`, `websiteUrl`, `instructions`, `model`,
and `temperature`. Roles are `customer-support`, `sales-agent`, and
`shopping-assistant`. Model defaults to `google/gemini-3.5-flash-lite`; use the model IDs shown in
Centillion's model picker. Temperature ranges from 0 to 1. If a supplied
website cannot be added, creation still returns the new ID with
`pendingSteps: ["ADD_SOURCE"]`; retry through the Sources endpoint.

Updates accept `name`, `instructions`, `model`, `temperature`, `guardrails`,
`greeting`, and `defaultLanguage`. Omitted fields stay unchanged. Set
`defaultLanguage` to `null` to clear it.

An explicit `branch_id` in the PUT/PATCH query or JSON body updates that
branch's published configuration and creates a new version automatically.
This update starts from the published head, so it does not publish unrelated
dashboard drafts. Optional `revision` detects a concurrent branch update;
`message` labels the new version. A shared agent name can only be changed on
Main. Published versions cannot be updated through `version_id`.

A PUT/PATCH without a branch selector updates Main the same way and returns
Main's new configuration. Personal drafts keep their unpublished changes as
the branch head moves. An open dashboard editor receives the new values for
fields without personal edits; an editor on another branch keeps its work.
A protected Main rejects updates from API keys. Agents created before version
control, which have no Main yet, update their settings directly.

Styles accept `displayName`, `initialMessages`, `messagePlaceholder`, `footer`,
`suggestedMessages`, `showSuggestedMessages`, `keepSuggestedMessages`, `theme`,
`accentColor`, `accentEnabled`, `accentHeader`, `accentSend`, `radius`,
`fontFamily`, `fontSize`, `bubblePosition`, `bubbleColor`, `bubbleIcon`,
`showAgentBubble`, `collectFeedback`, and `copyMessage`. Use the same values as
the dashboard. Unknown properties are rejected.

Clone accepts `{}` or an optional `name`. Each active branch starts with one
new version containing that branch's published configuration. Version history,
personal drafts, archived branches, and traffic splits are not copied. Main
receives 100% of the new agent's traffic. Actions, evaluation criteria, tests,
and voice configuration receive new agent-owned identities. Ready text, Q\&A,
file, and website sources remain shared workspace sources. Integrations,
conversations, Notion connections, and phone routing are managed separately.
The clone begins training automatically and checks the current per-agent
knowledge storage limit. The copy's Main answers with the new training as soon
as it finishes.

## Version control

New agents start with Main, version 1, and 100% traffic on Main. Reading
`GET /agents/{agentId}` returns Main's published configuration. Select another
branch with `branch_id`, a historical version with `version_id`, or the key
creator's personal branch draft with `include_draft=true`. A version ID cannot
be combined with `include_draft=true`. Selecting a configuration does not
change the dashboard editor.

```bash theme={null}
curl "https://app.cntillion.com/api/v2/agents/AGENT_ID?branch_id=BRANCH_ID&include_draft=true" \
  -H "Authorization: Bearer $CENTILLION_API_KEY"
```

Versioned agent reads include `branchId`, `versionId`, `isDraft`, and the full
`configuration` snapshot alongside the agent settings. Stored request headers
and signing secrets are redacted. Managed ticket and Notion source content is
not returned. Read-only keys also redact action request bodies.

| Method | Path | Behavior |
| - | - | - |
| GET, POST | `/agents/{agentId}/branches` | List branches or create one |
| GET, PUT, DELETE | `/agents/{agentId}/branches/{branchId}` | Read configuration, update metadata, or archive |
| POST | `/agents/{agentId}/branches/{branchId}/restore` | Restore an archived branch |
| PUT, DELETE | `/agents/{agentId}/branches/{branchId}/draft` | Save or discard the key creator's draft |
| POST | `/agents/{agentId}/branches/{branchId}/publish` | Publish a draft or restore a version |
| GET | `/agents/{agentId}/versions` | Page through agent version history |
| GET | `/agents/{agentId}/branches/{branchId}/versions` | Page through one branch's history |
| GET | `/agents/{agentId}/versions/{versionId}` | Read an immutable version snapshot |
| GET | `/agents/{agentId}/versions/{versionId}/compare` | Compare against `previous` or `live` |
| GET, PUT | `/agents/{agentId}/traffic` | Read or replace traffic allocations |
| GET | `/agents/{agentId}/branches/{branchId}/merge-preview` | Preview merging into `target_branch_id` |
| POST | `/agents/{agentId}/branches/{branchId}/merge` | Merge into a target branch |
| GET | `/agents/{agentId}/branches/{branchId}/rebase-preview` | Preview changes from Main |
| POST | `/agents/{agentId}/branches/{branchId}/rebase` | Pull in changes from Main |

Create a branch with `name` and optional `description`, `fromBranchId`, or
`versionId`. Metadata updates accept `name`, `description`, and `autoRebase`.
Main cannot be renamed or archived. Archive a branch only after its traffic
allocation is zero. Branch configuration reads accept `include_draft=true`.
History reads accept `limit` and `cursor`; agent history also accepts
`branch_id`. Version comparison accepts `against=previous` or `against=live`.

Saving a draft requires its branch `revision` and either `settings` containing
submitted prompt, guardrail, greeting, model, temperature, or language fields,
or a full `configuration` snapshot from a versioned read. Configuration IDs
must belong to the agent or its workspace library. Redacted credentials are
preserved when sent back unchanged. Changing a shared tool's definition
requires the key creator's tool editing permission.

```json theme={null}
{
  "revision": 2,
  "settings": {
    "instructions": "Help customers find the right support article.",
    "temperature": 0.3
  }
}
```

Publish with `revision`, optional `message`, and optional `restoreVersionId`.
Restoration creates a new version and keeps the existing history. Traffic
updates accept `allocations: [{"branchId": "BRANCH_ID", "percent": 100}]`;
percentages must total 100. Merge accepts `targetBranchId`, optional `force`,
and `archiveSource`, which defaults to true. Protected branches require a
workspace owner to make changes in the dashboard. All writes require a
full-access workspace key.

## Knowledge sources

| Method | Path | Behavior |
| - | - | - |
| GET, POST | `/agents/{agentId}/sources` | List or create sources |
| GET | `/agents/{agentId}/sources/summary` | Counts and bytes by source type |
| GET, PUT, DELETE | `/agents/{agentId}/sources/{sourceId}` | Read, update, or remove a source |
| POST | `/agents/{agentId}/sources/file` | Upload a file |
| PUT | `/agents/{agentId}/sources/{sourceId}/file` | Replace a file |

```json theme={null}
{"type":"text","name":"Return policy","content":"Returns are accepted within 30 days."}
```

```json theme={null}
{"type":"qna","name":"Opening hours","questions":["When are you open?"],"answer":"Monday to Friday, 9 am to 5 pm."}
```

```json theme={null}
{"type":"link","url":"https://example.com/help","linkType":"individual"}
```

PUT can send only changed source fields. Source types and link URLs cannot be
changed. Upload files with multipart fields `file` and optional `name`. Files
may be up to 20 MB; text and Q\&A sources allow 1 MB of extracted text. Existing
workspace storage limits apply. Notion sources can be read and deleted but
must be created or edited through the dashboard integration.

Ticket sources use the `tickets` type in list responses, filters, and summaries.
Their content is not returned. Configure the provider in the dashboard; cloning
an agent does not attach ticket sources or copy their provider connections.

These endpoints read and change Main's knowledge, including pending API source
changes. The branch open in the dashboard does not change their target.
POST `/agents/{agentId}/train` also trains Main's sources. Sources attached only
to another branch or a personal draft are excluded.

Writes queue training automatically. Poll the source for `untrained`, `updated`,
`trained`, or `failed`, or poll the agent's status and `trainingError`. Earlier
trained content remains available during replacement training. When training
finishes, Main gets a new version, "Knowledge updated through the API", with
Main's sources and the finished training, so chat answers from them. Personal
drafts keep their unpublished edits. Deleting a source removes it from Main
before retraining starts and leaves other branches' attachments in place.
Retry failed training with POST `/agents/{agentId}/train`. The summary returns
`count` and `bytes` for each source type, plus `shouldRetrain`, which is `true`
while any source has changes its last training did not include.

## Conversations and customer data

| Method | Path | Behavior |
| - | - | - |
| GET | `/agents/{agentId}/conversations` | List conversations across channels |
| GET | `/agents/{agentId}/conversations/export` | Include full message text |
| GET, PUT | `/agents/{agentId}/conversations/{conversationId}` | Read, pause, or resume |
| GET | `/agents/{agentId}/conversations/{conversationId}/messages` | Page through history |
| PUT | `/agents/{agentId}/conversations/{conversationId}/messages/{messageId}/feedback` | Set feedback |
| GET | `/agents/{agentId}/users/{userId}/conversations` | List a user's API conversations |
| GET | `/agents/{agentId}/leads` | Read captured leads |
| GET | `/agents/{agentId}/contacts` | Read contacts, optionally filtered by `userId` |
| GET | `/agents/{agentId}/contacts/{contactId}` | Read one contact |

Pause with `{"paused":true}` and resume with `{"paused":false}`. Feedback accepts
`{"feedback":"positive"}`, `"negative"`, or `null` to clear it. Conversation
lists accept `startDate` and `endDate` as ISO timestamps. Export accepts an
optional `conversationId`.

## Conversation analysis

`GET /agents/{agentId}/analysis/{conversationId}` returns the latest analysis
and latest result for each criterion. Use the internal conversation ID shown
in the dashboard or in an [analysis webhook](https://cntillion.com/docs/developer-guides/webhooks.md#conversationanalyzed).
For channel threads, pass `?source=CHANNEL_CONVERSATION`; the default is
`CONVERSATION_LOG`. A full-access or read-only workspace key can read results.

`data.analysis` includes job `status`, `error`, `completedAt` and `result`.
A completed result contains `data` (typed values, evidence and scope),
`sentiment` (label, score, frustration, rationale and user-turn trajectory),
and `topics`. It is `null` before analysis has been queued. Each entry in
`data.evaluations` contains its criterion name, status, verdict or score,
rationale and frozen scoring/model/scope configuration. Results may still be
pending; use the status rather than treating a missing value as failure.

The existing conversation detail endpoint also includes `analysis` for stored
chat conversations. Its conversation ID remains the external session ID,
as described above. Results from other workspaces are never returned.

## WhatsApp templates

Send an approved WhatsApp template from your own systems, for example when an
application needs more information or a payment bounced. A template is the only
way to message someone who has not written to your number in the last 24 hours.
Create templates in Meta's WhatsApp Manager; Centillion lists the ones Meta has
approved.

| Method | Path | Behavior |
| - | - | - |
| GET | `/agents/{agentId}/whatsapp/templates` | Approved templates and the numbers that can send them |
| POST | `/agents/{agentId}/whatsapp/messages/template` | Send a template to a phone number |
| GET | `/agents/{agentId}/whatsapp/messages/{messageId}` | A sent template's delivery status |

The listing returns `senders`, each with `phoneNumberId`, `phoneNumber`,
`displayName`, `status`, and `wabaId`, and `templates`, each with `name`,
`language`, `category`, `wabaId`, its `header`, `body`, and `footer` text, the
`variables` each component takes, and `sendable`. A template with an image,
video, or document header, or with a button that takes a value, has
`sendable: false` and an `unsupportedReason`. When Meta cannot be read for a
business account, `complete` is `false` and `unavailableWabaIds` names it.

```bash theme={null}
curl 'https://app.cntillion.com/api/v2/agents/YOUR_AGENT_ID/whatsapp/messages/template' \
  -H "Authorization: Bearer $CENTILLION_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: application-4821-needs-info' \
  -d '{
    "to": "+52 1 55 1234 5678",
    "from": "+52 55 1111 2222",
    "template": { "name": "application_needs_info", "language": "es_MX" },
    "variables": { "body": { "1": "María", "2": "your EIN letter" } },
    "customerName": "María López"
  }'
```

* `to` is the customer's number with its country code.
* `from` is a sender's `phoneNumberId` or `phoneNumber`. Omit it when the agent
  has one WhatsApp number.
* `template.language` can be omitted when the template is approved in one
  language.
* `variables` gives each placeholder's value by component and key: `"1"` for
  `{{1}}`, or the name of a named placeholder. Every placeholder needs a value.
  Header values may be up to 60 characters and body values up to 1,024, with no
  line breaks, tabs, or five spaces in a row.
* `customerName` names the conversation when this is the first message to the
  number.

A sent template returns `201` with `data.id`, `data.status` (`accepted`),
`data.providerMessageId`, the `body` as the customer sees it, and
`data.channelConversationId`. Replies arrive in that conversation, where the
agent answers them like any WhatsApp message while the number's automatic
replies are on. A conversation a teammate has taken over stays with them.

Every send needs an `Idempotency-Key` header, unique to the message you mean to
send. Retrying with the same key and body returns the first result with
`Idempotent-Replayed: true` and never messages the customer twice. Reusing the
key with another body returns `409 IDEMPOTENCY_KEY_REUSED`. When Centillion
cannot confirm whether Meta sent the message, it returns
`502 DELIVERY_UNCONFIRMED`, and retries with that key return
`409 DELIVERY_UNCONFIRMED`: check the conversation before sending again with a
new key.

Read the delivery status afterwards. `status` moves from `accepted` to `sent`,
`delivered`, and `read`, or to `failed` with Meta's error code in `error.code`.

Centillion does not send to a customer who sent STOP to any of the agent's
WhatsApp numbers. A customer who turned off marketing messages from your
business in WhatsApp still gets utility and authentication templates, but not
marketing ones. Sending needs a plan that includes API access, and Meta bills
each template to your WhatsApp Business account.

| Code | Status | Meaning |
| - | - | - |
| `IDEMPOTENCY_KEY_REQUIRED` | 400 | The `Idempotency-Key` header is missing |
| `SENDER_REQUIRED` | 400 | The agent has several numbers; pass `from` |
| `TEMPLATE_LANGUAGE_REQUIRED` | 400 | The template is approved in several languages; pass `template.language` |
| `PLAN_REQUIRED` | 403 | The workspace plan does not include API access |
| `SENDER_NOT_FOUND`, `TEMPLATE_NOT_FOUND`, `MESSAGE_NOT_FOUND` | 404 | No such number, approved template on the sender's business account, or sent message |
| `WHATSAPP_NOT_CONNECTED` | 409 | The agent has no connected WhatsApp number |
| `WHATSAPP_RECONNECT_REQUIRED` | 409 | Meta refused the number's access; reconnect it on the Channels page |
| `SEND_BLOCKED` | 409 | Meta is not accepting business-initiated messages from the number, often for billing |
| `RECIPIENT_OPTED_OUT` | 409 | The customer opted out, as above |
| `IDEMPOTENCY_KEY_REUSED`, `DELIVERY_UNCONFIRMED` | 409 | As above |
| `RECIPIENT_INVALID` | 422 | `to` is not a phone number with a country code |
| `MISSING_TEMPLATE_VARIABLES`, `UNKNOWN_TEMPLATE_VARIABLES`, `INVALID_TEMPLATE_VARIABLES` | 422 | The message names each variable, such as `body[2]` |
| `TEMPLATE_UNSUPPORTED` | 422 | The template has a media header or a button that takes a value |
| `TEMPLATE_REJECTED`, `RECIPIENT_NOT_REACHABLE`, `WHATSAPP_SEND_REJECTED` | 422 | Meta refused the message |
| `WHATSAPP_RATE_LIMITED` | 429 | Meta asked to slow down sending from this number |
| `WHATSAPP_UNAVAILABLE`, `DELIVERY_UNCONFIRMED` | 502 | Meta could not be reached, or the send could not be confirmed |

## Agent testing

Use the [testing API](https://cntillion.com/docs/api-reference/agent-testing.md) to create reusable workspace
tests, attach them to agents, run suites against selected configurations, and
read transcripts, verdicts, and grouped failure reasons. Runs support idempotent
requests and explicit message-credit opt-in. The JavaScript client and CLI can
use these results as a CI release check.

## Helpdesk

Workspace keys also authenticate the [Helpdesk API](https://cntillion.com/docs/api-reference/introduction.md).
Any key can read tickets. Creating and updating tickets needs a Full access key
and a plan that includes Helpdesk. A ticket's history shows changes made with a
key as made by the API. Attachment and image links in ticket responses still
need a signed-in dashboard session.

## Pagination and errors

List endpoints accept `limit`, from 1 to 100, default 20, and an opaque `cursor`.
The response is `{"data":[...],"pagination":{"cursor":null,"hasMore":false,"total":0}}`.
Pass the returned cursor with the same filters to fetch the next page. Message
pages contain chronological messages, paging backward from the newest.

PUT endpoints also accept PATCH. All writes require a full-access key. Every
resource query is scoped to the key's workspace; inaccessible resources return
404\. Workspace keys do not authenticate the voice APIs.
`GET /health` needs no key and returns `{"data":{"status":"ok"}}`.

Every response includes `x-request-id` and `Cache-Control: no-store`.
Authenticated requests include `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
and `X-RateLimit-Reset`, a Unix timestamp in milliseconds. Each key permits
100 requests per 10-second sliding window. A 429 response includes `Retry-After`
in seconds. Agent message limits and workspace credits still apply.

Errors use `{"error":{"code":"UNAUTHORIZED","message":"..."}}`:

| Status | Meaning |
| - | - |
| 400 | Invalid body, parameters, or cursor |
| 401 | Missing, invalid, expired, or revoked key |
| 403 | Read-only key used for a write, plan without the feature, agent capacity reached, or clone exceeds storage limit |
| 404 | Resource or endpoint not found |
| 409 | Paused conversation, conflicting source changes, or a WhatsApp send that cannot go out |
| 422 | A WhatsApp template request that cannot be sent |
| 413 | Request or file too large |
| 415 | Wrong content type |
| 429 | Request, message, or credit limit reached |
| 500 / 502 / 504 | Server failure, WhatsApp unavailable, or timeout |

Configure event deliveries in **Agent settings → Webhooks**. For client-side
embedding and identity, use the [website guide](https://cntillion.com/docs/channels/website.md) or
[mobile SDKs](https://cntillion.com/docs/channels/mobile-sdk.md).

## Batch calls

Create, schedule and manage phone or WhatsApp voice batches with the
[batch calling API](https://cntillion.com/docs/api-reference/batch-calls.md). It supports inline recipients,
resumable imports, paginated results and the same controls as Outbound.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.