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

# API Reference

> Invoke Braintrust experts programmatically via REST API.

Braintrust experts can be invoked programmatically via REST API, enabling automated pipelines, CI/CD integrations, custom applications, and webhook-driven workflows.

## Base URL

```
https://braintrust.ti.trilogy.com
```

## Authentication

All API requests require an API key passed as the `x-api-key` header.

```
x-api-key: YOUR_API_KEY
```

Get your API key from **Settings → Manage API Keys**. See [Managing API Keys](/setup/managing-api-keys) for a step-by-step guide.

<Warning>
  Never commit API keys to source control or log them in plain text. Treat them like passwords.
</Warning>

***

## Invoke an Expert

**POST** `/api/agents/invoke`

Send a message to an expert and receive a response. Supports streaming (default) and async job modes.

### Request Body

| Field                       | Type          | Required | Description                                                                                                                                                                                                                                                                                      |
| --------------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `agent`                     | string        | Yes\*    | Single agent ID (lowercase, hyphenated — from the expert's page)                                                                                                                                                                                                                                 |
| `agents`                    | string\[]     | Yes\*    | Array of agent IDs for multi-agent collaboration                                                                                                                                                                                                                                                 |
| `message`                   | string        | Yes      | Non-empty user message                                                                                                                                                                                                                                                                           |
| `thread_token`              | string        | No       | Conversation continuity handle (see [Thread Continuity](#thread-continuity))                                                                                                                                                                                                                     |
| `stream`                    | boolean       | No       | Default `true`. Set `false` for async job mode                                                                                                                                                                                                                                                   |
| `context`                   | string        | No       | Extra background context injected into the conversation                                                                                                                                                                                                                                          |
| `attachments`               | Attachment\[] | No       | Array of file attachments uploaded via the presigned-URL flow. Each entry references a previously uploaded file by `s3_key` (returned by the upload endpoint). Each entry contains exactly `s3_key`, `type`, `name`. Maximum 20 files. See [Attachments](#attachments) for the per-entry schema. |
| `deliver_to.gchat_space_id` | string        | No       | Deliver output to a Google Chat Space (e.g. `spaces/AAQAvU5RLdI`)                                                                                                                                                                                                                                |
| `deliver_to.agent_dm`       | boolean       | No       | Deliver output as a DM from the agent's identity                                                                                                                                                                                                                                                 |

\* Use either `agent` or `agents`, never both.

<Note>
  Fields `async` and `wait_for_completion` are not supported — use `stream` only.
</Note>

#### Attachments

Each entry in the `attachments` array is an object with the following fields:

| Field    | Type   | Required | Description                                                                                                  |
| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| `s3_key` | string | Yes      | Must start with `invoke-uploads/` and come from the presigned-URL flow below. Arbitrary S3 paths return 400. |
| `type`   | string | Yes      | MIME type of the uploaded file (e.g. `application/pdf`).                                                     |
| `name`   | string | Yes      | Original basename of the file.                                                                               |

No other fields are accepted — extra keys return 400. Maximum 20 attachments per invoke.

<Warning>
  Files must be uploaded before invoking the expert. Use POST `/api/agents/invoke/attachments/upload-url` to obtain a presigned URL, upload the file to that URL, then include the returned attachment object (`s3_key`, `type`, `name`) in your invoke request. The `s3_key` must start with `invoke-uploads/` — arbitrary S3 paths are rejected with 400.
</Warning>

**JSON request body example with attachments:**

```json theme={null}
{
  "agent": "data-analyst",
  "message": "Summarise the two reports attached.",
  "attachments": [
    {
      "s3_key": "invoke-uploads/you@example.com/session-1/1716300000000-q1-report.pdf",
      "type": "application/pdf",
      "name": "q1-report.pdf"
    },
    {
      "s3_key": "invoke-uploads/you@example.com/session-1/1716300000001-q2-report.pdf",
      "type": "application/pdf",
      "name": "q2-report.pdf"
    }
  ]
}
```

**curl example:**

```shell theme={null}
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "data-analyst",
    "message": "Summarise the two reports attached.",
    "attachments": [
      {
        "s3_key": "invoke-uploads/you@example.com/session-1/1716300000000-q1-report.pdf",
        "type": "application/pdf",
        "name": "q1-report.pdf"
      },
      {
        "s3_key": "invoke-uploads/you@example.com/session-1/1716300000001-q2-report.pdf",
        "type": "application/pdf",
        "name": "q2-report.pdf"
      }
    ]
  }'
```

##### Get a presigned upload URL

Call this endpoint once per file before invoking with attachments.

**POST** `/api/agents/invoke/attachments/upload-url`

**Request fields:**

| Field             | Type   | Required | Description                                                                                 |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |
| `uploadSessionId` | string | Yes      | Stable client ID for the upload batch (e.g. UUID); scopes S3 keys per user. Note camelCase. |
| `filename`        | string | Yes      | Basename only — no `/` or `..`.                                                             |
| `contentType`     | string | Yes      | MIME type sent on the subsequent PUT (e.g. `application/pdf`).                              |

**Response fields:**

| Field              | Type   | Description                                                                                                                  |
| ------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `uploadUrl`        | string | Presigned S3 URL. PUT the file bytes to this URL with `Content-Type` matching `contentType`.                                 |
| `s3Key`            | string | The key to pass back as `s3_key` on invoke.                                                                                  |
| `expiresInSeconds` | number | URL lifetime in seconds (default 3600).                                                                                      |
| `attachment`       | object | Ready-to-use attachment object containing `s3_key`, `type`, `name` — pass this directly in the invoke `attachments[]` array. |

```shell theme={null}
curl -s -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke/attachments/upload-url" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "uploadSessionId": "my-session-1",
    "filename": "report.pdf",
    "contentType": "application/pdf"
  }'
```

Response:

```json theme={null}
{
  "uploadUrl": "https://s3.amazonaws.com/braintrust-uploads/invoke-uploads/you@example.com/my-session-1/1716300000000-report.pdf?...",
  "s3Key": "invoke-uploads/you@example.com/my-session-1/1716300000000-report.pdf",
  "expiresInSeconds": 3600,
  "attachment": {
    "s3_key": "invoke-uploads/you@example.com/my-session-1/1716300000000-report.pdf",
    "type": "application/pdf",
    "name": "report.pdf"
  }
}
```

<Tip>
  Reuse the same `uploadSessionId` across multiple files in one batch. Call `upload-url` once per file; each response gives a unique `s3Key`.
</Tip>

***

## Streaming Mode (Default)

When `stream` is omitted or `true`, the response is a **Server-Sent Events (SSE)** stream at HTTP 200 with `Content-Type: text/event-stream`.

Lines beginning with `data:` are JSON payloads. Lines beginning with `: keepalive` are heartbeat comments — ignore them when parsing.

Streaming responses include an **`X-Request-Id`** response header containing the server-generated request correlation ID.

<Tip>
  Log `X-Request-Id` from every streaming response alongside your `task_id` and `thread_token`. If a request behaves unexpectedly, the `X-Request-Id` value lets support engineers locate the exact server-side trace without needing full request payloads.
</Tip>

### Event sequence

**1. `meta` — always the first event**

```
data: {"type":"meta","thread_token":"<uuid>","task_id":"<uuid>","request_id":"<uuid>","conversation_message_id":"<id>"}
```

**2. `chunk` — zero or more token events**

```
data: {"type":"chunk","actor":"Agent Display Name","text":"..."}
```

**3. `done` — terminal success**

Single agent:

```
data: {"type":"done","status":"completed","output":"...","thread_token":"<uuid>","task_id":"<uuid>","request_id":"<uuid>","agent":"Agent Display Name"}
```

Multi-agent collaboration:

```
data: {"type":"done","status":"completed","output":"...","thread_token":"<uuid>","task_id":"<uuid>","request_id":"<uuid>","agents_used":["Agent A","Agent B"]}
```

**On failure:**

```
data: {"type":"error","error":"...","thread_token":"<uuid>","task_id":"<uuid>","request_id":"<uuid>"}
```

Every successful invoke turn gets a UUID `request_id` echoed in `meta`, `done`, async `202`, poll responses, and the `X-Request-Id` response header. Save it to look up the LangSmith trace timeline (see [Traces](#traces-and-debugging)).

### Example

```shell theme={null}
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent":"AGENT_ID","message":"What can you help me with?"}'
```

<Tip>
  If you see `meta` → `chunk` lines → `done`, your integration is working. Save the `thread_token` from `meta` or `done` for follow-up messages.
</Tip>

***

## Async Mode (`stream: false`)

When `stream` is `false`, the server returns **202 Accepted** immediately with a `task_id` you can poll.

### Step 1 — Create the async task

```shell theme={null}
curl -s -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent":"AGENT_ID","message":"Summarize this project.","stream":false}'
```

Response:

```json theme={null}
{
  "status": "accepted",
  "task_id": "d7c615f6-...",
  "request_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status_url": "/api/agents/invoke/d7c615f6-...",
  "thread_token": "a1b2c3d4-...",
  "conversation_message_id": "..."
}
```

| Field                     | Meaning                                                                                                                                                                                                                             |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`                  | Always `"accepted"` on 202                                                                                                                                                                                                          |
| `task_id`                 | Use to poll `/api/agents/invoke/{task_id}`                                                                                                                                                                                          |
| `request_id`              | UUID correlation ID for this turn — echoed in `meta`/`done` events, this 202 body, poll responses, and the `X-Request-Id` streaming response header. Use it with the trace timeline endpoint (see [Traces](#traces-and-debugging)). |
| `status_url`              | Relative URL for polling; prepend the base URL                                                                                                                                                                                      |
| `thread_token`            | Conversation continuity handle for follow-up messages                                                                                                                                                                               |
| `conversation_message_id` | Internal message identifier                                                                                                                                                                                                         |

### Step 2 — Poll for the result

```shell theme={null}
curl -s "https://braintrust.ti.trilogy.com/api/agents/invoke/d7c615f6-..." \
  -H "x-api-key: YOUR_API_KEY"
```

Prepend the base URL to `status_url` to get the full polling URL.

**Possible `status` values:** `queued`, `running`, `completed`, `failed`, `timeout`, `canceled`

| Status                   | Response includes                                                        |
| ------------------------ | ------------------------------------------------------------------------ |
| `completed` or `timeout` | `output`, `agent` or `agents_used`, `duration_ms`                        |
| `failed`                 | `error`, `duration_ms`                                                   |
| `canceled`               | includes `output` (empty string if no output was produced before cancel) |

### Step 3 — Cancel (optional)

Cancel any `queued` or `running` task (including a streaming run, using its `task_id` from `meta`):

```shell theme={null}
curl -s -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke/{task_id}/cancel" \
  -H "x-api-key: YOUR_API_KEY"
```

Cancel is best-effort. On success you get **200**:

```json theme={null}
{ "task_id": "...", "status": "canceled", "cancel_applied": true }
```

If the task is already in a terminal state, you still get **200** but with `cancel_applied: false` and a `reason` field:

| reason                            | When it appears                                      |
| --------------------------------- | ---------------------------------------------------- |
| `already_terminal`                | Task already `completed`, `failed`, or `timeout`.    |
| `already_canceled`                | A prior cancel call already applied.                 |
| `already_completed_or_not_active` | Run finished or no longer active in the worker pool. |

Other statuses:

| Status | Meaning                                                             |
| ------ | ------------------------------------------------------------------- |
| `409`  | Record exists but cannot be canceled (missing internal message id). |
| `404`  | The `task_id` is not yours / not found.                             |

***

## Thread Continuity

`thread_token` keeps messages connected into a single conversation thread.

| Scenario                      | What to do                                                                          |
| ----------------------------- | ----------------------------------------------------------------------------------- |
| **First message, new thread** | Omit `thread_token` — server mints a UUID and returns it in `meta` / `done` / `202` |
| **Bring your own ID**         | Send any stable opaque string (ticket ID, session ID, etc.) — it is accepted as-is  |
| **Follow-up message**         | Send the same `thread_token` used on the first turn                                 |
| **Start over**                | Omit `thread_token` or use a new custom string                                      |

<Warning>
  If you change the `agents` array while reusing the same `thread_token`, the conversation will not continue from the previous thread. Keep the same agent roster across follow-up messages to maintain continuity.
</Warning>

### Multi-turn example

```shell theme={null}
# First message — server mints the thread_token
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent":"AGENT_ID","message":"Summarise last week sales data"}'

# Follow-up — continue the same thread
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent":"AGENT_ID","thread_token":"<token-from-first-response>","message":"Now break that down by region"}'
```

***

## Multi-Agent Collaboration

Use the `agents` array (not `agent`) to orchestrate multiple experts in a single turn.

```shell theme={null}
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agents":["archivist","prompt-to-skills"],"message":"Each introduce yourself."}'
```

* The `done` event and poll responses include **`agents_used`** (display names of agents that contributed), not `agents`.
* Keep the same `agents` roster and `thread_token` on follow-ups for continuity.

***

## Delivery Options

### Deliver to a Google Chat Space

```shell theme={null}
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent":"AGENT_ID",
    "message":"Post this update in the space.",
    "deliver_to":{"gchat_space_id":"spaces/SPACE_ID"}
  }'
```

Requirement: the BrainTrust bot must be a member of that space.

### Deliver as DM from the agent

```shell theme={null}
curl -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent":"AGENT_ID",
    "message":"Send this as a DM.",
    "deliver_to":{"agent_dm":true}
  }'
```

### Delivery diagnostics

Streaming `done` events may include `delivery_error` (string) when posting to the configured destination failed, or `delivered_to` (object) confirming where the result was sent. Polled GET task-status responses do NOT carry a separate `delivery_error` field on success — use streaming if you need delivery diagnostics in the HTTP body.

***

## Read Endpoints

### Task status

**GET** `/api/agents/invoke/{task_id}`

Retrieve status and output for any invocation (streaming or async) by `task_id`. The `task_id` is returned in the streaming `meta` event and in the async `202` response.

```shell theme={null}
curl -s "https://braintrust.ti.trilogy.com/api/agents/invoke/TASK_ID" \
  -H "x-api-key: YOUR_API_KEY"
```

Response example:

```json theme={null}
{
  "task_id": "d7c615f6-...",
  "request_id": "req_7e3a1b...",
  "status": "completed",
  "output": "...",
  "agent": "Agent Display Name",
  "duration_ms": 4231
}
```

| Field                    | Meaning                                                                                                        |
| ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `task_id`                | Identifies the invocation                                                                                      |
| `request_id`             | Correlation ID matching the `request_id` in the original 202 response and `X-Request-Id` on streaming requests |
| `status`                 | One of `queued`, `running`, `completed`, `failed`, `timeout`, `canceled`                                       |
| `output`                 | Present when `status` is `completed` — expert's response text                                                  |
| `agent` or `agents_used` | Present when `status` is `completed` — display name(s) of the expert(s) that responded                         |
| `duration_ms`            | Present when `status` is `completed` or `failed` — wall-clock time from task creation to completion            |
| `error`                  | Present when `status` is `failed` — error message                                                              |

### Invocation history

**GET** `/api/invocations`

```shell theme={null}
curl -s "https://braintrust.ti.trilogy.com/api/invocations?limit=10&agent=archivist" \
  -H "x-api-key: YOUR_API_KEY"
```

Query parameters:

| Param   | Description        |
| ------- | ------------------ |
| `limit` | 1–100, default 20  |
| `agent` | Filter by agent ID |

Response shape: `{ "invocations": [...] }` — each item includes `task_id`, `status`, `thread_token`, `mode`, `input`, `output`, and timestamps.

***

## Traces and Debugging

Every invoke turn returns a UUID `request_id` (in `meta`/`done`, the async 202 body, poll responses, and the `X-Request-Id` response header). Use it to fetch the LangSmith trace timeline for that turn. Same `x-api-key` as invoke; you must have hired the expert.

**GET** `/api/agents/{agent_id}/traces/{request_id}/timeline`

Returns run header fields (`name`, `status`, `start_time`, `end_time`, `error`) and an interleaved `timeline` of model and tool events. When `truncated` is `true`, the trace exceeded internal fetch limits.

```shell theme={null}
curl -s "https://braintrust.ti.trilogy.com/api/agents/archivist/traces/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/timeline" \
  -H "x-api-key: YOUR_API_KEY"
```

| Status | Meaning                                                                   |
| ------ | ------------------------------------------------------------------------- |
| `400`  | `request_id` is not a valid UUID.                                         |
| `403`  | No expert access, or the run belongs to another user.                     |
| `404`  | Unknown expert, or no LangSmith run for that `request_id` on this expert. |

<Info>
  LangSmith indexing can lag a few seconds after invoke completes — retry if you get 404 immediately after `done`.
</Info>

***

## Error Responses

| Status | Typical reason                                                                                                                                                   | Fix                                                                                                                                                                                                                       |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Bad JSON, missing `message`, invalid `agent`/`agents`, invalid `attachments` (wrong `s3_key`, extra fields, or more than 20 files), or unsupported extra fields. | Check JSON, `message`, and `agent`/`agents` usage. Verify each attachment's `s3_key` starts with `invoke-uploads/` and came from the upload-url endpoint. Remove unsupported flags like `async` or `wait_for_completion`. |
| `401`  | Missing or invalid API key                                                                                                                                       | Check the `x-api-key` header name and key value                                                                                                                                                                           |
| `403`  | Agent not hired or inactive                                                                                                                                      | Hire the agent first; response may include `hire_url`                                                                                                                                                                     |
| `404`  | Wrong agent ID or task ID                                                                                                                                        | Verify the IDs                                                                                                                                                                                                            |
| `409`  | Cancel not allowed for this task state — record exists but is in a terminal state or has no stored internal message id.                                          | Use the cancel endpoint only for `queued` or `running` tasks; check `cancel_applied` and `reason` in the 200 response for diagnostic detail.                                                                              |
| `500`  | Internal server error                                                                                                                                            | Retry; contact support if repeated                                                                                                                                                                                        |
| `502`  | Conversation backend unavailable                                                                                                                                 | Retry shortly                                                                                                                                                                                                             |

***

## Windows PowerShell

Use `curl.exe` — PowerShell's built-in `curl` is an alias for `Invoke-WebRequest` and is not compatible.

```
curl.exe -s -N -X POST "https://braintrust.ti.trilogy.com/api/agents/invoke" `
  -H "x-api-key: YOUR_API_KEY" `
  -H "Content-Type: application/json" `
  -d '{""agent"":""AGENT_ID"",""message"":""Say hello in one sentence.""}'
```

***

## Best Practices

* Start with streaming mode (`-N` flag) and parse the initial `meta` event before any `chunk` lines.
* Move to async mode only when your system needs job-style polling.
* Keep agent IDs lowercase with hyphens.
* Add `context` for better, more relevant answers.
* Save `task_id` for traceability and `thread_token` for conversation continuity.
* On multi-turn flows, always send the **current** `thread_token`, not a stale one from a prior segment.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Managing API Keys" icon="key" href="/setup/managing-api-keys">
    Create and manage your API keys.
  </Card>

  <Card title="Triggers" icon="clock" href="/automation/triggers">
    Automate expert invocations on a schedule.
  </Card>
</CardGroup>
