Overview
Urai runs an OpenAI-compatible chat completions endpoint. If your code already speaks the OpenAI Chat Completions format, you change two things to reach Urai: the base URL and the API key.
One endpoint reaches every model your organization has credentials for. The same endpoint runs your UraiJS tools, searches your knowledge collections, and reads the files you attach, all on the server. You get a finished answer back.
Base URL
Section titled “Base URL”https://chat.app.urai.dev/api/openai/v1Every path on this page is relative to that base URL.
Authenticate
Section titled “Authenticate”Send an API key as a bearer token.
Authorization: Bearer sk-urai-...To create a key:
- Sign in to Urai.
- Open API Keys in the sidebar. Only organization owners and admins see this page.
- Select Create API Key. Give the key a name. Set an expiry if you want one.
- Copy the key now. Urai shows the value one time and cannot show it again.
A key belongs to the organization that created it. The key reaches that organization’s models, tools, and collections. You can disable a key from the same page. Disabled keys and expired keys stop immediately.
Keep the key on your server. Anyone who has the key can make requests that your organization pays for. Do not put a key in browser code. Do not commit a key to source control.
Make the first request
Section titled “Make the first request”curl https://chat.app.urai.dev/api/openai/v1/chat/completions \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-haiku-4-5", "messages": [ { "role": "user", "content": "Give me one sentence on the water cycle." } ] }'The same request with the OpenAI Python SDK:
from openai import OpenAI
client = OpenAI( base_url="https://chat.app.urai.dev/api/openai/v1", api_key="sk-urai-...",)
response = client.chat.completions.create( model="anthropic/claude-haiku-4-5", messages=[{"role": "user", "content": "Give me one sentence on the water cycle."}],)
print(response.choices[0].message.content)And with the OpenAI Node SDK:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://chat.app.urai.dev/api/openai/v1", apiKey: "sk-urai-...",});
const response = await client.chat.completions.create({ model: "anthropic/claude-haiku-4-5", messages: [{ role: "user", content: "Give me one sentence on the water cycle." }],});
console.log(response.choices[0].message.content);Choose a model
Section titled “Choose a model”The model field takes the form provider/model, such as
anthropic/claude-haiku-4-5 or gemini/gemini-2.5-flash.
To see what your organization can use, list the models:
curl https://chat.app.urai.dev/api/openai/v1/models \ -H "Authorization: Bearer $URAI_API_KEY"{ "object": "list", "data": [ { "id": "anthropic/claude-haiku-4-5", "object": "model", "created": 0, "owned_by": "anthropic" }, { "id": "gemini/gemini-2.5-flash", "object": "model", "created": 0, "owned_by": "gemini" } ]}Send any id from that list as your model. A model your organization has no
credential for returns 400 with the code model_not_found.
To read one model, put its id in the path. The id contains a slash, and the endpoint accepts it:
curl https://chat.app.urai.dev/api/openai/v1/models/anthropic/claude-haiku-4-5 \ -H "Authorization: Bearer $URAI_API_KEY"Read the response
Section titled “Read the response”{ "id": "chatcmpl-2f1c9a0c4c1c4f5f9a2b1d3e4f5a6b7c", "object": "chat.completion", "created": 1710000000, "model": "anthropic/claude-haiku-4-5", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Water evaporates, forms clouds, and falls again as rain." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 36, "completion_tokens": 14, "total_tokens": 50 }}Every response has one choice. The endpoint does not support n greater
than 1.
usage covers the whole request. If the model called tools, the totals include
every round.
Stream the answer
Section titled “Stream the answer”Set "stream": true to receive
Server-Sent Events.
Each event holds a chat.completion.chunk object. The stream ends with
data: [DONE].
curl https://chat.app.urai.dev/api/openai/v1/chat/completions \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-haiku-4-5", "stream": true, "stream_options": { "include_usage": true }, "messages": [{ "role": "user", "content": "Count from 1 to 5." }] }'data: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}], ...}
data: {"choices":[{"index":0,"delta":{"content":"1 2 3"},"finish_reason":null}], ...}
data: {"choices":[{"index":0,"delta":{"content":" 4 5"},"finish_reason":null}], ...}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}], ...}
data: {"choices":[],"usage":{"prompt_tokens":46,"completion_tokens":21,"total_tokens":67}, ...}
data: [DONE]Three rules apply to the stream:
- The first chunk carries
{"role": "assistant"}and no content. - Tool rounds are silent. Urai sends no event while a tool runs. Text starts again when the model writes the answer.
stream_options.include_usageadds one final chunk before[DONE]. That chunk has an emptychoicesarray and the token totals.
stream = client.chat.completions.create( model="anthropic/claude-haiku-4-5", messages=[{"role": "user", "content": "Count from 1 to 5."}], stream=True,)for chunk in stream: if chunk.choices: print(chunk.choices[0].delta.content or "", end="")Ask for JSON
Section titled “Ask for JSON”Use response_format to constrain the shape of the answer.
For free-form JSON:
"response_format": { "type": "json_object" }For a schema:
"response_format": { "type": "json_schema", "json_schema": { "name": "invoice_summary", "description": "One line for each invoice in the message.", "schema": { "type": "object", "properties": { "invoices": { "type": "array", "items": { "type": "object", "properties": { "number": { "type": "string" }, "total": { "type": "number" } }, "required": ["number", "total"] } } }, "required": ["invoices"] } }}Three rules apply:
json_schema.nameholds 1 to 64 characters fromA-Za-z0-9_-.json_schema.schemais a JSON Schema object.- You cannot send
response_formatwithtool_choice: "required". A model writes a tool call or a constrained answer, not both. Urai rejects the pair with400and the codeinvalid_response_format.
Urai accepts "type": "text" and applies no constraint, because that is the
default behaviour of the provider.
Continue a conversation
Section titled “Continue a conversation”The endpoint is stateless by default, the same as OpenAI. You send the full
messages array on each request.
Urai also stores each conversation on the server. Every response carries an
x-thread-id header:
x-thread-id: 0723cee0-3912-434e-8450-2c701e6f9713Send that header back to continue the conversation. Include only the new message. Urai adds the earlier turns for you.
curl https://chat.app.urai.dev/api/openai/v1/chat/completions \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -H "x-thread-id: 0723cee0-3912-434e-8450-2c701e6f9713" \ -d '{ "model": "anthropic/claude-haiku-4-5", "messages": [{ "role": "user", "content": "And what did I ask before that?" }] }'The x-thread-id header is a Urai extension. The OpenAI specification has no
equivalent. To stay stateless, omit the header and send the history yourself.
System messages are not stored. Send them on every request.
Keep end users apart
Section titled “Keep end users apart”Set the user field to a stable identifier when one API key serves several end
users.
"user": "customer-4821"A thread belongs to the exact pair of API key and user value. To continue a
thread, send the same user value that created it. A different value returns
404 with the code thread_not_found. Urai returns 404 rather than 403, so
one end user cannot learn that another user’s thread exists.
Handle errors
Section titled “Handle errors”Errors use the OpenAI envelope and the matching HTTP status.
{ "error": { "message": "The model 'openai/gpt-4o' is not available for this organization", "type": "invalid_request_error", "param": "model", "code": "model_not_found" }}The Reference page lists every code.
Next steps
Section titled “Next steps”- Call your UraiJS tools. Name a tool and let Urai run it.
- Search knowledge collections. Answer from your uploaded documents.
- Send files and images. Attach a picture or a document to a request.
- Reference. Every field, header, and error code.