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

# Agent API Overview

> Talk to any deployed DataGen agent over an Anthropic-compatible streaming endpoint

Every DataGen agent exposes an Anthropic Messages-compatible endpoint. Point the
official Anthropic SDK — or plain `curl` — at your agent and it answers as a
streaming chat completion.

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

`{base-url}` is `https://api.datagen.dev` on DataGen Cloud, or
`http://<your-server-ip>:3001` if you self-host — see [Base URL](#base-url).

What runs behind it is a full agent session, not a bare model call: your agent's
files and repo, its skills, its MCP tools, and its secrets are all live inside a
sandbox for the duration of the turn.

<Note>
  `{agent-name}` is the agent's slug as shown on its detail page. Repo-backed
  agents use `owner-repo` (lowercase, `/` replaced by `-`), e.g.
  `datagendev-executive-assistant`. Storage-backed agents use the name you gave
  them, e.g. `data-agent`.
</Note>

***

## Base URL

Every example in this section uses the DataGen Cloud host. **If you self-host,
swap that host for your own server — the paths are identical.**

| Deployment           | Base URL                  |
| -------------------- | ------------------------- |
| DataGen Cloud        | `https://api.datagen.dev` |
| Self-hosted          | `http://<server-ip>:3001` |
| Self-hosted with TLS | `https://<your-domain>`   |

So the same endpoint, on each:

```bash theme={null}
# DataGen Cloud
https://api.datagen.dev/api/agents/invoice-agent/v1/messages

# Self-hosted — your server's IP or hostname, port 3001
http://10.0.0.42:3001/api/agents/invoice-agent/v1/messages

# Self-hosted with TLS — same domain as the web UI, no port
https://datagen.example.com/api/agents/invoice-agent/v1/messages
```

<Note>
  **Port 3001 is the API server, not the web UI.** Your team opens DataGen on
  port **3000**; integrations call **3001**. Both are on the same machine. If your
  operator changed `WASP_BACKEND_PORT` in `.env`, use that port instead — it is
  whatever `WASP_SERVER_URL` in your `.env` points at.
</Note>

With the TLS deployment profile, a reverse proxy serves the web UI and the API on
one domain and forwards `/api/*` to the API server, so port 3001 is closed to the
network and your base URL is simply your DataGen domain with no port.

<Tip>
  Make the base URL a config value in your integration, not a literal. Moving
  between cloud and self-hosted, or putting TLS in front of an existing install,
  then changes one environment variable instead of every call site.
</Tip>

***

## Your first call

<Steps>
  <Step title="Get an API key">
    In DataGen, open **Workspace Settings → API Keys** and create a key. Create
    it in the **same workspace as the agent** you want to call — the key can
    only reach that workspace's agents.
  </Step>

  <Step title="Send a message">
    ```bash 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 '{
        "model": "claude-haiku-4-5",
        "stream": true,
        "messages": [{"role": "user", "content": "What data sources can you reach?"}]
      }'
    ```
  </Step>

  <Step title="Keep the conversation id">
    The response carries an `X-Conversation-Id` header. Send it back as
    `metadata.conversation_id` on the next call and the agent picks up exactly
    where it left off. See [Conversations](/api-reference/agent-api/conversations).
  </Step>
</Steps>

### Two turns, end to end

The whole contract in one copy-pasteable script — ask something, then follow up in
the same conversation:

<CodeGroup>
  ```bash cURL theme={null}
  BASE=https://api.datagen.dev
  AGENT=data-agent

  turn() {  # turn "<message>" [conversation-id]
    curl -sN -D /tmp/h -X POST "$BASE/api/agents/$AGENT/v1/messages" \
      -H "X-Api-Key: $DATAGEN_API_KEY" -H "Content-Type: application/json" \
      -d "$(jq -n --arg m "$1" --arg c "${2:-}" '{
            stream: true,
            messages: [{role: "user", content: $m}],
            metadata: (if $c == "" then {} else {conversation_id: $c} end)
          }')" |
    # Print the assistant text as it arrives.
    sed -n 's/^data: //p' | jq -rj 'select(.delta.type == "text_delta") | .delta.text'
  }

  turn "List the tables you can reach."
  CONV=$(grep -i '^x-conversation-id' /tmp/h | tr -d '\r' | awk '{print $2}')

  turn "Now show me the row count of the first one." "$CONV"
  ```

  ```python Python theme={null}
  import json, requests   # or use the Anthropic SDK — see Send a Message

  BASE, AGENT = "https://api.datagen.dev", "data-agent"
  HEADERS = {"X-Api-Key": DATAGEN_API_KEY, "Content-Type": "application/json"}

  def turn(message, conversation_id=None):
      metadata = {}
      if conversation_id:
          metadata["conversation_id"] = conversation_id

      with requests.post(
          f"{BASE}/api/agents/{AGENT}/v1/messages",
          headers=HEADERS, stream=True, timeout=(10, 660),
          json={"stream": True,
                "messages": [{"role": "user", "content": message}],
                "metadata": metadata},
      ) as r:
          r.raise_for_status()
          for line in r.iter_lines(decode_unicode=True):
              if not line or not line.startswith("data: "):
                  continue
              evt = json.loads(line[6:])
              if evt.get("delta", {}).get("type") == "text_delta":
                  print(evt["delta"]["text"], end="", flush=True)
          return r.headers["X-Conversation-Id"]

  conv = turn("List the tables you can reach.")
  turn("Now show me the row count of the first one.", conv)
  ```
</CodeGroup>

Three things in that script carry the whole contract: `stream: true` on every
request, **one** user message per call (history comes from the conversation, not
the array), and the conversation id read from the response header and sent back.

<Note>
  Sending turns back to back from a script — as above — can outrun the server's
  bookkeeping and lose the previous turn's context. Add a short wait between
  automated turns; see
  [Resuming](/api-reference/agent-api/conversations#resuming).
</Note>

***

## Three differences from the Anthropic API

The wire format matches Anthropic's, so existing clients connect without changes.
The *semantics* differ in three ways that matter.

<Warning>
  **Only the last user message is sent to the agent.** The server reads the newest
  `role: "user"` text from `messages[]` and discards the rest. Conversation history
  lives on the server — pass `metadata.conversation_id`, don't replay the array.
  A three-message array whose first message states a fact and whose last asks about
  it will get "I don't know."
</Warning>

<Warning>
  **Always stream.** `stream: false` still returns `text/event-stream`. The
  official SDKs' non-streaming call (`messages.create()` / `client.messages.create`)
  does not raise on the mismatch — it returns a `Message` object filled with
  misparsed text. Use `messages.stream()` and its equivalents.
</Warning>

<Note>
  **Sampling parameters are ignored.** The server reads `messages`, `stream`,
  `model`, `system`, and `metadata`. `max_tokens`, `temperature`, `top_p`,
  `stop_sequences`, `tools`, and `tool_choice` are accepted and dropped —
  `max_tokens` is not required. The agent uses its own configured tools; you
  cannot inject client-side tool definitions.
</Note>

And one that has nothing to do with Anthropic's API: an agent can write.

<Note>
  **API-key calls are read-only by default.** An agent backed by a connected
  repository reads it but never commits or pushes, unless you send
  `metadata.read_only: false` — in which case a turn that changes files pushes a
  commit to the repository's default branch. See
  [Writing to a connected repository](/api-reference/agent-api/messages#writing-to-a-connected-repository).
</Note>

***

## Authentication

One header, on every request:

```
X-Api-Key: <your-api-key>
```

Create a key in the DataGen app under **Workspace Settings → API Keys**
(`/workspace/settings?tab=apikeys`) — the same page on a self-hosted install.

**The key decides the workspace.** Keys are created inside a workspace and belong
to it, so a key reaches only that workspace's agents — an agent in another
workspace returns `404`, even if you can see it in the web UI. To call an agent
in a team workspace, create the key while that workspace is selected. There is no
header for choosing a workspace per-request.

**The key decides what you may do.** Running a turn requires an **Admin**,
**Builder**, or **Operator** key; a **Viewer** key can read conversations but gets
`403` when it tries to send a message.

A missing or unrecognized key returns `401`; a revoked key returns
`401 {"message": "API key has been revoked"}`.

***

## Models

```bash theme={null}
curl "https://api.datagen.dev/api/agents/data-agent/v1/models" \
  -H "X-Api-Key: $DATAGEN_API_KEY"
```

Returns the model ids the agent can run on, with the agent's configured default
first:

```json theme={null}
{
  "data": [
    {"type": "model", "id": "claude-opus-4-7",   "display_name": "Claude Opus 4.7"},
    {"type": "model", "id": "claude-sonnet-4-6", "display_name": "Claude Sonnet 4.6"},
    {"type": "model", "id": "claude-haiku-4-5",  "display_name": "Claude Haiku 4.5"}
  ],
  "has_more": false
}
```

Same auth and error semantics as `/v1/messages`, which makes it a cheap way to
check that an agent name and key work together before sending a real turn.

When you omit `model`, resolution falls back to the automation's model (if you
passed `metadata.automation`), then the agent's default model.

***

## Where to go next

<CardGroup cols={3}>
  <Card title="Send a Message" icon="paper-plane" href="/api-reference/agent-api/messages">
    Full request body, SSE event stream, and SDK examples.
  </Card>

  <Card title="Conversations" icon="comments" href="/api-reference/agent-api/conversations">
    Resume, fork, list, share, and delete conversations.
  </Card>

  <Card title="Errors & Limits" icon="triangle-exclamation" href="/api-reference/agent-api/errors">
    Status codes, timeouts, and concurrency guidance.
  </Card>
</CardGroup>
