Conversations
The Responses API is stateful. Every response belongs to a stored thread, and Urai adds the earlier turns for you. You send the new message only.
store is always true on this surface. The thread is the state that makes
previous_response_id work, so there is no unstored mode.
Continue a conversation
Section titled “Continue a conversation”Send the id of the last response as previous_response_id.
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", "previous_response_id": "resp_9f2c1e40-1b7c-4c65-9a30-51a6b0f2c3d1", "input": "And when does it arrive?" }'const first = await client.responses.create({ model: "agent", input: "Where is order 4821?",});
const second = await client.responses.create({ model: "agent", previous_response_id: first.id, input: "And when does it arrive?",});A response id has the form resp_<uuid>. Urai also accepts the bare UUID, for a
client that stores the id without its prefix.
An id that is not a UUID returns 400 with the code invalid_response_id. An id
that names no response you can read returns 404 with the code
response_not_found.
Address the thread instead
Section titled “Address the thread instead”Every response carries the thread in a header:
x-thread-id: 0723cee0-3912-434e-8450-2c701e6f9713Send that header back to continue the same thread. This suits a client that tracks the conversation rather than the last response.
curl https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -H "x-thread-id: 0723cee0-3912-434e-8450-2c701e6f9713" \ -d '{ "model": "agent", "input": "And when does it arrive?" }'The x-thread-id header is a Urai extension, and it has the same name and
meaning on the Chat Completions API.
A header that is not a UUID returns 400 with the code invalid_thread_id.
Urai does not start a new thread instead, because a conversation that is lost
without a message is worse than a request that fails.
When you send neither previous_response_id nor x-thread-id, Urai starts a
new thread.
Keep end users apart
Section titled “Keep end users apart”Set user to a stable identifier when one API key serves several end users.
"user": "customer-4821"A thread belongs to the exact triple of API key, user value, and agent. To
continue a thread, send the same user value that created it. A different value
returns 404, so one end user cannot learn that another user’s conversation
exists.
When you omit user, every such call shares one bucket for that key. A request
made by a workflow run defaults to the
principal wf:<run_id>, so each run keeps its own thread.
The GET, DELETE, and cancel routes carry no body. Send the principal as a
query parameter on those:
GET /responses/resp_9f2c1e40-.../input_items?user=customer-4821Send more than one message
Section titled “Send more than one message”input takes a string, or a list of items.
{ "model": "agent", "input": [ { "role": "user", "content": "I ordered a lamp last week." }, { "role": "assistant", "content": "Thanks. What is the order number?" }, { "role": "user", "content": "It is 4821." } ]}The last user message drives the turn. Everything before it is written into
the thread as history first. Use this to seed a new thread with a conversation
that happened somewhere else.
Four rules apply:
- The list must hold at least one
usermessage. An empty list, or a list with no user message, returns400with the codeinvalid_input. - The last
usermessage must carry text or an attachment. systemanddevelopermessages are not stored. Send instructions in theinstructionsfield instead, on each request that needs them.- An item with a
typeother thanmessageis skipped. Tools run on the server, so a function call item that you echo back is already in the thread.
An unknown role returns 400 with the code invalid_input.
Add instructions for one request
Section titled “Add instructions for one request”instructions goes after the agent’s own system instructions. It does not
replace them.
{ "model": "agent", "instructions": "Answer in Portuguese. Keep it to two sentences.", "input": "Where is order 4821?"}The value applies to that request only. Urai does not store it on the thread.
A response echoes back your instructions and never the agent’s own. The
instructions that the author of the agent wrote stay on the server.
Pass context to your tools
Section titled “Pass context to your tools”vars carries context for the turn. Your UraiJS tools read it as meta.vars,
layered over the vars set on the agent. Use it for the identity of the caller,
the current page, a tenant id, or anything else the tools need.
{ "model": "agent", "vars": { "customer_id": "4821", "plan": "business" }, "input": "What is my delivery window?"}Urai writes the vars to the thread before the turn starts, so the tools of that same turn see them.
metadata is an alias for vars. Use it when your SDK exposes metadata as a
typed field and gives you no way to send an unknown one. When both set the same
key, vars wins.
Three rules apply:
- The value must be a JSON object. Anything else returns
400with the codeinvalid_vars. - The merged object holds at most 32 keys.
- A request that sends neither field leaves the vars of the thread as they are. To clear a key, send the field again without it.
The metadata field of a response reports the vars of the thread.
Attach files
Section titled “Attach files”Attachments reach the last user message. Two ways to send them.
Upload first with POST /api/openai/v1/files, then
send the ids:
{ "model": "agent", "attachments": ["9d4c1f7e-1a2b-4c3d-8e5f-6a7b8c9d0e1f"], "input": "What does this invoice total?"}Or send the bytes inline as content parts:
{ "model": "agent", "input": [ { "role": "user", "content": [ { "type": "input_text", "text": "What does this invoice total?" }, { "type": "input_file", "filename": "invoice-2041.pdf", "file_data": "data:application/pdf;base64,JVBERi0xLjQK..." } ] } ]}An image works the same way with input_image:
{ "type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo..." }Or by id, when you uploaded it first:
{ "type": "input_image", "file_id": "9d4c1f7e-1a2b-4c3d-8e5f-6a7b8c9d0e1f" }Four rules apply:
image_urlmust be a base64data:URL. Urai does not fetch remote URLs.- One request carries at most 16 attachments.
- One attachment holds at most 25 MiB after decoding.
- A file id belongs to the API key and the
uservalue that uploaded it.
The file rules and the error codes are the same as on the Chat Completions API. See Send files and images.
Read a stored response
Section titled “Read a stored response”curl https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses/$RESPONSE_ID \ -H "Authorization: Bearer $URAI_API_KEY"You get the same response object. A turn that is still running reports
"status": "in_progress" and holds the text that arrived so far.
instructions and user come back as null here. Urai does not store the
per-request values, and it never echoes the instructions of the agent.
List what led to a response
Section titled “List what led to a response”curl "https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses/$RESPONSE_ID/input_items" \ -H "Authorization: Bearer $URAI_API_KEY"{ "object": "list", "data": [ { "id": "msg_1c9a...", "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "Where is order 4821?" }] } ], "first_id": "msg_1c9a...", "last_id": "msg_1c9a...", "has_more": false}The list holds the user and assistant messages of the thread up to that response, and it is never paged.
Stop a running turn
Section titled “Stop a running turn”curl -X POST https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses/$RESPONSE_ID/cancel \ -H "Authorization: Bearer $URAI_API_KEY"The call returns the response with "status": "cancelled". The agent stops
where it is, and Urai keeps the text that arrived before the stop.
A later GET of the same response reports completed, because what is stored is
a finished message that holds partial text. A turn that already ended returns
its response unchanged.
A thread runs one turn at a time, so a cancel stops that one turn.
Delete a response
Section titled “Delete a response”curl -X DELETE https://chat.app.urai.dev/api/agents/$AGENT_ID/v1/responses/$RESPONSE_ID \ -H "Authorization: Bearer $URAI_API_KEY"{ "id": "resp_9f2c1e40-...", "object": "response", "deleted": true }The delete removes the assistant turn and the user turn that prompted it. The rest of the thread stays, so the responses before and after it still resolve.
The delete also removes the usage record of that turn, and it removes the two messages from search. Deleting a response deletes what it cost.