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

# Send a Message

> POST /api/agents/{agent}/v1/messages — request body, streaming response, and SDK usage

```
POST {base-url}/api/agents/{agent-name}/v1/messages
```

Runs one agent turn and streams the result back as Anthropic-style
server-sent events.

<Note>
  Examples below use DataGen Cloud (`https://api.datagen.dev`). Self-hosted
  installs replace that host with their own server —
  `http://10.0.0.42:3001/api/agents/invoice-agent/v1/messages` — and
  everything else is identical. See
  [Base URL](/api-reference/agent-api/overview#base-url).
</Note>

***

## Request body

```json theme={null}
{
  "model": "claude-haiku-4-5",
  "stream": true,
  "system": "Extra instructions for this turn.",
  "messages": [
    { "role": "user", "content": "Summarize yesterday's failed runs." }
  ],
  "metadata": {
    "conversation_id": "support-ticket-4821",
    "stream_tool_calls": true
  }
}
```

| Field      | Type    | Required | Notes                                                                                                                                                                                                                                |
| ---------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `messages` | array   | **yes**  | Non-empty. Only the newest `role: "user"` text is used. `content` may be a string or an array of `{"type":"text","text":"…"}` blocks (joined with blank lines). Non-text blocks are not read — use `metadata.attachments` for files. |
| `stream`   | boolean | no       | Send `true`. See the streaming warning on the [overview](/api-reference/agent-api/overview).                                                                                                                                         |
| `model`    | string  | no       | One of the ids from `/v1/models`. Falls back to the automation's model, then the agent's default.                                                                                                                                    |
| `system`   | string  | no       | **Appended to** the agent's own instructions — it never replaces them.                                                                                                                                                               |
| `metadata` | object  | no       | DataGen extensions, below.                                                                                                                                                                                                           |

Other Anthropic fields (`max_tokens`, `temperature`, `tools`, …) are accepted and
ignored.

### `metadata`

| Field               | Default                  | Effect                                                                                                                                                                                                  |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conversation_id`   | auto-generated           | Resume target. Any string — use your own ticket, thread, or order id. See [Conversations](/api-reference/agent-api/conversations).                                                                      |
| `stream_tool_calls` | `false`                  | Surface `tool_use` and `tool_result` blocks in the stream. Leave off for generic Anthropic clients that only expect text.                                                                               |
| `attachments`       | `[]`                     | Per-turn files, base64. Up to **10 files / 10 MiB decoded**. Written to `.uploads/<name>` in the agent's working directory and announced to the agent.                                                  |
| `read_only`         | `true` for API-key calls | Repo-backed agents: the repo is readable, but the turn never commits or pushes. Send `false` to let the agent write back — see [Writing to a connected repository](#writing-to-a-connected-repository). |
| `isolated`          | `false`                  | Run in a throwaway sandbox with no repo clone and no workspace files.                                                                                                                                   |
| `automation`        | —                        | Name of an active automation on this agent. Its stored prompt and model become the defaults for this turn; anything you send in the body wins. An unknown name is ignored.                              |
| `delivery`          | `"sync"`                 | Only `"sync"` is supported. `"async"` returns `501` — use webhooks or schedules for background runs.                                                                                                    |

<Accordion title="Internal flags">
  `metadata.builder` runs the turn against the agent builder's working tree
  (storage-backed agents only, requires Admin or Builder) and `metadata.test`
  marks a new conversation as a hidden test run. Both back the DataGen web UI;
  they are not intended for external integrations.
</Accordion>

### Writing to a connected repository

**API-key calls are read-only by default.** For an agent backed by a connected
repository, the agent clones and reads the repo but never writes back, unless you
explicitly ask it to.

To let a turn commit its work:

```json theme={null}
"metadata": { "read_only": false }
```

<Warning>
  With `read_only: false`, a turn that changes any file ends with `git add -A`, a
  commit authored by `datagen-agent`, and a **push to the repository's default
  branch** — not to a scratch branch. Send it only for integrations whose whole
  purpose is to modify the repository, and consider pointing the agent at a repo
  whose default branch is protected.
</Warning>

Storage-backed agents have no repository and never push. Their working files are
restored fresh for each turn and are not written back; the durable output of a
turn is whatever the agent writes to its outputs directory, which is published to
the workspace store.

### Attachments

```json theme={null}
"metadata": {
  "attachments": [
    { "name": "leads.csv", "mediaType": "text/csv", "dataBase64": "bmFtZSxkb21haW4K…" }
  ]
}
```

Base64 only — no `data:` prefix. Filenames are sanitized to a safe basename and
de-duplicated. Malformed entries and anything past the caps are dropped silently,
so validate client-side if a missing file would be a silent failure for you.
Attachments are never committed to a connected repo.

***

## Response

`200 OK`, `Content-Type: text/event-stream`, plus an **`X-Conversation-Id`**
response header — capture it when you didn't supply your own id.

```
event: message_start
data: {"type":"message_start","message":{"id":"msg_…","model":"claude-haiku-4-5","usage":{…}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"…"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Here's the summary…"}}

event: content_block_stop
data: {"type":"content_block_stop","index":1}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{…}}

event: message_stop
data: {"type":"message_stop"}
```

**Block types.** `text`, `thinking` (with a following `signature_delta`), and —
when `stream_tool_calls` is on — `tool_use` (with `input_json_delta`) and
`tool_result` (carrying `tool_use_id`, `content`, `is_error`). The `tool_result`
block is a DataGen extension; generic Anthropic clients ignore it.

**Granularity.** Blocks arrive whole, not token by token: each is
`content_block_start` + one full `content_block_delta` + `content_block_stop`.
`index` is a running counter for the turn.

**Termination.** The stream ends at `message_stop` — there is no `data: [DONE]`
sentinel.

**Usage.** `message_delta.usage` carries `input_tokens`, `output_tokens`,
`cache_read_input_tokens`, `cache_creation_input_tokens`, and the DataGen-specific
`total_cost_usd` for the turn.

<Warning>
  A failure after the stream opens arrives as an SSE `error` event on an
  already-`200` response:
  `event: error  data: {"error":{"type":"api_error","message":"…"}}`.
  Check for `error` events, not just the HTTP status.
</Warning>

***

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -N -X POST "https://api.datagen.dev/api/agents/data-agent/v1/messages" \
    -H "X-Api-Key: $DATAGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -D - \
    -d '{
      "model": "claude-haiku-4-5",
      "stream": true,
      "metadata": {"conversation_id": "support-ticket-4821"},
      "messages": [{"role": "user", "content": "Summarize yesterday'\''s failed runs."}]
    }'
  ```

  ```python Python theme={null}
  from anthropic import Anthropic

  client = Anthropic(
      api_key=DATAGEN_API_KEY,
      # no trailing /v1 — the SDK appends it
      base_url="https://api.datagen.dev/api/agents/data-agent",
  )

  with client.messages.stream(
      model="claude-haiku-4-5",
      max_tokens=1024,                      # required by the SDK, ignored by DataGen
      messages=[{"role": "user", "content": "Summarize yesterday's failed runs."}],
      extra_body={"metadata": {"conversation_id": "support-ticket-4821"}},
  ) as stream:
      for text in stream.text_stream:
          print(text, end="", flush=True)
      print(stream.get_final_message().usage)
  ```

  ```typescript TypeScript theme={null}
  import Anthropic from "@anthropic-ai/sdk";

  const client = new Anthropic({
    apiKey: process.env.DATAGEN_API_KEY!,
    baseURL: "https://api.datagen.dev/api/agents/data-agent",
  });

  const stream = client.messages.stream({
    model: "claude-haiku-4-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Summarize yesterday's failed runs." }],
    // @ts-expect-error DataGen extension
    metadata: { conversation_id: "support-ticket-4821", stream_tool_calls: true },
  });

  for await (const event of stream) {
    if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
      process.stdout.write(event.delta.text);
    }
  }
  ```
</CodeGroup>

<Note>
  `extra_body` (Python) and the cast on `metadata` (TypeScript) exist because the
  SDKs type `metadata` as Anthropic's `{user_id}` object. The field is passed
  through to DataGen untouched.
</Note>

### Reading the stream without an SDK

```bash theme={null}
curl -sN … | while IFS= read -r line; do
  case "$line" in
    'data: '*) printf '%s' "$(printf '%s' "${line#data: }" |
      jq -r 'select(.delta.type=="text_delta").delta.text // empty')" ;;
  esac
done
```

***

## Model-routed variant

If you'd rather select the agent by model string than by URL path:

```
POST https://api.datagen.dev/api/agents/v1/messages
```

with `"model": "datagen-agent--data-agent"` (or a bare agent slug). Useful when a
client only lets you configure one base URL and switches agents through its model
picker. Everything else is identical.
