Run a Model
Execute any AI model with a single API call and get real-time updates.
POST /Run/{owner-slug}/{model-slug}
Starts an AI model run. The endpoint accepts model-specific parameters and returns a task ID you can use to track progress via polling, WebSocket, or webhook by providing a callbackUrl parameter, and Wiro will POST the result to your URL when the task completes.
If you want one HTTP request to wait for the finished task, use POST /Run/{owner}/{model}/sync on this same API host. For OpenAI or Anthropic LLM protocols, see the Direct LLM Gateway.
Content Types
JSON (application/json)
Use JSON for text-based inputs: prompts, configuration, numeric parameters. This is the default and most common format.
Multipart (multipart/form-data)
Use multipart when the model requires file inputs (images, audio, documents). Include files as form fields and other parameters as text fields.
Request Parameters
Parameters vary by model. Use the /Tool/Detail endpoint to discover which parameters a model accepts. The following optional parameters apply to all runs:
Common Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
callbackUrl |
string | No | URL to receive a POST webhook when the task completes. It must be a public http(s) address: a host that is, or resolves to, a private, loopback or link-local address gets no webhook. The task still runs, and Task/Detail returns its result. Any 2xx answer counts as delivered; see Webhook Callback for retries and redirects. |
projectid |
string | No | Override the default project for billing (if you have multiple projects) |
URL inputs must be public too. When you send a file input as a URL, Wiro downloads the file itself, so the URL must be a public http(s) address, and so must every address it redirects to. A host that is, or resolves to, a private, loopback or link-local address isn't fetched, and the run gets no file for that input.
Response
A successful run returns a task ID and a WebSocket access token:
{
"result": true,
"errors": [],
"taskid": "2221",
"socketaccesstoken": "eDcCm5yyUfIvMFspTwww49OUfgXkQt"
}
Direct LLM turns and tool calls
Models whose Tool Detail response includes the llm-tool-call category accept seven optional ordinary model parameters on both asynchronous Run and /sync: messages, tools, tool_choice, parallel_tool_calls, response_format, previousTaskToken, and toolOutputs. response_format is present only on models that declare it, and it uses the flat shape {"type":"json_object"} or {"type":"json_schema","name":"person","schema":{...}}, not Chat's nested json_schema wrapper. A mode the model does not declare is rejected at $.response_format.type before the task is created. Tool Detail marks them advanced: true only to place them in the Advanced section of catalog-driven UIs; they use the same parameters object and Run transport as every other model parameter. Send JSON values directly, and use exactly one of prompt or non-empty messages on a first turn.
{
"messages": [{
"role": "user",
"content": "What is the weather in Paris?"
}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Return current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"],
"additionalProperties": false
},
"strict": true
}
}],
"tool_choice": "auto",
"parallel_tool_calls": true,
"session_id": "weather-session-1"
}
// Node.js
const response = await fetch(
"https://api.wiro.ai/v1/Run/claude/fable-5/sync",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.WIRO_API_KEY
},
body: JSON.stringify({
messages: [{
role: "user",
content: "What is the weather in Paris?"
}],
tools: [{
type: "function",
function: {
name: "get_weather",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"]
}
}
}],
tool_choice: "auto",
parallel_tool_calls: true,
session_id: "weather-session-1",
})
}
);
Wiro never executes caller-defined tools. A tool request is returned in outputs[].content.segments as its own ordered timeline item. Function calls use arguments, custom grammar/free-form calls use input, and both include stable id, call_id, name, and status fields. Execute a call only when its status is completed and the turn's finishreason is tool_calls; incomplete calls may contain truncated input and must never be executed.
The same structured LLM fields appear in three response locations:
- WebSocket
task_output:message.segmentsis the live cumulative snapshot; final-only metadata may be absent while generation is still running. - WebSocket
task_postprocess_end:message[].contentcontains the persisted final output. - Task Detail and non-streaming
/sync:tasklist[0].outputs[].contentcontains the persisted final output.
Inside that content:
segmentsis the authoritative ordered text/tool-call timeline.finishreasonexplains why generation stopped:stop,tool_calls,length,content_filter, orerror. Handlelength,content_filter, anderroras non-normal model completion.usagecontains normalized token and server-tool counters.input_tokensincludes cache reads and cache writes;output_tokensincludes reasoning; andtotal_tokensis exactlyinput_tokens + output_tokens. Therefore, do not add detail counters to the total. Optionalinput_tokens_detailscan includecached_tokens,cache_write_tokens, five-minute and one-hour cache-write subsets, and text/audio/image/video counters. Optionaloutput_tokens_detailscan include reasoning, text/audio/image/video, and accepted/rejected prediction counters. Optionalserver_tool_usecontains request counts rather than tokens. Providers omit unsupported counters. There is no cached-output counter because prompt caching applies to input context, not previously generated output.usageis informational and is used by protocol-compatible LLM responses; use the task-leveltotalcostfor the actual billed amount.
For a final llm-tool-call result, rely on pexit: "0" and require finishreason. Structured-output validation failures set a non-zero pexit.
{
"segments": [{
"type": "function_call",
"id": "fc_01",
"call_id": "call_01",
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}",
"status": "completed"
}],
"finishreason": "tool_calls",
"usage": {
"input_tokens": 84,
"input_tokens_details": {
"cached_tokens": 24,
"cache_write_tokens": 8,
"cache_write_5m_tokens": 8,
"text_tokens": 84
},
"output_tokens": 21,
"output_tokens_details": {
"reasoning_tokens": 9,
"text_tokens": 21
},
"total_tokens": 105,
"server_tool_use": {
"web_search_requests": 1
}
}
}
For tool-output continuation, execute the tool in your application and send another Run request with the first request's returned socketaccesstoken as previousTaskToken, the same session_id, and one matching entry in toolOutputs for every executed call. Do not send prompt, messages, tools, tool_choice, parallel_tool_calls, or response_format on this continuation. prompt, messages, tools, tool_choice and response_format are rejected outright; parallel_tool_calls is ignored. The tools, tool choice, and response format stored with the first turn are reapplied automatically, so a JSON contract set on the first turn still holds after the tool round-trip.
{
"previousTaskToken": "task-token",
"toolOutputs": [{
"call_id": "call_01",
"output": {
"temperature_c": 12,
"condition": "rain"
}
}],
"session_id": "weather-session-1"
}
Stateful turn continuation
A chat continues through its session_id. After a turn that ended with an answer, run the same model again with the same session_id and the next prompt. Wiro keeps the earlier turns of the session; some chat models also need the same user_id on every turn (see LLM & Chat Streaming). Send the same session_id on every turn, the first one included, and leave previousTaskToken out.
{
"prompt": "Give me one concrete example.",
"session_id": "weather-session-1"
}
previousTaskToken belongs to the tool-call round trip above: together with toolOutputs, it continues a turn whose finishreason is tool_calls, using the socketaccesstoken of that completed task from the same project, model and session. After a plain prompt turn, a request that sends previousTaskToken with the next prompt is refused with code 122 ("The previous task state for this conversation is no longer available."). Send the same session_id and the next prompt instead.
For OpenAI-compatible clients, previous_response_id remains the preferred public field on /v1/responses; do not send previousTaskToken to that endpoint. Direct asynchronous Run and /sync clients use previousTaskToken.
For cumulative SSE or WebSocket snapshots, replace the previous segments array. Do not append the complete array again. A call can move from in_progress to completed while keeping the same IDs. The final pexit: "0" remains the success indicator.
POST /Run/{owner-slug}/{model-slug}/sync
Wait for any finite model on https://api.wiro.ai/v1. This starts the same billed task as asynchronous Run, then returns final Task Detail JSON or streams generic task events. It is not an OpenAI or Anthropic protocol. Training and realtime models are rejected.
curl -X POST \
"https://api.wiro.ai/v1/Run/claude/fable-5/sync" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"prompt": "Reply with one concise sentence about Türkiye.",
"session_id": "fable-sync-example",
"effort": "medium",
"webSearch": "false",
"maxOutputTokens": 128
}'
Without streaming, the request stays open and returns Task Detail-compatible JSON. Check tasklist[0].pexit; "0" means the model task succeeded. For LLM tasks, outputs contains the structured result and debugoutput contains merged plain text.
{
"result": true,
"total": "1",
"tasklist": [{
"id": "2221",
"status": "task_postprocess_end",
"pexit": "0",
"outputs": []
}],
"errors": []
}
Add ?stream=true or send Accept: text/event-stream for named generic events: start, cumulative task snapshots as task_output, a final done, and periodic : keep-alive comments. Generic SSE does not use [DONE]. LLM task_output events contain one ordered segments array. Text items use thinking or answer; tool items use function_call or custom_tool_call.
event: task_output
data: {
"id": "2221",
"status": "task_output",
"pexit": null,
"segments": [
{ "type": "thinking", "text": "Checking the constraints..." },
{ "type": "answer", "text": "Here is the result." }
]
}
Each LLM segments value is the full accumulated ordered snapshot, not only the latest delta. Replace your previously stored segment list with each event instead of appending the whole array again. While generation is active, the final item's text, arguments, or input grows in place as chunks arrive; render each snapshot immediately instead of waiting for done. A new item is added when the output phase changes. Render thinking items as optional reasoning progress, answer items as the model response, and tool items as application-executed calls. The final done event contains Task Detail-compatible JSON; inspect tasklist[0].pexit to determine success.
curl -N -X POST \
"https://api.wiro.ai/v1/Run/claude/fable-5/sync?stream=true" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: text/event-stream" \
-d '{
"prompt": "Reply with one concise sentence about Türkiye.",
"session_id": "fable-stream-example",
"effort": "medium",
"webSearch": "false",
"maxOutputTokens": 128
}'
Disconnecting or reaching the 40-minute wait timeout ends only the synchronous wait; the task keeps running. A non-streaming timeout returns HTTP 504 with taskid and socketaccesstoken so you can resume tracking it through Task Detail or WebSocket. Only task_postprocess_end and task_cancel close the wait. task_error does not close a task. Errors use Wiro's result/errors envelope. Sync is an additional execution method; asynchronous Run remains unchanged.
/v1/Run/{owner-slug}/{model-slug}/sync?stream=true
OpenAI Chat/Responses, Anthropic Messages, and Cursor clients belong on https://llm.wiro.ai. See the Direct LLM Gateway.
Full Flow
The typical workflow after calling the Run endpoint:
- Run: call
POST /Run/{owner-slug}/{model-slug}and receive a task ID - Track: connect via WebSocket or poll
POST /Task/Detail - Receive: get outputs as the model produces them (streaming or final)
- Complete: task reaches
task_postprocess_end; verifypexit: "0"and read its outputs
For real-time streaming, use the WebSocket connection with the
socketaccesstoken returned in the run response. For simpler integrations, poll the Task Detail endpoint every few seconds.