Relay docs

Agents API

POST /v1/agents/{id}/chat/completions — run a stored agent. The request body is a normal chat completion payload, but the agent supplies the model, system prompt (free-text or a library prompt), skills, tools/MCP and optional knowledge-base retrieval. The model field in your body is ignored.

See Agents for how to compose one.

An agent whose model is realtime-capable can also be spoken to — see Realtime.

Three kinds of agent need the acting end user in X-Relay-User-Id (an Entra object id; X-User-Id is accepted as a legacy alias), and it is the same header in every case, so an agent that is two of these needs one header, not two:

  • A dataset agent, which also needs the data.query scope on the key, cannot stream, and returns a relay_dataset object with the SQL that ran and the rows it returned. See Dataset agents.
  • A connector agent, whose tools read that user's own Microsoft 365 account and which needs the connectors.use scope. See Connectors.
  • An agent that is not public, where the gateway checks the user against the agent's creator and its share list. See Agents for how the audience is set and why that id and not another.

Without the header these return 400 user_header_required; without the scope, 403 insufficient_scope.

#List agents

GET /v1/agents — the agents this key can run, so an app can discover them instead of hard-coding ids.

curl http://localhost:5300/v1/agents \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "X-Relay-User-Id: $END_USER_ID"

The user header is optional here even though a private agent's run requires it: listing without one returns the public agents, which is the useful answer for a client that has not yet picked a user.

{ "object": "list", "data": [
  {
    "id": "a1b2c3…",
    "name": "support-assistant",
    "description": "Answers returns and delivery questions",
    "model": "gpt-4o",
    "model_allowed": true,
    "realtime": false,
    "published_version": 3,
    "grounded": true,
    "retrieval_top_k": 4,
    "skills": 2,
    "tools": 1,
    "mcp_servers": 0,
    "dataset": false,
    "connectors": false,
    "public": true,
    "requires_user_header": false,
    "user_header": null,
    "temperature": 0.3,
    "max_tokens": 800,
    "pii_redaction": false,
    "web_search": false
  }
] }

Only enabled agents appear, and only those that are global (no workspace) or owned by the key's workspace — the same visibility rule as the prompt library. Non-public agents appear only when X-Relay-User-Id names their creator or a user they are shared with.

A few fields are worth knowing:

FieldMeaning
model_allowedfalse when the key's model allow-list excludes the agent's model, so a run would return 403 model_not_allowed. Reported rather than filtered on — an agent hidden for this reason looks deleted, while an unexplained 403 looks like a bug.
realtimeWhether the agent's model speaks the bidirectional-audio protocol, so the agent can be opened as a voice session. Reported here because the agent's model may not be in the key's own GET /v1/models view, so an app offering a "talk to an agent" picker could otherwise only discover the answer from a 400 unsupported_capability after the user had already chosen. Filter on this, unlike model_allowed — an agent that cannot be spoken to is not a permissions problem to explain, it simply has no voice option.
published_versionThe published version that will actually run. Null means the live draft is in effect, because a run prefers a published snapshot whenever one exists.
groundedWhether a knowledge base is attached. retrieval_top_k is present only when it is — otherwise it is a stored default that never takes effect.
publicfalse when the agent is shared with named users rather than open to every caller. Because the listing already omits the ones this user may not run, a false here means "you can see this because the user you named created it or was shared it", not "you might be refused".
dataset / connectorsWhether a dataset is pinned / a connector is attached — the two kinds of agent that act as the end user and need an extra scope.
requires_user_headerWhether a run needs X-Relay-User-Id. True for a dataset agent (to resolve per-user data access), a connector agent (its tools act as that user) or a non-public one (to check the share list); any one reason is enough, and user_header gives the header name.
web_searchWhether the agent can search the web during a run. Needs no extra scope or header. See Agents.
skills / tools / mcp_serversCounts, not ids.

This is a directory, not a definition: the system prompt, the prompt id and the skill / tool / MCP ids are deliberately not returned. They are authoring detail, the prompt can carry sensitive instructions, and the ids name resources that may belong to another team. Nothing but id is needed to invoke an agent. Compose and inspect agents in the panel under Agents.

#Request

curl -X POST http://localhost:5300/v1/agents/AGENT_ID/chat/completions \
  -H "Authorization: Bearer $RELAY_API_KEY" -H "Content-Type: application/json" \
  -d '{
        "messages": [{ "role": "user", "content": "How do I return an item?" }],
        "stream": false
      }'
  • The agent's settings win. Its model replaces whatever model you send, its temperature and max tokens apply as defaults, and if it has tools of its own they replace any tools in your body.
  • The message is optional. If the agent's prompt already contains the user turn (a chat-type prompt), or the system prompt is self-contained, you can omit it.
  • Variables. If the agent's prompt has {{variables}}, pass them as a top-level variables object; the gateway compiles the system prompt before running (the variables are not forwarded upstream):
{ "messages": [{ "role": "user", "content": "…" }],
  "variables": { "company": "Spinneys", "language": "English" } }
  • Attachments. A top-level file_ids array names uploaded files whose text is injected after any retrieved knowledge; the agent's PII redaction applies to it. An unreadable id fails the run with 404 file_not_found rather than being skipped.
  • Images go in as content parts exactly as on chat completions. Thread history keeps text only, so an image is not remembered on the next turn.

#Conversation memory

Add ?thread=<id> to give the agent multi-turn memory. Prior turns for that thread are prepended, and this turn is persisted afterwards (only on success, and only role + content). The response includes X-Relay-Thread-Id, and the thread id doubles as the trace's session id unless you sent X-Relay-Session-Id yourself.

POST /v1/agents/{id}/chat/completions?thread=my-thread-123

Threads are visible in the panel under Conversations.

#Tools & MCP

If the agent has MCP servers, workflows, a connector or a dataset attached, the gateway runs a tool loop: it calls the model, executes any requested tool itself, feeds the result back, and repeats. The budget is Gateway:ToolMaxIterations (default 5) for MCP and workflow tools, Gateway:Connectors:MaxIterations (default 8) for a connector agent and Gateway:Data:MaxIterations (default 6) for a dataset agent. Plain registry tools (no backend of their own) are advertised to the model, and their tool_calls are returned to you to execute. A registry tool whose name collides with an attached workflow or connector tool fails the run with 409 tool_name_conflict. See Skills, tools & MCP.

#Streaming vs. tool loop

  • An agent with none of the above (no MCP, workflow, connector or dataset) and no ?thread= streams like a normal chat call.
  • Otherwise the run is non-streamed and returns a single JSON completion: stream: true is ignored, except for a dataset agent, which refuses it with 400 stream_not_supported so the caller knows.

#Response

Standard chat completion shape, plus relay_web_search on a non-streamed run of a web search agent that searched (queries, pages found, citations and billed search count, summed over the tool loop — the shape is in Chat completions), plus X-Relay-Request-Id, X-Relay-Trace-Id, X-Relay-Observation-Id, X-Relay-Cost-Usd (non-streamed runs only), X-Relay-Cache when caching is on, X-Relay-Thread-Id for threads, and X-Relay-Dataset-Id / X-Relay-Sql-Queries / X-Relay-Rls-Applied for a dataset agent.