Reference
Base URL: https://chat.app.urai.dev/api/openai/v1
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
POST |
/chat/completions |
Create a completion, buffered or streamed. |
GET |
/models |
List the models the key’s organization can use. |
GET |
/models/{id} |
Read one model. The id contains a slash. |
POST |
/files |
Upload a file. Multipart. Returns 201. |
GET |
/files |
List the 100 most recent files for the key and user. |
GET |
/files/{id} |
Read one file object. |
GET |
/files/{id}/content |
Download the bytes. |
DELETE |
/files/{id} |
Delete the file. |
The /files reads and the delete take the principal as ?user=, because they
carry no body.
Request fields
Section titled “Request fields”| Field | Type | Notes |
|---|---|---|
model |
string | Required. The form provider/model. |
messages |
array | Required. At least one user or assistant message. |
temperature |
number | Sampling temperature. |
top_p |
number | Nucleus sampling. |
max_tokens |
integer | Maximum tokens to generate. |
max_completion_tokens |
integer | The same limit. This field wins over max_tokens. |
stop |
string or array | One stop sequence, or a list of them. |
seed |
integer | Best effort, where the provider supports it. |
stream |
boolean | Return Server-Sent Events. |
stream_options |
object | {"include_usage": true} adds a final usage chunk. |
tools |
array | Tools the model can use. Only function.name is read. |
tool_choice |
string or object | "none", "auto", "required", or a function object. |
response_format |
object | text, json_object, or json_schema. |
user |
string | Your identifier for the end user. Also isolates stored threads. |
metadata |
object | Urai extension. Reaches tools as meta.vars. |
knowledge |
object | Urai extension. {"collections": [...]} limits the knowledge tools. |
attachments |
array | Urai extension. File ids, attached to the last user message. |
Urai accepts any other OpenAI field and ignores it. A request built by a typed OpenAI SDK therefore works without a change.
Message roles
Section titled “Message roles”| Role | Handling |
|---|---|
system |
Becomes a system instruction. |
developer |
Becomes a system instruction. |
user |
Becomes a turn in the conversation. |
assistant |
Becomes a turn in the conversation. |
tool |
Ignored. Urai runs tools on the server. |
| Any other value | 400 with the code invalid_role. |
A request with no user or assistant message returns 400.
Urai does not store system messages on a thread. Send them on every request.
Content parts
Section titled “Content parts”content is a string, or an array of parts.
Part type |
Handling |
|---|---|
text |
Read as the message text. Several text parts are joined. |
image_url |
An attachment. The url must be a base64 data: URL. |
file |
An attachment. Set file_data or file_id. A part with neither fails. |
| Any other value | Ignored, so a client sending a new OpenAI part still works. |
Attachments are read on user messages only. See
Send files and images.
Headers
Section titled “Headers”| Header | Direction | Purpose |
|---|---|---|
Authorization: Bearer sk-urai-... |
Request | Required. Identifies the key and its organization. |
x-thread-id |
Response | The stored thread for this exchange. |
x-thread-id |
Request | Continues that thread. Send only the new message. |
A thread belongs to the pair of API key and user value. Send the same user
value that created the thread.
Response objects
Section titled “Response objects”A buffered completion:
{ "id": "chatcmpl-...", "object": "chat.completion", "created": 1710000000, "model": "anthropic/claude-haiku-4-5", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 36, "completion_tokens": 14, "total_tokens": 50 }}A streamed chunk:
{ "id": "chatcmpl-...", "object": "chat.completion.chunk", "created": 1710000000, "model": "anthropic/claude-haiku-4-5", "choices": [{ "index": 0, "delta": { "content": "..." }, "finish_reason": null }]}The final usage chunk, sent when stream_options.include_usage is true:
{ "id": "chatcmpl-...", "object": "chat.completion.chunk", "created": 1710000000, "model": "anthropic/claude-haiku-4-5", "choices": [], "usage": { "prompt_tokens": 46, "completion_tokens": 21, "total_tokens": 67 }}The stream ends with data: [DONE].
A file object, from every /files route except the content download:
{ "id": "9d4c1f7e-...", "object": "file", "bytes": 184320, "created_at": 1710000000, "filename": "invoice-2041.pdf", "purpose": "user_data", "mime_type": "application/pdf"}mime_type is a Urai addition to OpenAI’s shape. purpose defaults to
user_data and is returned as you sent it. DELETE /files/{id} returns
{"id": "...", "object": "file", "deleted": true}.
finish_reason
Section titled “finish_reason”| Value | Meaning |
|---|---|
stop |
The model finished the answer. |
length |
The token limit cut the output. |
content_filter |
The provider’s safety filter stopped the output. |
tool_calls |
The provider stopped on a tool call. |
Built-in tool names
Section titled “Built-in tool names”| Name | Purpose |
|---|---|
web_search |
Search the web. See the provider table. |
web_fetch |
Read the full text of web pages. Not on Gemini, where urlContext does this. |
search_documents |
Search the organization’s knowledge collections. |
read_document |
Read one document from those collections in full. |
Any other name in tools[] must match a UraiJS tool in your organization.
Errors
Section titled “Errors”{ "error": { "message": "The model 'openai/gpt-4o' is not available for this organization", "type": "invalid_request_error", "param": "model", "code": "model_not_found" }}| Status | Code | Cause |
|---|---|---|
| 401 | missing_api_key |
No Authorization header. |
| 401 | invalid_api_key |
Urai does not recognise the key. |
| 401 | api_key_disabled |
Someone disabled the key. |
| 401 | expired_api_key |
The key is past its expiry. |
| 400 | model_not_found |
Unknown model, or no credential for it in this organization. |
| 400 | invalid_role |
A message has an unknown role. |
| 400 | invalid_tool_choice |
tool_choice has a bad value or shape. |
| 400 | unknown_tool |
A name in tools[] matches no built-in and no tool in the organization. |
| 400 | tools_unavailable |
UraiJS is not available for this organization. |
| 400 | tool_not_configured |
The tool needs a credential or a collection that is missing. |
| 400 | unknown_collection |
knowledge.collections names a collection that does not exist. |
| 400 | invalid_response_format |
Bad schema name or shape, or response_format sent with tool_choice: "required". |
| 400 | invalid_thread_id |
x-thread-id is not a UUID. |
| 400 | invalid_attachment |
A remote URL, a bad data URL, invalid base64, or a file part with neither file_data nor file_id. |
| 400 | attachment_too_large |
One attachment is over 25 MiB. |
| 400 | too_many_attachments |
More than 16 attachments in one request. |
| 400 | invalid_multipart |
POST /files could not read the multipart body. |
| 400 | missing_file |
POST /files had no file part. |
| 400 | invalid_file |
The uploaded file is empty. |
| 400 | (none) | messages holds no user or assistant message. |
| 404 | thread_not_found |
The thread does not belong to this key and user. |
| 404 | file_not_found |
Unknown file id, or the id belongs to another user. |
| 404 | model_not_found |
GET /models/{id} found no such model. |
| 500 | (none) | Server error. Urai logs the detail and does not return it. |
| 502 | (none) | The model provider returned an error. |
A tool that fails at run time does not produce an HTTP error. The model receives
the error, and the request returns 200 with the model’s report of the problem.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Choices per response | 1. The endpoint does not support n greater than 1. |
| Attachments in one request | 16 |
| Size of one attachment | 25 MiB after decoding |
| Completion request body | 64 MiB. Base64 adds about one third to the size of the bytes. |
Files returned by GET /files |
The 100 most recent |
| Tool rounds per request | 10. The turn then stops with finish_reason: "stop". |
Passages per search_documents call |
8 by default, 25 at most. |
read_document window |
About 40,000 characters. The reply carries next_seq for the rest. |
usage scope |
The whole request, including every tool round. |