Skip to main content
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 for a step-by-step guide.
Never commit API keys to source control or log them in plain text. Treat them like passwords.

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

FieldTypeRequiredDescription
agentstringYes*Single agent ID (lowercase, hyphenated — from the expert’s page)
agentsstring[]Yes*Array of agent IDs for multi-agent collaboration
messagestringYesNon-empty user message
thread_tokenstringNoConversation continuity handle (see Thread Continuity)
streambooleanNoDefault true. Set false for async job mode
contextstringNoExtra background context injected into the conversation
attachmentsAttachment[]NoArray 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 for the per-entry schema.
deliver_to.gchat_space_idstringNoDeliver output to a Google Chat Space (e.g. spaces/AAQAvU5RLdI)
deliver_to.agent_dmbooleanNoDeliver output as a DM from the agent’s identity
* Use either agent or agents, never both.
Fields async and wait_for_completion are not supported — use stream only.

Attachments

Each entry in the attachments array is an object with the following fields:
FieldTypeRequiredDescription
s3_keystringYesMust start with invoke-uploads/ and come from the presigned-URL flow below. Arbitrary S3 paths return 400.
typestringYesMIME type of the uploaded file (e.g. application/pdf).
namestringYesOriginal basename of the file.
No other fields are accepted — extra keys return 400. Maximum 20 attachments per invoke.
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.
JSON request body example with attachments:
{
  "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:
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:
FieldTypeRequiredDescription
uploadSessionIdstringYesStable client ID for the upload batch (e.g. UUID); scopes S3 keys per user. Note camelCase.
filenamestringYesBasename only — no / or ...
contentTypestringYesMIME type sent on the subsequent PUT (e.g. application/pdf).
Response fields:
FieldTypeDescription
uploadUrlstringPresigned S3 URL. PUT the file bytes to this URL with Content-Type matching contentType.
s3KeystringThe key to pass back as s3_key on invoke.
expiresInSecondsnumberURL lifetime in seconds (default 3600).
attachmentobjectReady-to-use attachment object containing s3_key, type, name — pass this directly in the invoke attachments[] array.
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:
{
  "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"
  }
}
Reuse the same uploadSessionId across multiple files in one batch. Call upload-url once per file; each response gives a unique s3Key.

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.
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.

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).

Example

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?"}'
If you see metachunk lines → done, your integration is working. Save the thread_token from meta or done for follow-up messages.

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

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:
{
  "status": "accepted",
  "task_id": "d7c615f6-...",
  "request_id": "req_7e3a1b...",
  "status_url": "/api/agents/invoke/d7c615f6-...",
  "thread_token": "a1b2c3d4-...",
  "conversation_message_id": "..."
}
FieldMeaning
statusAlways "accepted" on 202
task_idUse to poll /api/agents/invoke/{task_id}
request_idUUID 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).
status_urlRelative URL for polling; prepend the base URL
thread_tokenConversation continuity handle for follow-up messages
conversation_message_idInternal message identifier

Step 2 — Poll for the result

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
StatusResponse includes
completed or timeoutoutput, agent or agents_used, duration_ms
failederror, duration_ms
canceledincludes 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):
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:
{ "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:
reasonWhen it appears
already_terminalTask already completed, failed, or timeout.
already_canceledA prior cancel call already applied.
already_completed_or_not_activeRun finished or no longer active in the worker pool.
Other statuses:
StatusMeaning
409Record exists but cannot be canceled (missing internal message id).
404The task_id is not yours / not found.

Thread Continuity

thread_token keeps messages connected into a single conversation thread.
ScenarioWhat to do
First message, new threadOmit thread_token — server mints a UUID and returns it in meta / done / 202
Bring your own IDSend any stable opaque string (ticket ID, session ID, etc.) — it is accepted as-is
Follow-up messageSend the same thread_token used on the first turn
Start overOmit thread_token or use a new custom string
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.

Multi-turn example

# 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.
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

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

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.
curl -s "https://braintrust.ti.trilogy.com/api/agents/invoke/TASK_ID" \
  -H "x-api-key: YOUR_API_KEY"
Response example:
{
  "task_id": "d7c615f6-...",
  "request_id": "req_7e3a1b...",
  "status": "completed",
  "output": "...",
  "agent": "Agent Display Name",
  "duration_ms": 4231
}
FieldMeaning
task_idIdentifies the invocation
request_idCorrelation ID matching the request_id in the original 202 response and X-Request-Id on streaming requests
statusOne of queued, running, completed, failed, timeout, canceled
outputPresent when status is completed — expert’s response text
agent or agents_usedPresent when status is completed — display name(s) of the expert(s) that responded
duration_msPresent when status is completed or failed — wall-clock time from task creation to completion
errorPresent when status is failed — error message

Invocation history

GET /api/invocations
curl -s "https://braintrust.ti.trilogy.com/api/invocations?limit=10&agent=archivist" \
  -H "x-api-key: YOUR_API_KEY"
Query parameters:
ParamDescription
limit1–100, default 20
agentFilter 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.
curl -s "https://braintrust.ti.trilogy.com/api/agents/archivist/traces/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/timeline" \
  -H "x-api-key: YOUR_API_KEY"
StatusMeaning
400request_id is not a valid UUID.
403No expert access, or the run belongs to another user.
404Unknown expert, or no LangSmith run for that request_id on this expert.
LangSmith indexing can lag a few seconds after invoke completes — retry if you get 404 immediately after done.

Error Responses

StatusTypical reasonFix
400Bad 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.
401Missing or invalid API keyCheck the x-api-key header name and key value
403Agent not hired or inactiveHire the agent first; response may include hire_url
404Wrong agent ID or task IDVerify the IDs
409Cancel 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.
500Internal server errorRetry; contact support if repeated
502Conversation backend unavailableRetry 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

Managing API Keys

Create and manage your API keys.

Triggers

Automate expert invocations on a schedule.