Call your UraiJS tools
Urai runs tools for you. You name the tools the model can use. Urai supplies the schema, executes each call the model makes, feeds the result back to the model, and repeats until the model writes an answer.
This is the main difference from the OpenAI endpoint. You never receive a
tool_calls array to run yourself. One request gives you the finished answer.
Name a tool
Section titled “Name a tool”Send the standard OpenAI shape and set only function.name.
{ "model": "anthropic/claude-haiku-4-5", "messages": [ { "role": "user", "content": "Text +1 555 010 1234 that the build passed." } ], "tools": [ { "type": "function", "function": { "name": "twilio_sms" } } ]}The name is the name of a tool in your organization, as it appears in
Developer Studio. Urai reads the schema from your organization’s UraiJS catalog.
Urai accepts description, parameters, and strict in the same object and
ignores them. A tool specification that a typed OpenAI SDK builds therefore
works without a change.
One tool holds several functions
Section titled “One tool holds several functions”A UraiJS tool is one script. Each @tool method in the script is one function
with its own name, description, and parameters.
class MessagingTools { @tool static async sendSms({ to, body }: { to: string; body: string }) { // ... }
@tool static async listMessages({ since }: { since: string }) { // ... }}When you name the tool twilio_sms, the model receives every function that the
tool declares. You cannot name a single function in tools[].
Two tools can declare a function with the same name. In that case the last tool wins and the earlier function becomes unreachable. Rename one of the functions.
Control when the model calls a tool
Section titled “Control when the model calls a tool”tool_choice takes four forms.
| Value | Behaviour |
|---|---|
"auto" |
The model decides. This is the default when you send tools. |
"none" |
The model does not call a tool. |
"required" |
The model must call a tool. |
{"type":"function","function":{"name":"..."}} |
The model must call this one function. |
The object form names a function, not a tool. Use a name from the tool’s
declarations, such as sendSms, and not the tool name twilio_sms.
You cannot combine tool_choice: "required" with response_format. See
Ask for JSON.
Pass context to the tool
Section titled “Pass context to the tool”The top-level metadata object reaches each tool as meta.vars.
{ "model": "anthropic/claude-haiku-4-5", "messages": [{ "role": "user", "content": "Send the invoice to the account owner." }], "tools": [{ "type": "function", "function": { "name": "billing" } }], "metadata": { "account_id": "acct_8812", "locale": "en-IN" }}Inside the tool:
const accountId = meta.vars.account_id;Urai always adds three more values: thread_id, organization_id, and
user_id. These three overwrite any key of the same name in your metadata.
Treat metadata as untrusted input in the tool. It arrives from the caller, and
Urai does not check it against any record.
Secrets
Section titled “Secrets”A UraiJS tool declares the secrets it needs. Urai resolves them from your organization’s integrations each time the tool runs. The tool reads each one by name:
import { secrets } from "urai:secrets";
const key = await secrets.get("ACME_API_KEY");If a secret is not connected, the tool returns an error to the model. The model
tells the user that it could not do the work. The request itself still returns
200. Connect the secret on the Integrations page and try again.
A full example
Section titled “A full example”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": "How many open orders does this account have?" } ], "tools": [ { "type": "function", "function": { "name": "orders" } } ], "metadata": { "account_id": "acct_8812" } }'Urai runs the turn like this:
- The model reads the question and the functions that the
orderstool declares. - The model calls
listOrderswith the arguments it chooses. - Urai runs the function in the UraiJS sandbox with your
metadataasmeta.vars. - Urai gives the result to the model.
- The model writes the answer, or calls another function and the loop repeats.
The response holds the answer only:
{ "id": "chatcmpl-...", "object": "chat.completion", "created": 1710000000, "model": "anthropic/claude-haiku-4-5", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Account acct_8812 has 3 open orders." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 812, "completion_tokens": 24, "total_tokens": 836 }}usage covers every round, not the last one.
The built-in tools
Section titled “The built-in tools”Urai supplies four tools itself. Name them the same way as your own tools.
"tools": [{ "type": "function", "function": { "name": "web_search" } }]| Name | What it does |
|---|---|
web_search |
Finds pages on the web. |
web_fetch |
Reads the full text of the pages. |
search_documents |
Searches the organization’s knowledge collections. |
read_document |
Reads one document from those collections in full. |
Urai does not read these names from your UraiJS catalog. The schemas are built in.
For the two web tools, see Search and read the web. For the two document tools, see Search knowledge collections.
Limits and failures
Section titled “Limits and failures”| Result | Cause |
|---|---|
400 unknown_tool |
The name is not a built-in and not a tool in your organization. The message lists the names that failed. |
400 tools_unavailable |
UraiJS is not available for this organization. |
400 tool_not_configured |
The tool needs something the organization does not have, such as the Brave Search credential or the Jina credential. |
| An error in the answer text | The tool ran and returned an error. The turn continues and the model reports the problem. |
Two limits apply to each request:
- A turn stops after 10 tool rounds. The response returns with
finish_reason: "stop". - Streaming stays silent during tool rounds. Text resumes when the model writes the answer.
Do not send tool messages
Section titled “Do not send tool messages”Urai ignores tool messages in the messages array, and it does not read
tool_calls on an assistant message. A client-side tool loop therefore does not
work against this endpoint.
Send the conversation as system, user, and assistant messages. Let Urai
run the tools.