Overview
Every agent you build in Urai has its own OpenAI-compatible Responses endpoint. You point an OpenAI SDK at that agent, send an input, and get the finished answer back. The agent brings its own model, its own instructions, its own tools, and its own knowledge collections.
This is the same agent that answers in the chat widget and in the Urai interface. A request through this API runs the same pipeline, so the agent behaves the same way at each door.
When to use this API
Section titled “When to use this API”| You want | Use |
|---|---|
| One agent, configured in the browser, called from your code | This API |
| A model of your choice, with tools and collections named per request | Chat Completions API |
| An agent in your product interface | The widget |
Turn the API on for an agent
Section titled “Turn the API on for an agent”The API is off for every agent until you turn it on. Open the agent in Urai and
enable the Responses API on its settings page. The field is
responses_api_enabled on the agent object.
Read the security note before you enable an agent that was written for the widget.
Base URL
Section titled “Base URL”https://chat.app.urai.dev/api/agents/{agent_id}/v1agent_id is the UUID of the agent. You find it in the address bar when you
open the agent in Urai, and in the id field of
GET /api/projects/{project_id}/assistants.
Every path on this page is relative to that base URL.
Authenticate
Section titled “Authenticate”Send an organization API key as a bearer token. The API takes the same
sk-urai-... keys as the Chat Completions API. There is no separate token for
an agent.
Authorization: Bearer sk-urai-...To create a key, open API Keys in the Urai sidebar. Only organization owners and admins see this page. Urai shows the value one time.
Keep the key on your server. Do not put a key in browser code.
Who can reach an enabled agent
Section titled “Who can reach an enabled agent”An enabled agent answers any valid key in its organization. A key that is scoped to a project reaches only the agents in that project.
This is why the API is off by default. An agent that you wrote for the widget has instructions and tools that you chose for that audience. Enable the API after you check that the agent is safe for anyone who holds an organization key.
Three errors cover the checks:
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_agent_id |
The path segment is not a UUID. |
| 404 | agent_not_found |
No such agent, or the agent belongs to another organization or another project. |
| 403 | agent_api_disabled |
The agent exists and is yours, but the API is off for it. |
An agent in another organization returns 404 and not 403, so a caller cannot
learn that the id names a real agent.
Make the first request
Section titled “Make the first request”curl https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent", "input": "Where is order 4821?" }'The same request with the OpenAI Node SDK:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: `https://chat.app.urai.dev/api/agents/${agentId}/v1`, apiKey: "sk-urai-...",});
const response = await client.responses.create({ model: "agent", input: "Where is order 4821?",});
console.log(response.output_text);And with the OpenAI Python SDK:
from openai import OpenAI
client = OpenAI( base_url=f"https://chat.app.urai.dev/api/agents/{agent_id}/v1", api_key="sk-urai-...",)
response = client.responses.create( model="agent", input="Where is order 4821?",)
print(response.output_text)model is not read. The SDKs make the field mandatory, so send any string.
Read the response
Section titled “Read the response”{ "id": "resp_9f2c1e40-1b7c-4c65-9a30-51a6b0f2c3d1", "object": "response", "created_at": 1710000000, "status": "completed", "error": null, "model": "anthropic/claude-haiku-4-5", "output": [ { "type": "function_call", "id": "fc_5b3e...", "call_id": "call_1", "name": "orders_lookup", "arguments": "{\"order\":\"4821\"}", "status": "completed" }, { "type": "message", "id": "msg_9f2c1e40-1b7c-4c65-9a30-51a6b0f2c3d1", "role": "assistant", "status": "completed", "content": [ { "type": "output_text", "text": "Order 4821 shipped on Tuesday.", "annotations": [] } ] } ], "usage": { "input_tokens": 1204, "input_tokens_details": { "cached_tokens": 900 }, "output_tokens": 86, "output_tokens_details": { "reasoning_tokens": 41 }, "total_tokens": 1290 }, "metadata": {}, "thread_id": "0723cee0-3912-434e-8450-2c701e6f9713"}The output array holds the work of the whole turn, in the order it happened:
the reasoning summary, then one function_call item for each tool the agent
ran, then the message. usage covers every round of that turn.
thread_id is a Urai addition. It names the stored conversation. See
Conversations.
model reports the model the turn ran on, in the form provider/model. The
agent owns that choice.
A request waits up to 10 minutes for the turn to finish. An agent that runs many tool rounds can take a while. To watch the work as it happens, stream it.
What the agent owns
Section titled “What the agent owns”The agent already holds the model, the system instructions, the tools, the skills, and the knowledge collections. You do not send them.
Fields that name those things fall into two groups.
Accepted and ignored. model, store, and any other OpenAI field that
Urai does not read. Their absence changes nothing you can observe, so a request
built by a typed SDK works without a change.
Rejected with 400. tools, tool_choice, and text.format with a value
other than text. Urai does not drop these quietly. A dropped function tool
looks like a model that never calls tools, which is hard to diagnose from the
outside.
To change what the agent can do, change the agent.
What you can send
Section titled “What you can send”| Field | Purpose |
|---|---|
input |
The turn. A string, or a list of message items. |
instructions |
Extra instructions for this request. Urai appends them after the agent’s own. |
stream |
Return Server-Sent Events. |
user |
Your identifier for the end user. It isolates stored conversations. |
previous_response_id |
Continue the conversation that produced that response. |
vars |
Context for this request. Your UraiJS tools read it. |
metadata |
An alias for vars, for SDKs with no way to send an unknown field. |
reasoning.effort |
The reasoning effort, where the model supports it. |
attachments |
File ids to attach to the turn. |
Conversations covers input, previous_response_id,
user, vars, and attachments in full.
List the model
Section titled “List the model”curl https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/models \ -H "Authorization: Bearer $URAI_API_KEY"{ "object": "list", "data": [ { "id": "anthropic/claude-haiku-4-5", "object": "model", "created": 1710000000, "owned_by": "Support agent" } ]}The list holds one entry, because the agent runs on one model. owned_by is the
name of the agent.
Handle errors
Section titled “Handle errors”Errors use the OpenAI envelope and the matching HTTP status.
{ "error": { "message": "An agent brings its own tools. Configure them on the agent instead of sending them per request.", "type": "invalid_request_error", "param": "tools", "code": "tools_not_supported_for_agent" }}A turn that fails after it starts does not return an HTTP error. The response
comes back with "status": "failed" and an error object, because the turn ran
and cost you tokens.
The Reference page lists every code.
Next steps
Section titled “Next steps”- Conversations. Continue a thread, keep end users apart, pass context, and attach files.
- Streaming. Every event, in the order Urai sends it.
- Reference. Every endpoint, field, object, and error code.