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

# Direct API

> POST to /api/ingest from any language.

## Overview

Use this path if you're not on Node.js, or if you need to send data the SDK doesn't capture. The ingest endpoint accepts conversations and agent definitions in one of five formats.

## Endpoint

```
POST https://app.calado.ai/api/ingest
```

## Authentication

Pass your agent's API key as a Bearer token.

```
Authorization: Bearer cl_your_api_key_here
```

Keys are prefixed with `cl_` and managed from your agent's **Settings** page in the dashboard.

## Request format

The top-level request body:

### Required

At least one of `conversations` or `agentDefinitions` must be present. Empty payloads are rejected.

### Optional

<ParamField body="agentId" type="string (UUID)">
  Pin the request to a specific agent. Validated against the API key. If omitted, the key's default agent is used.
</ParamField>

<ParamField body="format" type="enum">
  Only set this when forwarding provider request/response bodies untouched. One of `anthropic`, `openai_chat`, `openai_responses`. Omit to use the default shape shown in the examples. See [Format selector](#format-selector) below.
</ParamField>

<ParamField body="conversations" type="array">
  Array of conversation objects. Max 1,000 per request.
</ParamField>

<ParamField body="agentDefinitions" type="array">
  Array of agent definition objects. Max 100 per request.
</ParamField>

## Conversation object

### Required

<ParamField body="messages" type="array" required>
  Array of message objects. Min 1, max 10,000.
</ParamField>

### Optional

<ParamField body="externalId" type="string">
  Your stable id for this conversation. 1-255 characters. Used to link conversations across ingest calls (for multi-turn sessions) and to deduplicate.
</ParamField>

<ParamField body="sessionId" type="string">
  Grouping id for related conversations. Max 255 characters.
</ParamField>

<ParamField body="agentDefinitionId" type="string (UUID)">
  Link this conversation to a specific agent definition version. Used when analyzing against a known spec.
</ParamField>

<ParamField body="metadata" type="object">
  Arbitrary key-value data. Surfaces in the dashboard under the conversation.
</ParamField>

<ParamField body="traceData" type="object">
  Structured trace information. See [Trace data](#trace-data) below.
</ParamField>

<ParamField body="step" type="object">
  Position of this conversation in a multi-agent orchestration. See [Steps](#steps) below.
</ParamField>

## Message object

### Required

<ParamField body="role" type="string" required>
  Typically `user`, `assistant`, `system`, or `tool`. Any non-empty string is accepted.
</ParamField>

<ParamField body="content" type="string | ContentBlock[]" required>
  Either a plain string (max 100 KB) or an array of content blocks (1-1,000 blocks).
</ParamField>

### Optional

<ParamField body="timestamp" type="string (ISO 8601)">
  When the message was produced. Used for ordering when multiple messages share the same logical turn.
</ParamField>

## Agent definition object

### Required

<ParamField body="name" type="string" required>
  Human-readable name. 1-500 characters. Combined with `type` to dedupe versions.
</ParamField>

<ParamField body="type" type="enum" required>
  One of `system_prompt` or `tool_schema`.
</ParamField>

<ParamField body="content" type="string" required>
  The definition text. 1 byte to 100 KB.
</ParamField>

## Content blocks

Messages support either a plain string or typed content blocks. Block types mirror Anthropic's:

| Type          | Shape                                                                                                                      |
| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `text`        | `{ type: "text", text: string }`                                                                                           |
| `thinking`    | `{ type: "thinking", thinking: string }`                                                                                   |
| `tool_use`    | `{ type: "tool_use", id?: string, name: string, input: unknown }`                                                          |
| `tool_result` | `{ type: "tool_result", tool_use_id?: string, content?: string \| Array<{type:"text", text:string}>, is_error?: boolean }` |
| *other*       | `{ type: string, ...anything }` — unknown block types pass through                                                         |

## Trace data

When you have structured trace info (tool calls with outputs, retrieval results), attach it so analysis has more to work with.

<ParamField body="traceData.events" type="array">
  `{ type, timestamp, duration?, metadata? }[]`. Generic trace events.
</ParamField>

<ParamField body="traceData.toolCalls" type="array">
  `{ name, input, output?, error?, timestamp?, duration? }[]`. One entry per tool invocation.
</ParamField>

<ParamField body="traceData.retrievals" type="array">
  `{ query, chunks[], sourceDocIds?, timestamp? }[]`. RAG retrieval results.
</ParamField>

## Steps

When a conversation is one call inside a multi-agent orchestration, attach a `step` so calado can render the run as a tree and attribute behavior to the right sub-agent. Omit it for single-agent conversations.

<ParamField body="step.id" type="string">
  Stable id for this step within the run.
</ParamField>

<ParamField body="step.parentId" type="string">
  Id of the parent step. Omit for the root step. Cannot equal `step.id`.
</ParamField>

<ParamField body="step.roleName" type="string">
  Human-readable role for this sub-agent, e.g. `orchestrator` or `flight_search`.
</ParamField>

<ParamField body="step.inlineDefinition" type="string | object">
  The sub-agent's dynamic prompt or tool schema for this call — the "how it should work" side calado analyzes this step against. String or object, up to 100 KB. Omit it and the step is analyzed against the agent's static definition.
</ParamField>

## Format selector

The `format` field tells calado how to parse the `conversations` array when you're forwarding provider bodies untouched. Omit it to use the default shape shown in the examples.

| Value              | When to use                                                                      |
| ------------------ | -------------------------------------------------------------------------------- |
| `anthropic`        | You're forwarding Anthropic `messages.create` request/response bodies untouched. |
| `openai_chat`      | You're forwarding OpenAI `chat.completions` request/response bodies.             |
| `openai_responses` | You're using OpenAI's newer `responses.create` API.                              |

<Warning>
  If you're forwarding provider-shaped data, set `format` explicitly. Otherwise, leave it off and use the shape shown in the examples.
</Warning>

## Example

```bash theme={null}
curl -X POST https://app.calado.ai/api/ingest \
  -H "Authorization: Bearer $CALADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversations": [
      {
        "externalId": "session_abc123",
        "messages": [
          { "role": "user", "content": "What is the refund window?" },
          { "role": "assistant", "content": "You can request a full refund within 30 days." }
        ]
      }
    ],
    "agentDefinitions": [
      {
        "name": "system",
        "type": "system_prompt",
        "content": "You are a customer support agent for Acme Corp."
      }
    ]
  }'
```

## Response

### Success (202 Accepted)

```json theme={null}
{
  "conversations": {
    "accepted": 12,
    "skipped": 0,
    "failed": 0
  },
  "agentDefinitions": {
    "created": 1,
    "unchanged": 2
  },
  "warnings": []
}
```

| Field                        | Meaning                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| `conversations.accepted`     | Stored with `pending` classification. Analysis runs when you click **Run Analysis** in the dashboard. |
| `conversations.skipped`      | Rejected for structural reasons (see `warnings`).                                                     |
| `conversations.failed`       | Stored, but analysis will not run.                                                                    |
| `agentDefinitions.created`   | New definitions stored (content hash changed or new).                                                 |
| `agentDefinitions.unchanged` | Content hash matched an existing version.                                                             |
| `warnings`                   | Non-fatal per-item messages.                                                                          |

### Error

| Code                    | Meaning                                                            |
| ----------------------- | ------------------------------------------------------------------ |
| `400 Bad Request`       | Payload failed schema validation. Body contains Zod error details. |
| `401 Unauthorized`      | Missing, malformed, or invalid API key.                            |
| `403 Forbidden`         | Key exists but doesn't have access to the specified `agentId`.     |
| `413 Payload Too Large` | Request body exceeds 5 MB.                                         |
| `429 Too Many Requests` | Rate limited. Back off and retry.                                  |
| `5xx`                   | Server error. Safe to retry with exponential backoff.              |

## Batch limits

| Limit                                             | Value  |
| ------------------------------------------------- | ------ |
| Request body size                                 | 5 MB   |
| Conversations per request                         | 1,000  |
| Agent definitions per request                     | 100    |
| Combined conversations + agent definitions        | 1,000  |
| Messages per conversation                         | 10,000 |
| Content blocks per message                        | 1,000  |
| Message content                                   | 1 MB   |
| Agent definition `content`                        | 100 KB |
| Tool schemas per conversation (adapter-extracted) | 256    |

## Error handling

Retry on `5xx` and network errors only, with jittered exponential backoff. `4xx` responses are permanent and should not be retried.

On repeated `401` responses, stop retrying and surface the error to your operator. The key is wrong, and retrying won't fix it.

<Card title="Next: serverless patterns" icon="arrow-right" href="/ingestion/serverless">
  Flush the queue before your function freezes.
</Card>
