Introduction 
Everything you need to get started with the Wiro AI platform.
What is
?
Wiro is an AI model marketplace and API platform that lets you run AI models through a single, unified API. Instead of managing infrastructure for each model provider, you make one API call to Wiro and we handle the rest.
- Unified API — one interface for all models (image generation, LLMs, audio, video, and more)
- Pay-per-use pricing — only pay for what you consume, no upfront commitments
- Real-time WebSocket updates — stream task progress and outputs live
- 9 SDK languages — curl, Python, Node.js, PHP, C#, Swift, Dart, Kotlin, Go
Base URL
All API requests are made to:
https://api.wiro.ai/v1
WebSocket connections use:
wss://socket.wiro.ai/v1
Quick Start
- Sign up Create an account at wiro.ai
- Create a project Go to the Dashboard to get your API key
- Pick a model Browse the marketplace and choose a model
- Make your first API call See Code Examples for full end-to-end samples
Response Format
Every API response returns JSON with a consistent structure:
{
"result": true,
"errors": [],
"data": { ... }
}
When result is false, the errors
array contains human-readable messages describing what went wrong.
Rate Limits & Error Handling
API requests are rate-limited per project. If you exceed the limit, the API returns a 429 Too Many Requests status. Implement exponential backoff in your retry logic.
Common HTTP status codes:
200— Success400— Bad request (check parameters)401— Unauthorized (invalid or missing API key)403— Forbidden (signature mismatch or insufficient permissions)429— Rate limit exceeded500— Internal server error
Authentication
Secure your API requests with signature-based or simple key authentication.
Overview
Wiro supports two authentication methods. You choose the method when creating a project — it cannot be changed afterward.
Signature-Based Authentication
Uses HMAC-SHA256 to sign every request. The API secret never leaves your environment, making this method ideal for client-side applications where the key might be exposed.
How it works
- Generate a nonce Use a unix timestamp or random integer
- Concatenate Combine:
API_SECRET + NONCE - Create HMAC-SHA256 hash Use your
API_KEYas the secret key - Send as headers Include the signature, nonce, and API key in request headers
SIGNATURE = HMAC-SHA256(key=API_KEY, message=API_SECRET + NONCE)
Required Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-api-key | string | Yes | Your project API key |
x-signature | string | Yes | HMAC-SHA256(API_SECRET + NONCE, API_KEY) |
x-nonce | string | Yes | Unix timestamp or random integer |
API Key Only Authentication
For server-side applications where you control the environment, you can use the simpler API-key-only method. Just include the x-api-key header — no signature required.
Required Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-api-key | string | Yes | Your project API key |
Comparison
| Feature | Signature-Based | API Key Only |
|---|---|---|
| Security | High — secret never sent over the wire | Moderate — key sent in every request |
| Complexity | Requires HMAC computation | Single header |
| Best for | Client-side apps, mobile, public repos | Server-side, internal tools |
| Replay protection | Yes (via nonce) | No |
How to Choose
- Building a client-side or mobile app? Use Signature-Based.
- Running a server-side backend with controlled access? API Key Only is simpler.
- Unsure? Default to Signature-Based — it's always the safer option.
Projects
Organize your API access, billing, and usage with projects.
What is a Project?
A project is a container that holds your API keys, billing settings, and usage tracking. Each project gets its own API key and secret, letting you separate environments (development, staging, production) or different applications.
- Each project has its own API key and (optionally) API secret
- Usage and billing are tracked per project
- You can create multiple projects under one account
Creating a Project
- Go to the Dashboard Navigate to wiro.ai/panel
- Open Projects Go to Projects and click New Project
- Name your project Enter a descriptive project name
- Select authentication method Signature-Based — generates API key + secret | API Key Only — generates only an API key
- Create Click Create and copy your credentials immediately
API Credentials
After creating a project, your API key (and secret, if signature-based) are displayed once. Copy and store them securely — you won't be able to view the secret again.
Important: Treat your API secret like a password. Never commit it to version control or expose it in client-side code without signature-based authentication.
Managing Projects
From the Projects page in your Dashboard, you can:
- Update name — rename your project at any time
- Regenerate keys — invalidates existing keys and generates new ones
- View usage — see API calls, costs, and task history
- Delete project — permanently removes the project and revokes all keys
Regenerating keys immediately invalidates the old ones. Update your application with the new credentials before the old ones stop working.
Models
Browse and discover AI models available on the Wiro platform.
POST /Tool/List
Returns a paginated list of available models. Filter by categories, search by name, and sort results.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start | string | No | Offset for pagination (default: "0") |
limit | string | No | Number of results to return (default: "20") |
search | string | No | Search query to filter models by name |
sort | string | No | Sort field: id, relevance |
order | string | No | Sort direction: ASC or DESC |
categories | string[] | No | Filter by categories (e.g. image-generation, llm, audio, video) |
tags | string[] | No | Filter by tags |
slugowner | string | No | Filter by model owner slug |
hideworkflows | boolean | No | Hide workflow models from results (recommended: true) |
summary | boolean | No | Return summarized model data (recommended for listings) |
Response
{
"result": true,
"errors": [],
"total": 2,
"tool": [
{
"id": "1611",
"title": "Virtual Try-on",
"slugowner": "wiro",
"slugproject": "Virtual Try-On",
"cleanslugowner": "wiro",
"cleanslugproject": "virtual-try-on",
"description": "Integrate the Wiro Virtual Try-On API...",
"image": "https://cdn.wiro.ai/uploads/models/...",
"computingtime": "10 seconds",
"categories": ["tool", "image-to-image", "image-editing"],
"tags": [],
"marketplace": 1,
"onlymembers": "1",
"averagepoint": "5.00",
"commentcount": "1",
"dynamicprice": "[{\"inputs\":{},\"price\":0.09,\"priceMethod\":\"cpr\"}]",
"taskstat": {
"runcount": 672,
"successcount": "254",
"errorcount": "198",
"lastruntime": "1774007585"
}
}
]
}
POST /Tool/Detail
Returns full details for a specific model, including its input parameters, pricing, categories, and configuration.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
slugowner | string | Yes | Model owner slug (e.g. stability-ai) |
slugproject | string | Yes | Model project slug (e.g. sdxl) |
summary | boolean | No | Return summarized data |
Response
{
"result": true,
"errors": [],
"tool": [{
"id": "1611",
"title": "Virtual Try-on",
"slugowner": "wiro",
"slugproject": "Virtual Try-On",
"cleanslugowner": "wiro",
"cleanslugproject": "virtual-try-on",
"description": "Integrate the Wiro Virtual Try-On API...",
"image": "https://cdn.wiro.ai/uploads/models/...",
"computingtime": "10 seconds",
"readme": "<p>The Wiro Virtual Try-On AI model...</p>",
"categories": ["tool", "image-to-image", "image-editing"],
"parameters": null,
"inspire": [
{
"inputImageHuman": "https://cdn.wiro.ai/uploads/sampleinputs/...",
"inputImageClothes": ["https://cdn.wiro.ai/..."]
}
],
"samples": ["https://cdn.wiro.ai/uploads/models/..."],
"tags": [],
"marketplace": 1,
"onlymembers": "1",
"dynamicprice": "[{\"inputs\":{},\"price\":0.09,\"priceMethod\":\"cpr\"}]",
"averagepoint": "5.00",
"commentcount": "1",
"ratedusercount": "3",
"taskstat": {
"runcount": 672,
"successcount": "254",
"errorcount": "198",
"lastruntime": "1774007585"
},
"seotitle": "AI Virtual Try-On: Integrate Realistic Apparel Fitting",
"seodescription": "Integrate the Wiro Virtual Try-On API..."
}]
}
Model Browser
Browse available models interactively. Click on a model to see its details on the model page.
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 — Wiro will POST the result to your URL when the task completes.
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 |
projectid | string | No | Override the default project for billing (if you have multiple projects) |
Response
A successful run returns a task ID and a WebSocket access token:
{
"result": true,
"errors": [],
"taskid": "2221",
"socketaccesstoken": "eDcCm5yyUfIvMFspTwww49OUfgXkQt"
}
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
endstatus with full results
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.
Model Parameters
Understand parameter types, content types, and how to send inputs to any model.
Discovering Parameters
Every model has its own set of input parameters. Use the /Tool/Detail endpoint to retrieve a model's parameter definitions. The response includes a parameters array where each item describes a parameter group with its items:
{
"parameters": [
{
"title": "Input",
"items": [
{
"id": "prompt",
"type": "textarea",
"label": "Prompt",
"required": true,
"placeholder": "Describe what you want...",
"note": "Text description of the desired output"
},
{
"id": "inputImage",
"type": "fileinput",
"label": "Input Image",
"required": true,
"note": "Upload an image or provide a URL"
}
]
}
]
}
Parameter Types
| Type | Description | Example Parameters |
|---|---|---|
text | Single-line text input | URLs, names, short strings |
textarea | Multi-line text input | prompt, negative_prompt, descriptions |
select | Dropdown with predefined options | outputType, language, style |
range | Numeric value (slider) | width, height, scale, strength |
fileinput | Single file upload (1 file or 1 URL) | inputImage, inputAudio |
multifileinput | Multiple files (up to N files/URLs) | inputDocumentMultiple |
combinefileinput | Up to N entries (files, URLs, or mixed) | inputImageClothes |
JSON vs Multipart
The content type of your request depends on whether the model requires file inputs:
| Condition | Content-Type | When to Use |
|---|---|---|
| No file parameters | application/json | Text-only models (LLMs, image generation from prompt) |
| Has file parameters | multipart/form-data | Models that accept image, audio, video, or document uploads |
Tip: For fileinput and multifileinput parameters, use the {id}Url suffix to send URLs (e.g., inputImageUrl). For combinefileinput, pass URLs directly in the original parameter — no suffix needed. You can also pass a URL directly to any file parameter (e.g., inputImage) if the {id}Url field doesn't exist.
File Upload Patterns
Single File (fileinput)
For parameters like inputImage, send either a file or a URL. When using multipart, always include both the {id} and {id}Url fields — leave one empty:
# Option 1: Upload file — send file in {id}, empty {id}Url
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "inputImage=@/path/to/photo.jpg" \
-F "inputImageUrl="
# Option 2: Send URL via {id}Url — send empty {id}, URL in {id}Url
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "inputImage=" \
-F "inputImageUrl=https://example.com/photo.jpg"
# Option 3: Pass URL directly in {id} (no {id}Url needed)
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "inputImage=https://example.com/photo.jpg"
Note: Option 3 is the simplest when you only have a URL. If the {id}Url field doesn't exist for a parameter, always use this approach.
Multiple Files (multifileinput)
For parameters like inputDocumentMultiple, upload up to N files, send comma-separated URLs, or mix both:
# Option 1: Upload multiple files — add empty {id}Url
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]" \
-F "inputDocumentMultipleUrl="
# Option 2: Send URLs (comma-separated in {id}Url) — add empty {id}
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "inputDocumentMultiple=" \
-F "inputDocumentMultipleUrl=https://example.com/doc1.pdf,https://example.com/doc2.pdf"
# Option 3: Mixed — files in {id}, URLs in {id}Url
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "inputDocumentMultipleUrl=https://example.com/doc2.pdf,https://example.com/doc3.pdf"
Combined (combinefileinput)
For parameters like inputImageClothes, files and URLs go directly in the same {id} field — no {id}Url suffix:
# Option 1: Upload files — each as a separate {id} entry
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]"
# Option 2: Send URLs — each directly in {id}
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "inputImageClothes=https://example.com/shirt.jpg" \
-F "inputImageClothes=https://example.com/pants.jpg"
# Option 3: Mixed — files and URLs in the same {id} field
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "inputImageClothes=https://example.com/pants.jpg"
Common Model Patterns
Image Generation (text-to-image)
Models like Stable Diffusion, Flux — JSON body, no file uploads:
{
"prompt": "A futuristic city at sunset",
"negative_prompt": "blurry, low quality",
"width": 1024,
"height": 1024
}
Image-to-Image (upscaler, style transfer)
Models that take an input image — multipart with file upload:
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "scale=4"
Virtual Try-On
Multiple image inputs — multipart with multiple files:
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]"
LLM / Document Processing
Text prompt with optional document uploads:
curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "prompt=Extract the candidate name and skills" \
-F "outputType=json" \
-F "language=en"
Note: LLM responses are delivered as structured content in the outputs array (with contenttype: "raw") and as merged plain text in debugoutput. See Tasks for details.
Realtime Voice Conversation
Realtime voice models accept configuration parameters (voice, system instructions, audio format, etc.) as JSON. Parameters vary per model — use /Tool/Detail to discover them. The actual audio interaction happens over Realtime Voice WebSocket after the task starts:
// Example: OpenAI GPT Realtime
{
"voice": "marin",
"system_instructions": "You are a helpful voice assistant.",
"input_audio_format": "audio/pcm",
"output_audio_format": "audio/pcm",
"input_audio_rate": "24000",
"output_audio_rate": "24000"
}
Webhook Callback
All models support an optional callbackUrl parameter. When provided, Wiro will POST the task result to your URL when the task completes — no polling required:
{
"prompt": "A sunset over mountains",
"callbackUrl": "https://your-server.com/webhook/wiro"
}
Tasks
Track, monitor, and control your AI model runs.
Task Lifecycle
Every model run creates a task that progresses through a defined set of stages:
Task Statuses
| Status | Description |
|---|---|
task_queue |
The task is queued and waiting to be picked up by an available worker. Emitted once when the task enters the queue. |
task_accept |
A worker has accepted the task. The task is no longer in the general queue and is being prepared for execution. |
task_preprocess_start |
Optional preprocessing has started. This includes operations like downloading input files from URLs, converting file types, and validating/formatting parameters before the model runs. Not all models require preprocessing. |
task_preprocess_end |
Preprocessing completed. All inputs are ready for GPU assignment. |
task_assign |
The task has been assigned to a specific GPU. The model is being loaded into memory. This may take a few seconds depending on the model size. |
task_start |
The model command has started executing. Inference is now running on the GPU. |
task_output |
The model is producing output. This event is emitted multiple times — each time the model writes to stdout, a new task_output message is sent via WebSocket. For LLM models, each token/chunk arrives as a separate task_output event, enabling real-time streaming. |
task_error |
The model wrote to stderr. This is an interim log event, not a final failure — many models write warnings or debug info to stderr during normal operation. The task may still complete successfully. Always wait for task_postprocess_end to determine the actual result. |
task_output_full |
The complete accumulated stdout log, sent once after the model process finishes. Contains the full output history in a single message. |
task_error_full |
The complete accumulated stderr log, sent once after the model process finishes. |
task_end |
The model process has exited. Emitted once. This fires before post-processing — do not use this event to determine success. Wait for task_postprocess_end instead. |
task_postprocess_start |
Post-processing has started. The system is preparing the output files — encoding, uploading to CDN, and generating access URLs. |
task_postprocess_end |
Post-processing completed. Check pexit to determine success: "0" = success, any other value = error. The outputs array contains the final files with CDN URLs, content types, and sizes. This is the event you should listen for to get the final results. |
task_cancel |
The task was cancelled (if queued) or killed (if running) by the user. |
Realtime Conversation Only
The following statuses are exclusive to realtime conversation models (e.g. voice AI). They are not emitted for standard model runs.
| Status | Description |
|---|---|
task_stream_ready |
Realtime model is ready to receive audio/text input — you can start sending data |
task_stream_end |
Realtime session has ended — the model finished speaking or the session was closed |
task_cost |
Real-time cost update emitted during execution — shows the running cost of the task |
Determining Success or Failure
Both successful and failed tasks reach task_postprocess_end. The status alone does not tell you whether the task succeeded. Wait for task_postprocess_end and then check pexit or outputs (or both) to determine the actual result:
pexit— the process exit code."0"means success, any other value means the model encountered an error. This is the most reliable indicator.outputs— the output array. For non-LLM models, this contains CDN file URLs. For LLM models, this contains a structured entry withcontenttype: "raw"holding the response text, thinking, and answer arrays. If it's empty or missing, the task likely failed.
Note: For LLM models, outputs contains a single entry with contenttype: "raw" and a content object holding prompt, raw, thinking, and answer. The merged plain text is also available in debugoutput. Always use pexit as the primary success check.
// Success (image/audio model): pexit "0", file outputs with CDN URLs
{
"pexit": "0",
"outputs": [{
"name": "0.png",
"contenttype": "image/png",
"size": "202472",
"url": "https://cdn1.wiro.ai/.../0.png"
}]
}
// Success (LLM model): pexit "0", structured raw content in outputs
{
"pexit": "0",
"debugoutput": "Hello! How can I help you today?",
"outputs": [{
"contenttype": "raw",
"content": {
"prompt": "Say hello",
"raw": "Hello! How can I help you today?",
"thinking": [],
"answer": ["Hello! How can I help you today?"]
}
}]
}
// Failure: pexit non-zero
{
"pexit": "1",
"outputs": []
}
Important: task_error events during execution are interim log messages, not final failures. A task can emit error logs and still complete successfully. Always wait for task_postprocess_end and check pexit.
Billing & Cost
The totalcost field in the Task Detail response shows the actual cost charged for the run. Only successful tasks are billed — if pexit is non-zero (failure), the task is not charged and totalcost will be "0".
// Successful run — billed
{
"status": "task_postprocess_end",
"pexit": "0",
"totalcost": "0.003510000000",
"elapsedseconds": "6.0000"
}
// Failed run — not billed
{
"status": "task_postprocess_end",
"pexit": "1",
"totalcost": "0",
"elapsedseconds": "4.0000"
}
Use the totalcost field to track spending per task. For more details on how costs are calculated, see Pricing.
LLM Models
For LLM (Large Language Model) requests, the model's response is available in two places: as merged plain text in debugoutput, and as a structured entry in the outputs array with contenttype: "raw". The structured output includes separate thinking and answer arrays alongside the original prompt and full raw text.
For real-time streaming of LLM responses, use WebSocket instead of polling. Each task_output event delivers a chunk of the response as it's generated, giving your users an instant, token-by-token experience.
POST /Task/Detail
Retrieves the current status and output of a task. You can query by either tasktoken or taskid.
| Parameter | Type | Required | Description |
|---|---|---|---|
tasktoken |
string | No | The task token returned from the Run endpoint |
taskid |
string | No | The task ID (alternative to tasktoken) |
Response
{
"result": true,
"errors": [],
"total": "1",
"tasklist": [{
"id": "534574",
"socketaccesstoken": "eDcCm5yyUfIvMFspTwww49OUfgXkQt",
"parameters": { "prompt": "Hello, world!" },
"status": "task_postprocess_end",
"pexit": "0",
"debugoutput": "",
"starttime": "1734513809",
"endtime": "1734513813",
"elapsedseconds": "6.0000",
"totalcost": "0.003510000000",
"modeldescription": "FLUX.2 [dev] is a 32 billion parameter rectified flow transformer...",
"modelslugowner": "wiro",
"modelslugproject": "flux-2-dev",
"outputs": [{
"name": "0.png",
"contenttype": "image/png",
"size": "202472",
"url": "https://cdn1.wiro.ai/.../0.png"
}]
}]
}
| Field | Type | Description |
|---|---|---|
id | string | Task ID. |
socketaccesstoken | string | Token to connect via WebSocket. |
parameters | object | The input parameters sent in the run request. |
status | string | Current task status (see Task Lifecycle). |
pexit | string | Process exit code. "0" = success. |
debugoutput | string | Accumulated stdout output. For LLM models, contains the merged response text. |
starttime | string | Unix timestamp when execution started. |
endtime | string | Unix timestamp when execution ended. |
elapsedseconds | string | Total execution time in seconds. |
totalcost | string | Actual cost charged for the run in USD. |
modeldescription | string | Description of the model that was executed. |
modelslugowner | string | Model owner slug (e.g. "google", "wiro"). |
modelslugproject | string | Model project slug (e.g. "nano-banana-pro"). |
outputs | array | Output files (CDN URLs) or structured LLM content (contenttype: "raw"). |
POST /Task/Cancel
Cancels a task that is still in the queue stage. Tasks that have already been assigned to a worker cannot be cancelled — use Kill instead.
| Parameter | Type | Required | Description |
|---|---|---|---|
tasktoken |
string | Yes | The task token to cancel |
POST /Task/Kill
Terminates a task that is currently running (any status after
assign). The worker will stop processing and the task will move to cancel status.
| Parameter | Type | Required | Description |
|---|---|---|---|
tasktoken |
string | Yes | The task token to kill |
POST /Task/InputOutputDelete
Deletes all output files and input files associated with a completed task. Removes files from S3 storage, local filesystem, and the database. Also invalidates CloudFront CDN cache so deleted files stop being served immediately.
The task must be in a terminal state (task_postprocess_end or task_cancel). Only the task owner can delete files. Shared sample input files (/sampleinputs/) are automatically excluded from deletion.
| Parameter | Type | Required | Description |
|---|---|---|---|
tasktoken |
string | Yes | The task token (socketaccesstoken) |
Response
{
"result": true,
"errors": []
}
After deletion:
- Output files are removed from S3 and CDN cache
- Input files uploaded by the user are removed from S3 and local storage
- The task's
outputfolderidis set to"0"(Task Detail will return empty outputs) - Task record and parameters are preserved — only the files are deleted
- Calling the endpoint again on the same task returns
result: trueimmediately (idempotent)
Errors
| Error | When |
|---|---|
task-not-exist |
Invalid tasktoken or unauthorized |
Task must be completed or cancelled before deleting files |
Task is still running |
LLM & Chat Streaming
Stream LLM responses in real time with thinking/answer separation, session history, and multi-turn conversations.
Overview
LLM (Large Language Model) requests on Wiro work differently from standard model runs:
- Responses are available as structured content in the
outputsarray (contenttype: "raw") and as merged text indebugoutput - Streaming
task_outputmessages contain structuredthinkingandanswerarrays — not plain strings - Multi-turn conversations are supported via
session_idanduser_idparameters pexitis the primary success indicator
Available LLM models include:
- openai/gpt-5-2 — GPT-5-2
- openai/gpt-oss-20b — GPT OSS 20B
- qwen/qwen3-5-27b — Qwen 3.5 27B
Session & Chat History
Wiro maintains conversation history per session. By sending a session_id, the model remembers previous messages and can build on the context of the conversation. Combined with user_id, this enables fully stateful chat experiences:
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | No | UUID identifying the conversation session. The server stores chat history per session — reuse the same ID for follow-up messages to maintain full context. |
user_id | string | No | UUID identifying the user. Allows the model to distinguish between different users sharing the same session or to personalize responses. |
prompt | string | Yes | The user's message or question. |
// First message — start a new session
{
"prompt": "What is quantum computing?",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
// Follow-up — reuse the same session_id
{
"prompt": "Can you explain qubits in more detail?",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Tip: Generate a new UUID for session_id when starting a fresh conversation. Reuse it for all follow-up messages — the server automatically stores and retrieves the full chat history for that session. To start a new conversation with no prior context, simply generate a new UUID.
Thinking & Answer Phases
Many LLM models separate their output into two phases:
- Thinking — the model's internal reasoning process (chain-of-thought)
- Answer — the final response to the user
When streaming via WebSocket, task_output messages for LLM models contain a structured object (not a plain string):
// LLM task_output message format
{
"type": "task_output",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"type": "progressGenerate",
"task": "Generate",
"speed": "12.4",
"speedType": "words/s",
"raw": "<think>Let me analyze...</think>Quantum computing uses qubits...",
"thinking": ["Let me analyze this step by step...", "The key factors are..."],
"answer": ["Quantum computing uses qubits that can exist in superposition..."],
"isThinking": false,
"elapsedTime": "3s"
}
}
Both thinking and answer are arrays of strings. A model may alternate between thinking and answering multiple times during a single response. The arrays are indexed in pairs — thinking[0] corresponds to answer[0], thinking[1] to answer[1], and so on:
// Multi-turn thinking/answer cycle
{
"thinking": [
"Let me break this into parts...", // thinking[0]
"Now let me verify my reasoning..." // thinking[1]
],
"answer": [
"Quantum computing uses qubits...", // answer[0] — response after thinking[0]
"To summarize: qubits can be 0, 1, or..." // answer[1] — response after thinking[1]
]
}
Each task_output event contains the full accumulated arrays up to that point — not just the new chunk. Simply replace your displayed content with the latest arrays. Use isThinking to show a "thinking" indicator in your UI while the model reasons.
| Field | Type | Description |
|---|---|---|
message.raw | string | Full accumulated raw output including thinking tags. |
message.thinking | string[] | Array of reasoning/chain-of-thought chunks. May be empty for models without thinking. |
message.answer | string[] | Array of response chunks. This is the content to show the user. |
message.isThinking | boolean | Whether the model is currently in a thinking phase. |
message.speed | string | Generation speed (e.g. "12.4"). |
message.speedType | string | Speed unit (e.g. "words/s"). |
message.elapsedTime | string | Elapsed time since generation started (e.g. "3s", "1m 5s"). |
Note: Standard (non-LLM) models send message as a progress object or plain string. LLM models send it as a structured object with thinking, answer, and metadata fields. Check the message.type to distinguish.
Streaming Flow
The complete flow for streaming an LLM response:
- Run the model with
prompt,session_id, anduser_id - Connect to WebSocket and send
task_info - Receive
task_outputmessages — each contains the growingthinkingandanswerarrays - Display the latest
answerarray content to the user (optionally showthinkingin a collapsible section) - Complete — on
task_postprocess_end, checkpexitfor success
Polling Alternative
If you don't need real-time streaming, you can poll POST /Task/Detail instead. The response includes both debugoutput (merged plain text) and a structured entry in outputs with separate thinking and answer arrays:
{
"result": true,
"tasklist": [{
"status": "task_postprocess_end",
"pexit": "0",
"debugoutput": "Quantum computing uses qubits that can exist in superposition...",
"outputs": [{
"contenttype": "raw",
"content": {
"prompt": "What is quantum computing?",
"raw": "Quantum computing uses qubits that can exist in superposition...",
"thinking": [],
"answer": ["Quantum computing uses qubits that can exist in superposition..."]
}
}]
}]
}
Note: debugoutput contains the merged plain text (thinking + answer combined). The outputs array provides the structured breakdown with separate thinking and answer arrays. For real-time token streaming, use WebSocket instead.
WebSocket
Receive real-time task updates via a persistent WebSocket connection.
Connection URL
wss://socket.wiro.ai/v1
Connect to this URL after calling the Run endpoint. Use the
socketaccesstoken from the run response to register your session.
Connection Flow
- Connect — open a WebSocket connection to
wss://socket.wiro.ai/v1 - Register — send a
task_infomessage with yourtasktoken - Receive — listen for messages as the task progresses through its lifecycle
- Close — disconnect after the
task_postprocess_endevent (this is the final event with results)
Registration message format:
{
"type": "task_info",
"tasktoken": "your-socket-access-token"
}
Message Types
| Message Type | Description |
|---|---|
task_queue |
The task is queued and waiting to be picked up by an available worker. |
task_accept |
A worker has accepted the task and is preparing for execution. |
task_preprocess_start |
Optional preprocessing has started (downloading input files from URLs, converting file types, validating parameters). |
task_preprocess_end |
Preprocessing completed. All inputs are ready for GPU assignment. |
task_assign |
The task has been assigned to a specific GPU. The model is being loaded into memory. |
task_start |
The model command has started executing. Inference is now running on the GPU. |
task_output |
The model is producing output. Emitted multiple times — each stdout write sends a new message. For LLMs, each token/chunk arrives as a separate event for real-time streaming. |
task_error |
The model wrote to stderr. This is an interim log event, not a final failure — many models write warnings to stderr during normal operation. The task may still succeed. |
task_output_full |
The complete accumulated stdout log, sent once after the model process finishes. |
task_error_full |
The complete accumulated stderr log, sent once after the model process finishes. |
task_end |
The model process has exited. Fires before post-processing — do not use this to determine success. Wait for task_postprocess_end instead. |
task_postprocess_start |
Post-processing has started. The system is preparing output files — encoding, uploading to CDN, generating access URLs. |
task_postprocess_end |
Post-processing completed. Check pexit to determine success ("0" = success). The outputs array contains the final files. This is the event to listen for. |
task_cancel |
The task was cancelled (if queued) or killed (if running) by the user. |
Message Format
Every WebSocket message is a JSON object with this base structure:
{
"type": "task_accept",
"id": "534574",
"tasktoken": "eDcCm5yyUfIvMFspTwww49OUfgXkQt",
"message": null,
"result": true
}
The type field indicates the status. The message field varies by type — it's null for lifecycle events, a string or object for output events, and an array for the final result.
Lifecycle Events
These events signal task state changes. The message field is null:
// task_accept, task_preprocess_start, task_preprocess_end,
// task_assign, task_start, task_end, task_postprocess_start
{
"type": "task_assign",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": null,
"result": true
}
Output Events
Standard models — message is a progress object or plain string:
// Progress output (image generation, video, etc.)
{
"type": "task_output",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"type": "progressGenerate",
"task": "Generate",
"percentage": "60",
"stepCurrent": "6",
"stepTotal": "10",
"speed": "1.2",
"speedType": "it/s",
"elapsedTime": "5s",
"remainingTime": "3s"
},
"result": true
}
// Simple string output (when no progress format is detected)
{
"type": "task_output",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": "Processing complete.",
"result": true
}
LLM models — message is a structured object with thinking/answer arrays. See LLM & Chat Streaming for full details:
{
"type": "task_output",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"type": "progressGenerate",
"task": "Generate",
"speed": "12.4",
"speedType": "words/s",
"raw": "Quantum computing uses qubits...",
"thinking": ["Let me analyze this..."],
"answer": ["Quantum computing uses qubits..."],
"isThinking": false,
"elapsedTime": "3s"
},
"result": true
}
Error Events
task_error is an interim stderr log, not a final failure. The message is a string or progress object:
{
"type": "task_error",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": "UserWarning: Some weights were not initialized...",
"result": true
}
Full Output Events
Sent once after the process exits. Contains the complete accumulated log:
// Standard model
{
"type": "task_output_full",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"raw": "0%|...| 0/10\n10%|█| 1/10\n...\n100%|██████████| 10/10\nDone."
},
"result": true
}
// LLM model — includes thinking/answer separation
{
"type": "task_output_full",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"raw": "<think>Let me analyze...</think>Quantum computing uses qubits...",
"thinking": ["Let me analyze this step by step..."],
"answer": ["Quantum computing uses qubits that can exist in superposition..."]
},
"result": true
}
// Stderr log (only sent if stderr is non-empty)
{
"type": "task_error_full",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": {
"raw": "UserWarning: Some weights were not initialized..."
},
"result": true
}
Final Result
task_postprocess_end is the event you should listen for. The message contains the outputs array:
// Standard model — file outputs with CDN URLs
{
"type": "task_postprocess_end",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": [{
"name": "0.png",
"contenttype": "image/png",
"size": "202472",
"url": "https://cdn1.wiro.ai/.../0.png"
}],
"result": true
}
// LLM model — structured raw content
{
"type": "task_postprocess_end",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"message": [{
"contenttype": "raw",
"content": {
"prompt": "Explain quantum computing",
"raw": "Quantum computing uses qubits...",
"thinking": [],
"answer": ["Quantum computing uses qubits..."]
}
}],
"result": true
}
Realtime Events
These events are exclusive to realtime voice models:
// Session is ready — start sending audio
{
"type": "task_stream_ready",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"result": true
}
// AI finished speaking for this turn
{
"type": "task_stream_end",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"result": true
}
// Cost update per turn
{
"type": "task_cost",
"id": "534574",
"tasktoken": "eDcCm5yy...",
"turnCost": 0.002,
"cumulativeCost": 0.012,
"usage": { "input_tokens": 150, "output_tokens": 89 },
"result": true
}
Binary Frames
For realtime voice models, the WebSocket may send binary frames containing raw audio data. Check if the received message is a Blob (browser) or Buffer (Node.js) before parsing as JSON.
Ending a Session
For realtime/streaming models that maintain a persistent session, send a task_session_end message to gracefully terminate:
{
"type": "task_session_end",
"tasktoken": "your-socket-access-token"
}
After sending this, wait for the task_postprocess_end event before closing the connection. This is the final event that contains the complete results.
Realtime Voice
Build interactive voice conversation apps with realtime AI models.
Overview
Realtime voice models enable two-way audio conversations with AI. Unlike standard model runs that process a single input and return a result, realtime sessions maintain a persistent WebSocket connection where you stream microphone audio and receive AI speech in real time.
The flow is:
- Run the realtime model via POST /Run to get a
socketaccesstoken - Connect to the WebSocket and send
task_infowith your token - Wait for
task_stream_ready— the model is ready to receive audio - Stream microphone audio as binary frames
- Receive AI audio as binary frames and play them
- End the session with
task_session_end
Run Parameters
Each realtime model has its own set of parameters. Use POST /Tool/Detail to discover the exact parameters for a specific model. See Model Parameters for details on parameter types.
Available realtime conversation models include:
- openai/gpt-realtime-mini — GPT Mini Realtime Voice Assistant
- openai/gpt-realtime — GPT Realtime Voice Assistant
- elevenlabs/realtime-conversational-ai — ElevenLabs Conversational AI
Common parameters across realtime models typically include voice selection, system instructions, and audio format settings. Example run for an OpenAI realtime model:
curl -X POST "https://api.wiro.ai/v1/Run/openai/gpt-realtime-mini" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"voice": "marin",
"system_instructions": "You are a friendly assistant.",
"input_audio_format": "audio/pcm",
"output_audio_format": "audio/pcm",
"input_audio_rate": "24000",
"output_audio_rate": "24000"
}'
Important: Parameters vary per model. Always check the model's detail page or use /Tool/Detail to get the exact parameter list before integrating.
Connection & Registration
After running the task, connect to the WebSocket and register with task_info:
var ws = new WebSocket("wss://socket.wiro.ai/v1");
ws.onopen = function() {
ws.send(JSON.stringify({
type: "task_info",
tasktoken: "YOUR_SOCKET_ACCESS_TOKEN"
}));
};
Note: Both standard and realtime models use type: "task_info" with tasktoken to register on the WebSocket.
Realtime Events
During a realtime session, you'll receive these WebSocket events:
| Event | Description |
|---|---|
task_stream_ready | Session is ready — start sending microphone audio |
task_stream_end | AI finished speaking for this turn — you can speak again |
task_cost | Cost update per turn — includes turnCost, cumulativeCost, and usage (raw cost breakdown from the model provider) |
task_output | Transcript messages prefixed with TRANSCRIPT_USER: or TRANSCRIPT_AI: |
task_end | The model process has exited. Post-processing follows — wait for task_postprocess_end to close the connection. |
Audio Format
Both directions (microphone → server, server → client) use the same format:
| Property | Value |
|---|---|
| Format | PCM (raw, uncompressed) |
| Bit depth | 16-bit signed integer (Int16) |
| Sample rate | 24,000 Hz (24 kHz) |
| Channels | Mono (1 channel) |
| Byte order | Little-endian |
| Chunk size | 4,800 samples (200 ms) = 9,600 bytes |
Binary Frame Format
Every binary WebSocket frame (in both directions) is structured as:
[tasktoken]|[PCM audio data]
The pipe character | (0x7C) separates the token from the raw audio bytes.
Sending Microphone Audio
Capture microphone at 24 kHz using the Web Audio API with an AudioWorklet. Convert Float32 samples to Int16, prepend your task token, and send as a binary frame.
Key steps:
- Request microphone with
getUserMedia(enable echo cancellation and noise suppression) - Create an
AudioContextat 24,000 Hz sample rate - Use an AudioWorklet to buffer and convert samples to Int16
- Send each chunk as
tasktoken|pcm_databinary frame
Receiving AI Audio
AI responses arrive as binary WebSocket frames in the same PCM Int16 24 kHz format. To play them:
- Check if the message is a
Blob(binary) before parsing as JSON - Find the pipe
|separator and extract audio data after it - Convert Int16 → Float32 and create an
AudioBuffer - Schedule gapless playback using
AudioBufferSourceNode
Transcripts
Both user and AI speech are transcribed automatically. Transcripts arrive as task_output messages with a string prefix:
TRANSCRIPT_USER:— what the user saidTRANSCRIPT_AI:— what the AI said
// Example task_output message
{
"type": "task_output",
"message": "TRANSCRIPT_USER:What's the weather like today?"
}
{
"type": "task_output",
"message": "TRANSCRIPT_AI:I'd be happy to help, but I don't have access to real-time weather data."
}
Ending a Session
To gracefully end a realtime session, send task_session_end:
{
"type": "task_session_end",
"tasktoken": "YOUR_SOCKET_ACCESS_TOKEN"
}
After sending this, the server will process any remaining audio, send final cost/transcript events, and then emit task_postprocess_end. Wait for task_postprocess_end before closing the WebSocket.
Safety: If the client disconnects without sending task_session_end, the server automatically terminates the session to prevent the pipeline from running indefinitely (and the provider from continuing to charge). Always send task_session_end explicitly for a clean shutdown.
Insufficient balance: If the wallet runs out of balance during a realtime session, the server automatically stops the session. You will still receive the final task_cost and task_end events.
Realtime Text to Speech
Stream text-to-speech audio in real time using Wiro realtime TTS models.
Overview
Realtime TTS models convert text into streaming audio. Unlike standard TTS that returns a complete audio file, realtime TTS streams synthesized speech via WebSocket as it generates — enabling instant playback without waiting for the full audio to be produced.
No microphone is needed — you simply submit text and receive audio.
See the full documentation for complete details, audio format specification, and code examples in JavaScript, Python, and Node.js.
Realtime Speech to Text
Stream microphone audio and receive live transcription via WebSocket.
Overview
Realtime STT models transcribe audio in real time. Stream microphone audio via WebSocket and receive progressive text transcription as the user speaks — enabling live dictation, captioning, and transcription applications.
No audio playback is needed — you send microphone audio and receive text.
See the full documentation for complete details, audio format specification, and code examples in JavaScript, Python, and Node.js.
Files
Manage folders and upload files for use with AI models.
Overview
The Files API lets you organize and upload data that can be referenced in model runs. Common use cases include:
- Training data — upload datasets for fine-tuning models
- File inputs — provide images, audio, or documents as model inputs
- Batch processing — store files for repeated use across multiple runs
POST /File/FolderCreate
Creates a new folder to organize your uploaded files.
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Folder name |
parentid |
string | No | Parent folder ID for nested structure (omit for root) |
Note: Folder names only allow letters, numbers, hyphens, and underscores (A-Z a-z 0-9 _ -).
Response
{
"result": true,
"errors": [],
"list": [{
"id": "folder-abc123",
"name": "training-data",
"parentid": "root-folder-id",
"size": "0",
"contenttype": "",
"addedtime": "1716276543"
}]
}
POST /File/Upload
Uploads a file using multipart/form-data. You can optionally assign it to a folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
file |
file | Yes | The file to upload (multipart form field) |
folderid |
string | No | Target folder ID (uploads to user's default folder if omitted) |
File size limit: 100 MB per file.
Supported file types: Images (jpg, png, gif, jpeg, webp, heic), video (mp4, webm, mov), audio (mp3, wav, m4a), documents (pdf, csv, docx, xlsx, pptx, txt, md, epub), and ZIP archives (automatically extracted).
Response
{
"result": true,
"errors": [],
"list": [{
"id": "file-id",
"name": "dataset.csv",
"contenttype": "text/csv",
"size": "1048576",
"parentid": "folder-id",
"url": "https://cdn1.wiro.ai/...",
"addedtime": "1716276727",
"accesskey": "..."
}]
}
Using Files in Runs
Once uploaded, reference a file by its URL or ID in your model run parameters. For example, an image upscaler model might accept a
imageUrl parameter — pass the URL returned from the upload response.
{
"imageUrl": "https://files.wiro.ai/...",
"scale": 4
}
Pricing
Understand how billing works for AI model runs on Wiro.
Overview
When you run an AI model through Wiro, you are billed based on the type of work performed. Each model has its own pricing, visible on the model's page in the marketplace and on the pricing page. You pay only for successful runs — server errors are never billed.
Wiro uses a prepaid credit model. You add credits to your account and they are drawn down as you use models. Credits also determine your concurrency limit.
Billing Methods
Every model on Wiro uses one of the following billing methods. The method is set per model and determines how the cost is calculated.
Fixed-Rate Methods
| Billing Method | Code | How it works |
|---|---|---|
| Per Request | cpr | Fixed cost per run, regardless of output. Most common for image generation, image editing, and simple models. |
| Per Second | cps | Cost per second of processing time. When no dynamic pricing is set, this is the default — cost = elapsed seconds × cps rate. |
| Per Output | cpo | Cost per output item generated. Multiple files = pay per file. No output = base price charged once. |
| Per Token | cpt | Cost per token used. Total tokens (input + output) extracted from model's usage metadata. Used for LLM models. |
Usage-Based Methods
| Billing Method | Code | How it works |
|---|---|---|
| Per Pixel | cp-pixel | Cost based on output resolution. Each 1,048,576 pixels (1024×1024) = one tier. Can include per-input-image costs (priceInput). |
| Per Audio Second | cp-audiosecondslength | Cost per second of input audio duration. Duration measured via ffprobe. |
| Per Character | cp-promptlength | Cost per character in the input prompt. Total = prompt length × price. |
| Per Video Second | cp-outputVideoLength | Cost per second of generated output video. Duration measured via ffprobe. |
Special Methods
| Billing Method | Code | How it works |
|---|---|---|
| Per Realtime Turn | cp-realtimeturn | For realtime voice models. Billing per conversation turn, deducted in real time during the session. |
| Model-Reported | cp-readoutput | The model reports its own cost in stdout/stderr JSON output. |
Dynamic Pricing
Many models have dynamic pricing — the cost varies based on the input parameters you choose. For example, a video generation model might charge different rates depending on the resolution and duration you select.
The pricing is returned in the dynamicprice field of the Tool/List and Tool/Detail API responses as a JSON array:
[
{
"inputs": { "resolution": "720p", "duration": "5" },
"price": 0.13,
"priceMethod": "cpr"
},
{
"inputs": { "resolution": "1080p", "duration": "5" },
"price": 0.29,
"priceMethod": "cpr"
}
]
How Dynamic Pricing Works
Each entry in the dynamicprice array represents a pricing tier:
| Field | Type | Description |
|---|---|---|
inputs | object | The input parameter combination this price applies to. Empty {} means the price applies to all configurations. |
price | number | The cost in USD for this configuration. |
priceMethod | string | The billing method code (see tables above). |
priceExtra | number (optional) | Extra cost per additional tier. Used by cp-pixel — each additional 1MP tier costs this amount. |
priceInput | number (optional) | Per-input cost. Used by cp-pixel — each input image incurs this cost per 1MP tier. |
When inputs contains specific parameter values (e.g. "resolution": "720p"), that price only applies when you run the model with those exact parameters. When inputs is empty ({}), it's a flat rate that applies regardless of input parameters.
Input matching also supports QUANTITY values (e.g. "QUANTITY:1", "QUANTITY:3") for models where the number of input files affects pricing.
Example: Video Generation Pricing
A video model might have pricing tiers based on resolution and duration:
| Resolution | Duration | Price |
|---|---|---|
| 480p | 5 seconds | $0.06 |
| 720p | 5 seconds | $0.13 |
| 1080p | 5 seconds | $0.29 |
| 480p | 10 seconds | $0.12 |
| 720p | 10 seconds | $0.26 |
| 1080p | 10 seconds | $0.58 |
Example: Simple Flat Pricing
An image generation model with a flat rate:
[{ "inputs": {}, "price": 0.03, "priceMethod": "cpr" }]
This means every run costs $0.03, regardless of parameters.
Fallback Pricing (Per-Second)
When a model does not have dynamicprice set, billing falls back to per-second pricing:
totalcost = elapsed_seconds × cps
Where cps (cost per second) is either the model's own rate or the queue group's default rate. The API also returns an approximatelycost field — an estimate based on the model's average run time:
approximatelycost = average_elapsed_seconds × cps
This gives you a rough idea of the expected cost before running the model.
Checking Prices
Via the API
Pricing information is included in both the Tool/List and Tool/Detail responses in the dynamicprice field. Use POST /Tool/Detail with the model's slugowner and slugproject to get full pricing details.
Via MCP
When using the MCP server, both search_models and get_model_schema tools return pricing information in their responses. Your AI assistant can check the cost before running a model.
Pricing Page
Browse and compare model prices interactively on the pricing page. Select a budget to see how many runs each model can perform.
What You Pay For
You are billed for successfully completed model runs. A run is successful when the task reaches task_postprocess_end status with pexit of "0". The actual cost is recorded in the task's totalcost field, which you can retrieve via Task/Detail.
What You Are Not Charged For
- Server errors — if a run fails due to a server-side error, no charge is incurred.
- Queue time — time spent waiting in the queue before processing starts is free.
- Cancelled tasks — tasks cancelled before processing completes are not billed.
Monitoring Your Spending
- Check the
totalcostfield in Task/Detail responses to see the cost of individual runs. - View your overall balance, usage history, and billing details in the Dashboard.
- When using MCP, the
get_tasktool returns the cost of completed runs.
Concurrency Limits
Understand and manage how many requests you can run simultaneously on Wiro.
Overview
Concurrency limits control how many tasks your account can process at the same time. When you reach your limit, the API returns an error response with code 96. You should wait for a running task to complete before submitting a new one, or add funds to increase your limit.
How It Works
Your concurrency limit is determined by your current account balance:
- When your balance is $250 or below, you can run concurrent tasks equal to 10% of your current USD balance (minimum 1).
- When your balance is above $250, there is no concurrency limit.
Examples
| Account Balance | Concurrent Task Limit |
|---|---|
| $10 | 1 concurrent task (minimum) |
| $50 | 5 concurrent tasks |
| $100 | 10 concurrent tasks |
| $150 | 15 concurrent tasks |
| $250 | 25 concurrent tasks |
| $251+ | Unlimited (no limit applied) |
The formula: max(1, floor(balance_usd * 0.10)). Once your balance exceeds $250, all limits are removed.
What Counts as Active
Only tasks that are actively being processed count toward your concurrency limit. A task is considered active from task_queue until it reaches a terminal status:
task_postprocess_end— task completed (success or failure)task_cancel— task was cancelled or killed
Once a task reaches either of these statuses, it no longer counts toward your limit.
API Response
When you hit the concurrency limit, the POST /Run endpoint returns an error with code 96:
{
"result": false,
"errors": [
{
"code": 96,
"message": "You have reached your concurrent task limit. With your current balance of $50.00, you can run up to 5 tasks at the same time. Add funds to increase your limit."
}
]
}
The error message includes your current balance and the calculated limit, so you know exactly how many concurrent tasks you can run.
Error Codes
| Code | Meaning | Action |
|---|---|---|
96 | Concurrent task limit reached | Wait for a running task to finish, or add funds |
97 | Insufficient balance | Add funds to your account |
Increasing Your Limit
To increase your concurrency limit, simply add credits to your account. Your limit is recalculated automatically based on your current balance at the time of each run request.
For enterprise needs or custom concurrency arrangements, contact support.
Best Practices
- Check error code
96— if you get this error, wait for a running task to complete before submitting new ones. - Use WebSocket for monitoring — instead of polling
/Task/Detailrepeatedly, connect via WebSocket to get real-time updates without extra API calls. - Use
wait=falsein MCP — for long-running models (video, 3D), submit withwait=falseand check withget_taskto avoid holding connections. - Implement exponential backoff — if polling task status, start at 3 seconds and increase the interval for longer tasks.
Error Reference
Understand API error responses, error codes, and how to handle them.
Response Format
When an API request fails, the response includes result: false and an errors array:
{
"result": false,
"errors": [
{
"code": 97,
"message": "Insufficient balance"
}
]
}
All API responses return HTTP 200 — use the result field and error code to determine success or failure.
Error Codes
Error codes indicate the category of the problem. Use these for conditional logic in your application.
| Code | Category | Description |
|---|---|---|
0 | General | Server-side errors, validation failures, missing parameters |
1 | Not Found / Client | Resource not found or not accessible |
96 | Concurrency Limit | Too many concurrent tasks for your balance. See Concurrency Limits |
97 | Insufficient Balance | Not enough funds to run the model |
98 | Authentication Required | Sign in required to access this model |
99 | Token Invalid | Bearer token missing, invalid, or expired |
Authentication Errors
Returned when API key or bearer token authentication fails. All return HTTP 401.
| Error | Code | Message |
|---|---|---|
| API key not found | 0 | Project authorization is not founded. |
| Signature required | 0 | Project requires signature authentication. x-signature and x-nonce headers are required. |
| Invalid signature | 0 | Project authorization is not valid. |
| IP not allowed | 0 | Requested ip {ip} is not allowed. |
| Bearer token missing | 99 | Authorization bearer token is not founded in headers. |
| Bearer token invalid | 99 | Authorization bearer token is invalid. |
| Bearer token expired | 99 | Authorization bearer token expired. |
| Endpoint not found | 0 | Error parsing url. (HTTP 404) |
Run Errors
Returned by POST /Run/{owner}/{model}.
Balance & Limits
| Error | Code | Message | Action |
|---|---|---|---|
| Insufficient balance | 97 | Insufficient balance | Add funds — minimum $0.50 required ($10 for training) |
| Concurrent task limit | 96 | You have reached your concurrent task limit. With your current balance of ${balance}, you can run up to {maxConcurrent} tasks at the same time. | Wait for a task to finish, or add funds. See Concurrency Limits |
| Sign in required | 98 | sign in to run this model | Model requires a registered account |
Validation Errors
| Error | Code | Message |
|---|---|---|
| Missing parameter | 0 | Request parameter [{name}] required |
| Invalid number | 0 | Request parameter [{name}] must be integer or float |
| Out of range | 0 | Request parameter [{name}] must be between {min} and {max} |
| File required | 0 | Request files [{name}] required |
| Invalid request body | 0 | Request parameters are invalid. |
Model Access Errors
| Error | Code | Message |
|---|---|---|
| Model not accessible | 1 | tool-not-accessible |
| Model not found | 1 | slug-owner-project-not-exist |
| Account suspended | 0 | Your account has been suspended. Please contact support. |
| Permission denied | 0 | You don't have any permission for this action. |
Task Errors
Returned by POST /Task/Detail, POST /Task/Cancel, POST /Task/Kill, and POST /Task/InputOutputDelete.
| Error | Code | Message | Endpoint |
|---|---|---|---|
| Task not found | 1 | There is no task yet. | Detail, Cancel, Kill, InputOutputDelete |
| Missing identifier | 0 | taskid or socketaccesstoken is required | Detail |
| Not cancellable | 1 | Task is not in a cancellable state. | Cancel |
| Kill failed | 1 | Task could not be killed: {reason} | Kill |
| Task not completed | 1 | Task must be completed or cancelled before deleting files | InputOutputDelete |
| Permission denied | 0 | You don't have any permission for this action. | All |
Error Handling
Always check result first, then inspect the code in the errors array:
const data = await response.json();
if (!data.result) {
const error = data.errors[0];
switch (error.code) {
case 96:
console.log('Concurrent limit — wait for a task to finish');
break;
case 97:
console.log('Insufficient balance — add funds');
break;
case 98:
console.log('Sign in required');
break;
default:
console.log('Error:', error.message);
}
}
Retry Strategy
| Code | Retryable | Strategy |
|---|---|---|
0 (validation) | No | Fix the request parameters |
0 (server) | Yes | Retry with exponential backoff |
1 | No | Check model slug or task token |
96 | Yes | Wait for a running task to complete |
97 | No | Add funds, then retry |
98 | No | Sign in or use authenticated credentials |
99 | No | Check your API key or bearer token |
FAQ
Common questions about using the Wiro API. If you can't find the answer here, contact support.
Sign up at wiro.ai, then create a project at wiro.ai/panel/project. Your API key (and secret, if signature-based) are displayed once — copy and store them securely.
Signature-Based is recommended — it uses HMAC-SHA256 so your API secret never leaves your environment. API Key Only is simpler and fine for server-side applications where you control the environment. See Authentication for details.
Yes. Every account has a concurrency limit that controls how many tasks can run at the same time. The limit scales automatically based on your account balance. See Concurrency Limits for the full table.
No. If a task fails (non-zero pexit), you are not charged. Only successfully completed tasks are billed.
Use the POST /Tool/Detail endpoint — the response includes the model's pricing information. If you're using the MCP server, the search_models and get_model_schema tools also return pricing.
Output files are stored on Wiro's CDN and available for a limited time. Download and store any files you need to keep long-term. See Files for details on file management.
Yes. Output URLs returned by Wiro are publicly accessible. Anyone with the URL can access the file. If you need private storage, download the files to your own infrastructure.
Connect to the WebSocket at wss://socket.wiro.ai/v1 and register with the socketaccesstoken from your run response. You'll receive events as the task progresses. For simpler integrations, you can poll the Task Detail endpoint.
LLM models return their response in two places: as merged text in debugoutput, and as a structured entry in the outputs array with contenttype: "raw" containing separate thinking and answer arrays. For streaming, each token arrives as a separate task_output WebSocket event. See LLM & Chat Streaming for details.
Yes. For fileinput and multifileinput parameters, use the {id}Url suffix (e.g., inputImageUrl). For combinefileinput, pass URLs directly in the original parameter. You can also pass a URL directly to any file parameter if the {id}Url field doesn't exist. See Model Parameters.
pexit is the process exit code — "0" means success, any other value means failure. It's the most reliable way to determine if a task succeeded. Always check pexit in the task_postprocess_end event or Task Detail response. See Tasks.
No. The Wiro MCP server is free. You only pay for the model runs you trigger, at standard pricing.
Yes. Install the Wiro AI community node in your n8n instance to access all Wiro models as drag-and-drop nodes in your workflows.
Yes. All models support an optional callbackUrl parameter. When provided, Wiro will POST the task result to your URL when the task completes. See Webhook Callback in Model Parameters.
Code Examples
Complete end-to-end examples in all 9 supported languages.
Overview
Each example below demonstrates the full Wiro workflow: authenticate, run a model, poll for task completion, and retrieve the result. Choose your preferred language from the tabs.
- curl — Shell scripting with bash
- Python — Using the
requestslibrary - Node.js — Using
axios - PHP — Using cURL functions
- C# — Using
HttpClient(.NET 6+) - Swift — Using async/await
URLSession - Dart — Using the
httppackage - Kotlin — Using
java.net.http - Go — Using the standard library
net/http
Full Examples
Select a language tab to see the complete example. All examples perform the same steps:
- Set up authentication headers
- Run a model (
POST /Run/{owner-slug}/{model-slug}) - Poll the task status (
POST /Task/Detail) - Print the final output
Wiro MCP Server
Connect AI coding assistants to Wiro's AI models via the Model Context Protocol.
What is MCP?
Model Context Protocol (MCP) is an open standard that lets AI assistants use external tools directly. With the Wiro MCP server, your AI assistant can search models, run inference, track tasks, and upload files — all without leaving your editor.
The hosted MCP server is available at mcp.wiro.ai/v1 and works with any MCP-compatible client, including Cursor, Claude Code, Claude Desktop, and Windsurf. Every request uses your own API key — nothing is stored on the server.
You need a Wiro API key to use the MCP server. If you don't have one yet, create a project here.
Links
- Model Context Protocol (MCP) — open standard specification
- GitHub: wiroai/Wiro-MCP — source code & self-hosting instructions
- npm: @wiro-ai/wiro-mcp — npm package
- Wiro Model Catalog — browse all available models
- Create API Key — get started in seconds
Setup
Connect your AI assistant to Wiro's MCP server. Pick your client:
Cursor
- Open MCP Settings — Use
Cmd+Shift+P(Ctrl+Shift+Pon Windows) and search for "Open MCP settings". - Add the Wiro server — Add the following to your
mcp.jsonfile:Signature Auth (if your project uses signature-based authentication):
{ "mcpServers": { "wiro": { "url": "https://mcp.wiro.ai/v1", "headers": { "Authorization": "Bearer YOUR_API_KEY:YOUR_API_SECRET" } } } }API Key Only Auth (if your project uses API Key Only authentication):
{ "mcpServers": { "wiro": { "url": "https://mcp.wiro.ai/v1", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } - Restart Cursor — Save the file and restart Cursor to activate the connection.
Claude Code
Run this command in your terminal:
claude mcp add --transport http wiro \
https://mcp.wiro.ai/v1 \
--header "Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET"
That's it. Claude Code will now have access to all Wiro tools.
Claude Desktop
Add the following to your claude_desktop_config.json:
{
"mcpServers": {
"wiro": {
"url": "https://mcp.wiro.ai/v1",
"headers": {
"Authorization": "Bearer YOUR_API_KEY:YOUR_API_SECRET"
}
}
}
}
Windsurf
Open Settings → MCP and add a new server:
{
"mcpServers": {
"wiro": {
"serverUrl": "https://mcp.wiro.ai/v1",
"headers": {
"Authorization": "Bearer YOUR_API_KEY:YOUR_API_SECRET"
}
}
}
}
Other MCP Clients
The Wiro MCP server uses the Streamable HTTP transport at:
https://mcp.wiro.ai/v1
Authentication is via the Authorization header:
Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET
Or for API Key Only auth: Bearer YOUR_API_KEY
Credentials are sent as plain text — no base64 encoding needed. Any MCP client that supports Streamable HTTP transport can connect.
Authentication
The MCP server supports both Wiro authentication types. Your project's auth type determines what credentials you provide.
Signature-Based (Recommended)
More secure. Requires both API key and API secret. Pass them as plain text separated by a colon:
Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET
API Key Only
Simpler. Only requires the API key:
Authorization: Bearer YOUR_API_KEY
No base64 encoding needed. Credentials are sent per-request and are never stored. The server is fully stateless.
Available Tools
The MCP server exposes 11 tools organized in four categories. Your AI assistant picks the right tool automatically.
Model slugs: Use the clean/lowercase format owner/model (e.g. openai/sora-2, wiro/virtual-try-on). These correspond to the cleanslugowner/cleanslugproject values returned by search_models.
Discovery
| Tool | Description |
|---|---|
search_models | Search Wiro's model catalog by keyword, category, or owner |
get_model_schema | Get the full parameter schema and pricing for any model |
recommend_model | Describe what you want to build and get model recommendations ranked by relevance |
explore | Browse curated AI models organized by category — featured, recently added, popular |
Execution
| Tool | Description |
|---|---|
run_model | Run any model and wait for the result, or submit and get a task token |
Task Management
| Tool | Description |
|---|---|
get_task | Check task status, outputs, cost, and elapsed time |
get_task_price | Get the cost of a completed task — shows whether it was billed and the total charge |
cancel_task | Cancel a task still in the queue |
kill_task | Kill a task that is currently running |
Utility
| Tool | Description |
|---|---|
upload_file | Upload a file from a URL to Wiro. Most models accept direct URLs without uploading first — use this for reuse across runs. |
search_docs | Search the Wiro documentation for guides, API references, and examples |
Examples
Generate an image
"Generate a photorealistic image of a mountain lake at golden hour"
The assistant will use search_models → get_model_schema → run_model and return the image URL.
Generate a video
"Create a 5-second cinematic video of a drone shot over mountains using Kling V3"
The assistant will use get_model_schema → run_model (wait=false) → get_task to poll for the result.
Find models
"What models are available for text-to-video?"
The assistant will call search_models with categories filter.
How It Works
The MCP server is stateless. Each request is fully isolated:
- Your AI assistant sends a request to
mcp.wiro.ai/v1with your credentials - The server calls the Wiro API on your behalf
- Results are returned to your assistant
All tools are dynamic — they fetch model data at runtime. New models are instantly available via MCP.
Tool Reference
search_models
Search Wiro's model catalog by keyword, category, owner, or any combination. Calls POST /Tool/List on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
search | string (optional) | Free-text search, e.g. "flux", "video generation" |
categories | string[] (optional) | Filter by category: text-to-image, text-to-video, image-to-video, llm, text-to-speech, image-editing, etc. |
slugowner | string (optional) | Filter by model owner slug, e.g. "openai", "stability-ai", "klingai" |
sort | string (optional) | Sort by: relevance, time, ratedusercount, commentcount, averagepoint |
start | number (optional) | Pagination offset (default 0) |
limit | number (optional) | Max results (default 20, max 100) |
Returns a list of models with their cleanslugowner/cleanslugproject (the slug you pass to other tools), title, description, categories, and pricing.
get_model_schema
Get the full parameter schema for a specific model. Calls POST /Tool/Detail on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
model | string | Model slug using clean/lowercase format: "owner/model". Use cleanslugowner/cleanslugproject from search_models. Examples: "openai/sora-2", "black-forest-labs/flux-2-pro", "wiro/virtual-try-on" |
Returns the model's parameter groups, each containing items with id, type (text, textarea, select, range, fileinput, etc.), label, required, options, default, and note. Also includes pricing information. Use these to construct the params object for run_model.
recommend_model
Describe what you want to create and get model recommendations ranked by relevance. Calls POST /Tool/List on the Wiro API with relevance sorting.
| Parameter | Type | Description |
|---|---|---|
task | string | What you want to do, e.g. "generate a photorealistic portrait", "upscale an image to 4K", "transcribe audio to text" |
Returns a list of recommended models with slugs, descriptions, categories, and pricing — sorted by relevance to your task.
explore
Browse curated AI models on Wiro, organized by category. Calls POST /Tool/Explore on the Wiro API. No parameters required.
Returns models grouped into curated sections like "Recently Added", "Image Generation", "Video", etc. Each model includes its slug, description, categories, and rating. Use this to discover what's available without searching.
run_model
Run any AI model on Wiro. Calls POST /Run/{owner}/{model} on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
model | string | Model slug in clean/lowercase format. Same as get_model_schema. Examples: "openai/sora-2", "klingai/kling-v3" |
params | object | Model-specific parameters as key-value pairs. Use get_model_schema to discover accepted fields. Common: prompt, negativePrompt, width, height, aspectRatio |
wait | boolean (optional) | If true (default), polls POST /Task/Detail until the task completes and returns the result. If false, returns the task token immediately for async monitoring via get_task. |
timeout_seconds | number (optional) | Max seconds to wait (default 120, max 600). Only applies when wait=true. |
When wait=true, returns the final task result including pexit (exit code, "0" = success), outputs (CDN URLs for generated files), and debugoutput (LLM text responses).
When wait=false, returns taskid and tasktoken — use get_task to check progress.
get_task
Check task status and get results. Calls POST /Task/Detail on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
tasktoken | string (optional) | The task token returned from run_model |
taskid | string (optional) | The task ID (alternative to tasktoken) |
Returns the task's current status, pexit (process exit code), outputs (file URLs), debugoutput (LLM responses), elapsedseconds, and totalcost.
Determining success: Check pexit — "0" means success, any other value means failure. For LLM models, the response is available as structured content in outputs (with contenttype: "raw") and as merged text in debugoutput. See Tasks for the full task lifecycle.
get_task_price
Get the cost of a completed task. Calls POST /Task/Detail on the Wiro API and returns billing information.
| Parameter | Type | Description |
|---|---|---|
tasktoken | string (optional) | The task token returned from run_model |
taskid | string (optional) | The task ID (alternative to tasktoken) |
Returns the task's billing status, total cost, and duration. Only successful tasks (pexit: "0") are billed — failed tasks show $0 with a clear explanation that they were not charged.
cancel_task
Cancel a task that is still queued (before worker assignment). Calls POST /Task/Cancel on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
tasktoken | string | The task token to cancel |
Tasks that have already been assigned to a worker cannot be cancelled — use kill_task instead.
kill_task
Kill a task that is currently running (after worker assignment). Calls POST /Task/Kill on the Wiro API.
| Parameter | Type | Description |
|---|---|---|
tasktoken | string | The task token to kill |
The worker will stop processing and the task will move to task_cancel status.
upload_file
Upload a file from a URL to Wiro for use as model input. Downloads the file and uploads it via POST /File/Upload.
Tip: Most models accept direct URLs in file parameters (e.g. inputImage, inputImageUrl) — you don't need to upload first. Use upload_file when you need to reuse the same file across multiple runs or when the model specifically requires a Wiro-hosted file. See Model Parameters for details.
| Parameter | Type | Description |
|---|---|---|
url | string | URL of the file to upload (image, audio, video, document) |
file_name | string (optional) | Custom filename. If not provided, derived from the URL. |
Returns the uploaded file's Wiro URL, which can be passed to any model parameter that accepts a file.
search_docs
Search the Wiro documentation for guides, API references, and code examples.
| Parameter | Type | Description |
|---|---|---|
query | string | What you're looking for, e.g. "how to upload a file", "websocket", "authentication", "LLM streaming" |
Returns relevant documentation sections matching your query.
FAQ
What models can I use?
All models in the Wiro catalog.
Is my API key stored?
No. The server is fully stateless. Credentials are sent per-request and never stored.
Does it cost extra?
No. The MCP server is free. You pay for model runs at standard pricing.
Can I self-host?
Yes. See Self-Hosted MCP for instructions.
Self-Hosted MCP
Run the Wiro MCP server locally on your own machine using npx.
Quick Start
Add to your AI assistant's MCP config:
{
"mcpServers": {
"wiro": {
"command": "npx",
"args": ["-y", "@wiro-ai/wiro-mcp"],
"env": {
"WIRO_API_KEY": "your-api-key",
"WIRO_API_SECRET": "your-api-secret"
}
}
}
}
That's it. Your assistant now has access to all Wiro AI models.
Setup
Add the self-hosted MCP server to your AI assistant:
Cursor
Open MCP settings (Cmd+Shift+P → "Open MCP settings") and add:
{
"mcpServers": {
"wiro": {
"command": "npx",
"args": ["-y", "@wiro-ai/wiro-mcp"],
"env": {
"WIRO_API_KEY": "your-api-key",
"WIRO_API_SECRET": "your-api-secret"
}
}
}
}
Claude Code
claude mcp add wiro -- npx -y @wiro-ai/wiro-mcp
Then set environment variables:
export WIRO_API_KEY="your-api-key"
export WIRO_API_SECRET="your-api-secret"
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"wiro": {
"command": "npx",
"args": ["-y", "@wiro-ai/wiro-mcp"],
"env": {
"WIRO_API_KEY": "your-api-key",
"WIRO_API_SECRET": "your-api-secret"
}
}
}
}
Windsurf
Add to your MCP settings:
{
"mcpServers": {
"wiro": {
"command": "npx",
"args": ["-y", "@wiro-ai/wiro-mcp"],
"env": {
"WIRO_API_KEY": "your-api-key",
"WIRO_API_SECRET": "your-api-secret"
}
}
}
}
Other Clients
Any MCP client that supports the stdio transport can connect. The command is:
npx -y @wiro-ai/wiro-mcp
Set WIRO_API_KEY and WIRO_API_SECRET as environment variables in your client's MCP configuration.
Authentication
Signature-Based (Recommended)
Provide both API key and secret:
WIRO_API_KEY=your-api-key
WIRO_API_SECRET=your-api-secret
API Key Only
Omit WIRO_API_SECRET:
WIRO_API_KEY=your-api-key
Available Tools
The self-hosted server provides the same 11 tools as the hosted MCP server. Your AI assistant picks the right tool automatically.
Model slugs: Use the clean/lowercase format owner/model (e.g. openai/sora-2, wiro/virtual-try-on). These correspond to cleanslugowner/cleanslugproject values returned by search_models.
Discovery
| Tool | API Endpoint | What it does |
|---|---|---|
search_models | POST /Tool/List | Search and browse AI models by keyword, category, or owner. Returns model slugs, titles, descriptions, categories, and pricing. |
get_model_schema | POST /Tool/Detail | Get full parameter schema and pricing for any model — parameter names, types, options, defaults, and required fields. |
recommend_model | POST /Tool/List | Describe what you want to build and get model recommendations ranked by relevance. |
explore | POST /Tool/Explore | Browse curated AI models organized by category. No parameters needed. |
Execution
| Tool | API Endpoint | What it does |
|---|---|---|
run_model | POST /Run/{owner}/{model} | Run any model with parameters. With wait=true (default), polls until complete and returns outputs. With wait=false, returns task token for async monitoring. |
Task Management
| Tool | API Endpoint | What it does |
|---|---|---|
get_task | POST /Task/Detail | Check task status, pexit (exit code), outputs (CDN URLs), debugoutput (LLM responses), elapsed time, and cost. pexit="0" means success. |
get_task_price | POST /Task/Detail | Get the cost of a completed task. Shows whether it was billed and the total charge. Only successful tasks (pexit: "0") are billed. |
cancel_task | POST /Task/Cancel | Cancel a task still in queue (before worker assignment). |
kill_task | POST /Task/Kill | Kill a running task (after worker assignment). Task moves to task_cancel status. |
Utility
| Tool | API Endpoint | What it does |
|---|---|---|
upload_file | POST /File/Upload | Upload a file from a URL to Wiro. Most models accept direct URLs without uploading first. |
search_docs | Wiro Docs | Search the Wiro documentation for guides, API references, and examples. |
See the Wiro MCP Server page for detailed parameter tables and examples.
Environment Variables
| Variable | Required | Description |
|---|---|---|
WIRO_API_KEY | Yes | Your Wiro project API key |
WIRO_API_SECRET | No | API secret (for signature auth) |
WIRO_API_BASE_URL | No | Override API URL (default: https://api.wiro.ai/v1) |
GitHub & npm
- GitHub: github.com/wiroai/Wiro-MCP
- npm: @wiro-ai/wiro-mcp
Using as a Library
import { createMcpServer, WiroClient } from '@wiro-ai/wiro-mcp';
const client = new WiroClient('your-api-key', 'your-api-secret');
const server = createMcpServer(client);
Self-Hosting on Your Server
git clone https://github.com/wiroai/Wiro-MCP.git
cd Wiro-MCP
npm install
npm run build
export WIRO_API_KEY="your-api-key"
export WIRO_API_SECRET="your-api-secret"
node dist/index.js
Requires Node.js 18 or later. Create a project to get API keys.
Node.js Library
Use Wiro AI models directly in your Node.js or TypeScript projects with a simple API client.
Overview
The @wiro-ai/wiro-mcp package exports a WiroClient class that you can use as a standalone API client — no MCP setup required. It handles authentication (both signature-based and API key only), model discovery, execution, task polling, and file uploads.
Links
Installation
npm install @wiro-ai/wiro-mcp
Requires Node.js 18 or later.
Quick Start
import { WiroClient } from '@wiro-ai/wiro-mcp/client';
const client = new WiroClient('YOUR_API_KEY', 'YOUR_API_SECRET');
// Run an image generation model
const run = await client.runModel('google/nano-banana-pro', {
prompt: 'A futuristic city at sunset',
aspectRatio: '16:9',
resolution: '2K'
});
if (!run.result) {
console.log('Run failed:', run.errors);
process.exit(1);
}
// Wait for the result (polls Task/Detail until complete)
const result = await client.waitForTask(run.socketaccesstoken);
const task = result.tasklist[0];
if (task.pexit === '0') {
console.log('Output:', task.outputs[0].url);
} else {
console.log('Failed:', task.pexit);
}
Authentication
The client supports both Wiro authentication methods:
Signature-Based (Recommended)
Provide both API key and secret. HMAC-SHA256 signatures are generated automatically per request.
const client = new WiroClient('your-api-key', 'your-api-secret');
API Key Only
Omit the secret for simpler server-side usage.
const client = new WiroClient('your-api-key');
Custom Base URL
Override the API endpoint if needed (third parameter):
const client = new WiroClient('key', 'secret', 'https://custom-api.example.com/v1');
Available Methods
| Method | Description |
|---|---|
searchModels(params?) | Search and browse models by keyword, category, or owner. |
getModelSchema(model) | Get full parameter schema and pricing for a model. |
explore() | Browse curated models organized by category. |
runModel(model, params) | Run a model. Returns task ID and socket access token. |
waitForTask(tasktoken, timeoutMs?) | Poll until the task completes. Default timeout: 120 seconds. |
getTask({ tasktoken?, taskid? }) | Get current task status and outputs. |
cancelTask(tasktoken) | Cancel a task still in the queue. |
killTask(tasktoken) | Kill a running task. |
uploadFile(url, fileName?) | Upload a file from a URL for use as model input. |
Examples
Search Models
const models = await client.searchModels({
search: 'image generation',
categories: ['text-to-image'],
limit: 5
});
for (const model of models.tool) {
console.log(`${model.cleanslugowner}/${model.cleanslugproject} — ${model.title}`);
}
Get Model Parameters
const detail = await client.getModelSchema('google/nano-banana-pro');
const model = detail.tool[0];
if (model.parameters) {
console.log('Parameters:');
for (const group of model.parameters) {
for (const param of group.items) {
console.log(` ${param.id} (${param.type}) ${param.required ? '— required' : ''}`);
}
}
}
Run an LLM
const run = await client.runModel('openai/gpt-5-2', {
prompt: 'Explain quantum computing in 3 sentences'
});
const result = await client.waitForTask(run.socketaccesstoken);
const task = result.tasklist[0];
if (task.pexit === '0') {
// Merged text
console.log(task.debugoutput);
// Structured thinking/answer
const output = task.outputs[0];
if (output.contenttype === 'raw') {
console.log('Thinking:', output.content.thinking);
console.log('Answer:', output.content.answer);
}
}
Upload a File and Use It
Tip: Most models accept direct URLs in file parameters — you can pass inputImage: 'https://example.com/photo.jpg' directly without uploading. Use uploadFile() when you need to reuse files across multiple runs.
// Upload an image
const upload = await client.uploadFile('https://example.com/photo.jpg');
const fileUrl = upload.list[0].url;
// Use it in a model run
const run = await client.runModel('wiro/virtual-try-on', {
inputImageHuman: fileUrl,
inputImageClothes: 'https://example.com/shirt.jpg'
});
const result = await client.waitForTask(run.socketaccesstoken);
console.log('Output:', result.tasklist[0].outputs[0].url);
Poll Manually
const run = await client.runModel('klingai/kling-v3', {
prompt: 'A drone shot over mountains',
duration: '5',
aspectRatio: '16:9'
});
// Poll manually instead of using waitForTask
const interval = setInterval(async () => {
const detail = await client.getTask({ tasktoken: run.socketaccesstoken });
const task = detail.tasklist[0];
console.log('Status:', task.status);
if (task.status === 'task_postprocess_end') {
clearInterval(interval);
if (task.pexit === '0') {
console.log('Video:', task.outputs[0].url);
}
}
}, 5000);
TypeScript
All types are exported from the package:
import { WiroClient } from '@wiro-ai/wiro-mcp/client';
import type {
RunModelResult,
TaskDetailResponse,
Task,
TaskOutput,
ToolListResponse,
ToolDetailResponse,
ToolListItem,
SearchModelsParams,
} from '@wiro-ai/wiro-mcp/client';
| Type | Description |
|---|---|
RunModelResult | Response from runModel() — taskid, socketaccesstoken. |
TaskDetailResponse | Response from getTask() / waitForTask() — contains tasklist array. |
Task | Individual task object — status, pexit, outputs, debugoutput, etc. |
TaskOutput | Output entry — file (name, url) or LLM (contenttype: "raw", content). |
ToolListResponse | Response from searchModels() — contains tool array. |
ToolDetailResponse | Response from getModelSchema() — contains model with parameters. |
SearchModelsParams | Search parameters — search, categories, slugowner, limit, etc. |
n8n Wiro Integration
Use all Wiro AI models directly in your n8n workflows — video, image, audio, LLM, 3D, and more.
Overview
n8n is a powerful workflow automation platform. The Wiro AI community node gives you access to all Wiro AI models as individual nodes you can drag and drop into any workflow.
Each model is a separate node — so you get dedicated parameters, descriptions, and output handling for every model without any configuration hassle.
Links
- npm: @wiro-ai/n8n-nodes-wiroai
- GitHub: wiroai/n8n-nodes-wiroai
- n8n Community Nodes Installation Guide
- Wiro Model Catalog — browse all available models
Available Model Categories
| Category | Models | Examples |
|---|---|---|
| Video Generation | Text-to-video, image-to-video | Sora 2, Veo 3, Kling V3, Seedance, Hailuo, PixVerse, Runway |
| Image Generation | Text-to-image, style transfer | Imagen V4, Flux 2 Pro, Seedream, Nano Banana, SDXL |
| Image Editing | Try-on, face swap, background removal | Virtual Try-On, Face Swap, Inpainting, Style Transfer |
| Audio & Speech | TTS, STT, voice clone, music | ElevenLabs TTS, Gemini TTS, Whisper STT, Voice Clone |
| LLM Chat | Chat completion, RAG | GPT-5, Gemini 3, Qwen 3.5, RAG Chat |
| 3D Generation | Image/text to 3D | Trellis 2, Hunyuan3D 2.1 |
| Translation | Multi-language with image support | Gemma-based (4B, 12B, 27B) |
| E-Commerce | Product photos, ads, templates | Product Photoshoot, Shopify Templates, UGC Creator |
| HR Tools | CV analysis, job descriptions | CV Evaluator, Resume Parser, Culture Fit |
Installation
Install the community node package in your n8n instance:
Via n8n UI (Recommended)
- Open your n8n instance Navigate to your self-hosted or cloud n8n dashboard
- Go to Settings Open Settings → Community Nodes
- Install the node Click Install a community node and enter:
@wiro-ai/n8n-nodes-wiroai - Confirm Click Install and wait for completion
Via Command Line
npm install @wiro-ai/n8n-nodes-wiroai
Restart n8n after installation.
Authentication
The node supports both Wiro authentication methods:
- Add credentials Go to Credentials → Add new → Wiro API in n8n
- Select auth method Signature-Based — enter API key + secret (recommended) | API Key Only — enter API key only
- Save Click Save to store your credentials
Get your credentials at wiro.ai/panel/project.
Usage
Each Wiro model appears as a separate node in the n8n node picker. Search for "Wiro" or the model name to find it.
Example: Generate a Video with Sora 2
- Add the Wiro - Sora 2 Pro node to your workflow
- Connect your Wiro credentials
- Set the parameters:
- Prompt:
A cat astronaut floating in space - Seconds:
8 - Resolution:
1080p
- Prompt:
- Run the workflow
The node returns the task result with output URLs:
{
"taskid": "abc123",
"status": "completed",
"url": "https://cdn1.wiro.ai/xyz/0.mp4"
}
Example: Transcribe Audio with Whisper
- Add the Wiro - Whisper Large 3 node
- Connect an audio file from a previous node or provide a URL
- Select language and output format
- Run — get the transcribed text
Example: LLM Chat with GPT-5
- Add the Wiro - GPT-5 node
- Set your prompt and system instructions
- Run — get the AI response
Compatibility
| Requirement | Version |
|---|---|
| n8n | v1.0+ |
| Node.js | v18+ |
| Package | @wiro-ai/n8n-nodes-wiroai@latest |
Agent Overview
Deploy and manage autonomous AI agents through a single API.
What are Wiro Agents?
Wiro Agents are autonomous AI assistants that run persistently in isolated containers. Unlike one-shot model runs, agents maintain conversation memory, connect to external services, and use tools to complete tasks on your behalf — all managed through the API.
The system has two layers:
- Agent templates (the catalog) — Pre-built agent definitions published by Wiro. Each template defines the agent's capabilities, required credentials, tools, and pricing. Browse the catalog with
POST /Agent/List. - UserAgent instances (your deployments) — When you deploy an agent template, Wiro creates a personal instance tied to your account. Each instance runs in its own container with its own credentials, configuration, conversation history, and billing.
Every instance is fully isolated. Your credentials, conversations, and data are never shared with other users.
Base URL
https://api.wiro.ai/v1
Authentication
Agents use the same authentication as the rest of the Wiro API. Include your key in every request:
| Method | Header |
|---|---|
| API Key | x-api-key: YOUR_API_KEY |
| Bearer Token | Authorization: Bearer YOUR_API_KEY |
Public endpoints — Agent/List and Agent/Detail are catalog endpoints and do not require authentication. You can browse available agents without an API key.
Authenticated endpoints — All UserAgent/* endpoints (Deploy, MyAgents, Detail, Update, Start, Stop, CreateExtraCreditCheckout, CancelSubscription, UpgradePlan, RenewSubscription) require a valid API key.
For full details, see Authentication.
Agent Lifecycle
Deploying and running an agent follows this flow:
- Browse — call
POST /Agent/Listto discover available agents in the catalog - Deploy — call
POST /UserAgent/Deploywith the agent's guid, title,useprepaid: true, andplan("starter"or"pro"). The subscription cost is deducted from your wallet immediately. - Configure — if the agent requires credentials (API keys, OAuth tokens), call
POST /UserAgent/Updateto provide them. See Agent Credentials for details - Start — call
POST /UserAgent/Startto queue the agent for launch - Running — the agent's container starts and the agent becomes available for conversation
- Chat — send messages via
POST /UserAgent/Message/Send. See Agent Messaging for the full messaging API
UserAgent Statuses
Every deployed agent instance has a numeric status that reflects its current state:
| Status | Name | Description |
|---|---|---|
0 | Stopped | Agent is not running. Call Start to launch it. |
1 | Stopping | Agent is shutting down. Wait for it to reach Stopped before taking action. |
2 | Queued | Agent is queued and waiting for a worker to pick it up. |
3 | Starting | A worker has accepted the agent and is spinning up the container. |
4 | Running | Agent is live and ready to receive messages. |
5 | Error | Agent encountered an error during execution. Call Start to retry. |
6 | Setup Required | Agent needs credentials or configuration before it can start. Call Update to provide them. |
Automatic Restart (restartafter)
When you update an agent's configuration while it is Running (status 3 or 4), the system automatically triggers a restart cycle: the agent is moved to Stopping (status 1) with restartafter set to true. Once the container fully stops, the system automatically re-queues it, applying the new configuration on startup.
This means you can update credentials or settings on a running agent without manually stopping and starting it.
Endpoints
POST /Agent/List
Lists available agents in the catalog. This is a public endpoint — no authentication required.
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Full-text search across agent titles and descriptions |
category | string | No | Filter by category (e.g. "productivity", "social-media") |
sort | string | No | Sort column: id, title, slug, status, createdat, updatedat, totalrun, activerun. Default: id |
order | string | No | Sort direction: ASC or DESC. Default: DESC |
limit | number | No | Results per page (max 1000). Default: 20 |
start | number | No | Offset for pagination. Default: 0 |
Response
{
"result": true,
"errors": [],
"total": 12,
"agents": [
{
"id": 5,
"guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Instagram Manager",
"slug": "instagram-manager",
"headline": "Automate your Instagram presence with AI",
"description": "An autonomous agent that manages your Instagram account...",
"cover": "https://cdn.wiro.ai/uploads/agents/instagram-manager-cover.webp",
"categories": ["social-media", "marketing"],
"samples": ["https://cdn.wiro.ai/uploads/agents/instagram-manager-sample-1.webp"],
"pricing": {
"starter": { "price": 9, "credits": 1000 },
"pro": { "price": 29, "credits": 5000 }
},
"skills": ["post_image", "reply_comment", "schedule_post"],
"status": 1,
"createdat": "1711929600",
"updatedat": "1714521600"
}
]
}
POST /Agent/Detail
Retrieves full details for a single agent by guid or slug. This is a public endpoint — no authentication required.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | No* | Agent guid |
slug | string | No* | Agent slug (e.g. "instagram-manager") |
guid or slug. If both are provided, slug takes priority.Response
{
"result": true,
"errors": [],
"agents": [
{
"id": 5,
"guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Instagram Manager",
"slug": "instagram-manager",
"headline": "Automate your Instagram presence with AI",
"description": "An autonomous agent that manages your Instagram account...",
"cover": "https://cdn.wiro.ai/uploads/agents/instagram-manager-cover.webp",
"categories": ["social-media", "marketing"],
"samples": ["https://cdn.wiro.ai/uploads/agents/instagram-manager-sample-1.webp"],
"pricing": {
"starter": { "price": 9, "credits": 1000 },
"pro": { "price": 29, "credits": 5000 }
},
"skills": ["post_image", "reply_comment", "schedule_post"],
"ratelimit": { "actionTypes": { "message": 10, "create": 5 } },
"configuration": {
"credentials": {
"instagram": {
"_editable": { "authMethod": true },
"optional": false,
"authMethod": "",
"igUsername": "",
"connectedAt": ""
}
}
},
"status": 1,
"createdat": "1711929600",
"updatedat": "1714521600"
}
]
}
POST /UserAgent/Deploy
Creates a new agent instance from a catalog template. The agent is created with status 6 (Setup Required). After deploying, call UserAgent/Detail to see which credentials are needed, provide them via UserAgent/Update, then call UserAgent/Start to launch the agent. The subscription cost is deducted from your prepaid wallet immediately.
| Parameter | Type | Required | Description |
|---|---|---|---|
agentguid | string | Yes | The guid of the agent template from the catalog |
title | string | Yes | Display name for your instance |
description | string | No | Optional description |
configuration | object | No | Initial credential values. Format: { "credentials": { "key": "value" } } |
useprepaid | boolean | Yes | Set to true to pay from wallet balance. Cost is deducted immediately. |
plan | string | Yes | Plan tier: "starter" or "pro". Check Agent/Detail pricing for available plans and prices. |
Request
POST /UserAgent/Deploy
{
"agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "My Instagram Bot",
"useprepaid": true,
"plan": "starter"
}
Response
{
"result": true,
"errors": [],
"useragents": [
{
"id": 47,
"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321",
"agentid": 5,
"title": "My Instagram Bot",
"status": 6,
"setuprequired": true,
"subscription": {
"plan": "agent-starter",
"status": "active",
"amount": 49,
"currency": "usd",
"provider": "prepaid"
},
"createdat": "1714608000",
"updatedat": "1714608000"
}
]
}
POST /UserAgent/MyAgents
Lists all agent instances deployed under your account.
| Parameter | Type | Required | Description |
|---|---|---|---|
sort | string | No | Sort column: id, title, status, createdat, updatedat, startedat, runningat, stopdat. Default: id |
order | string | No | Sort direction: ASC or DESC. Default: DESC |
limit | number | No | Results per page (max 1000). Default: 20 |
start | number | No | Offset for pagination. Default: 0 |
category | string | No | Filter by category |
Response
{
"result": true,
"errors": [],
"useragents": [
{
"id": 47,
"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321",
"agentid": 5,
"title": "My Instagram Bot",
"status": 4,
"setuprequired": false,
"subscription": {
"plan": "agent-instagram-manager-pro",
"status": "active",
"amount": 29,
"currency": "usd",
"currentperiodend": 1717200000,
"renewaldate": "2026-06-01T00:00:00.000Z",
"daysremaining": 62,
"pendingdowngrade": null,
"provider": "prepaid"
},
"agent": {
"id": 5,
"title": "Instagram Manager",
"slug": "instagram-manager",
"cover": "https://cdn.wiro.ai/uploads/agents/instagram-manager-cover.webp",
"categories": ["social-media", "marketing"],
"pricing": {
"starter": { "price": 9, "credits": 1000 },
"pro": { "price": 29, "credits": 5000 }
}
},
"extracredits": 0,
"extracreditsexpiry": null,
"createdat": "1714608000",
"updatedat": "1714694400",
"startedat": "1714694400",
"runningat": "1714694410"
}
]
}
POST /UserAgent/Detail
Retrieves full details for a single deployed agent instance, including subscription info.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
Response
{
"result": true,
"errors": [],
"useragents": [
{
"id": 47,
"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321",
"agentid": 5,
"title": "My Instagram Bot",
"status": 4,
"setuprequired": false,
"configuration": {
"credentials": {
"instagram": {
"_editable": { "authMethod": true },
"optional": false,
"authMethod": "wiro",
"igUsername": "myaccount",
"connectedAt": "2025-04-01T12:00:00.000Z"
}
}
},
"subscription": {
"plan": "agent-instagram-manager-pro",
"status": "active",
"amount": 29,
"currency": "usd",
"currentperiodend": 1717200000,
"renewaldate": "2026-06-01T00:00:00.000Z",
"daysremaining": 62,
"pendingdowngrade": null,
"provider": "prepaid"
},
"agent": {
"id": 5,
"title": "Instagram Manager",
"slug": "instagram-manager",
"cover": "https://cdn.wiro.ai/uploads/agents/instagram-manager-cover.webp",
"pricing": {
"starter": { "price": 9, "credits": 1000 },
"pro": { "price": 29, "credits": 5000 }
}
},
"extracredits": 2000,
"extracreditsexpiry": 1730419200,
"createdat": "1714608000",
"updatedat": "1714694400",
"startedat": "1714694400",
"runningat": "1714694410"
}
]
}
| Field | Type | Description |
|---|---|---|
guid | string | Unique identifier for this agent instance. |
agentid | number | The catalog agent ID this instance was deployed from. |
title | string | Display name you gave this instance. |
status | number | Current status code (see UserAgent Statuses). |
setuprequired | boolean | true if credentials are missing or incomplete. |
configuration | object | Credential fields with values (passwords are masked). |
subscription | object|null | Active subscription info, or null if no subscription. |
agent | object | Parent agent template info (title, slug, cover, pricing). |
extracredits | number | Remaining extra credits purchased for this instance. |
extracreditsexpiry | number|null | Unix timestamp when the earliest extra credit pack expires. |
subscription.provider | string | Payment provider ("prepaid"). |
POST /UserAgent/Update
Updates an agent instance's configuration, title, description, or categories. If the agent is currently starting (status 3) or running (status 4), this triggers an automatic restart to apply the new settings.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
title | string | No | New display name |
description | string | No | New description |
configuration | object | No | Updated credentials. Format: { "credentials": { "key": "value" } } |
categories | array | No | Updated categories. Cannot be empty if provided. |
6 (Setup Required) and the update completes all required credentials, the status automatically changes to 0 (Stopped), allowing you to start it.Response
Returns the updated agent instance with setuprequired flag and agent summary. Does not include subscription — use UserAgent/Detail for the full view.
POST /UserAgent/Start
Starts a stopped agent instance. The agent is moved to Queued (status 2) and will be picked up by a worker.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
Response
{
"result": true,
"errors": []
}
Start will fail with a descriptive error if:
- The agent is already running or queued
- The agent is currently stopping
- Setup is incomplete (status
6) - No active subscription exists
- No credits remain (monthly or extra)
POST /UserAgent/Stop
Stops a running agent instance. If the agent is Queued (status 2), it is immediately set to Stopped. If it is Starting or Running (status 3/4), it moves to Stopping (status 1) and the container is shut down gracefully.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
Response
{
"result": true,
"errors": []
}
POST /UserAgent/CreateExtraCreditCheckout
Purchases additional credits for a Pro plan agent. Deducts from your prepaid wallet balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentGuid | string | Yes | Your UserAgent instance guid |
pack | string | Yes | Credit pack: package1, package2, or package3 |
useprepaid | boolean | Yes | Set to true to pay from wallet balance. Credits added immediately. |
Request
POST /UserAgent/CreateExtraCreditCheckout
{
"useragentGuid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321",
"pack": "package2",
"useprepaid": true
}
Response
{
"result": true,
"url": null,
"errors": []
}
Credits are added immediately from your wallet balance. url is null for prepaid purchases. Credits expire 6 months after purchase.
POST /UserAgent/CancelSubscription
Cancels a subscription at the end of the current billing period. The agent remains active until the period ends.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
Response
{
"result": true,
"cancelsAt": 1717200000,
"errors": []
}
The cancelsAt field is the Unix timestamp when the subscription will expire. The agent continues running until this date. You can reverse the cancellation by calling RenewSubscription before the period ends.
POST /UserAgent/UpgradePlan
Upgrades a Starter subscription to Pro. The prorated cost for the remaining days is deducted from your wallet.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
plan | string | Yes | Target plan: "pro" (only starter-to-pro upgrade is supported) |
Response
{
"result": true,
"plan": "agent-pro",
"proratedCharge": 11.33,
"newMonthlyCredits": 5000,
"errors": []
}
Downgrades are not supported. To change from Pro to Starter, cancel and re-deploy.
POST /UserAgent/RenewSubscription
Renews an expired subscription or reverses a pending cancellation. The renewal cost is deducted from your wallet.
| Parameter | Type | Required | Description |
|---|---|---|---|
guid | string | Yes | Your UserAgent instance guid |
Response (renewal)
{
"result": true,
"action": "renewed",
"plan": "agent-starter",
"amount": 49,
"errors": []
}
Response (undo cancel)
When called on an active subscription with a pending cancellation:
{
"result": true,
"action": "undo-cancel",
"errors": []
}
After renewal, the agent status is reset to 0 (Stopped). Call Start to launch it again. Monthly credits are refreshed for the new billing period.
Agent Pricing
Agent pricing is subscription-based, billed monthly. Two payment methods are available:
| Feature | Starter | Pro |
|---|---|---|
| Monthly price | Varies by agent (e.g. $9/mo) | Varies by agent (e.g. $29/mo) |
| Monthly credits | Included (e.g. 1,000) | Included (e.g. 5,000) |
| Extra credit packs | Not available | Available (expire in 6 months) |
| Plan upgrade | Upgrade to Pro anytime | — |
Each agent in the catalog defines its own pricing tiers in the pricing field. Check the Agent/Detail response for exact prices and credit amounts.
Payment Method
All subscriptions use your prepaid wallet balance. The cost is deducted immediately when you deploy or renew. Subscriptions renew automatically if your wallet has sufficient balance; otherwise the subscription expires. Manage subscriptions through the CancelSubscription, UpgradePlan, and RenewSubscription endpoints.
Credits are consumed per message or action, depending on the agent type. When monthly credits run out, the agent cannot be started until credits are renewed (next billing cycle) or extra credits are purchased.
Error Messages
Agent-specific errors you may encounter:
| Error | When |
|---|---|
Agent not found | The agentguid or slug does not match any catalog agent |
User agent not found | The guid does not match any of your deployed instances |
Agent not found or inactive | The catalog agent exists but is disabled |
Active subscription required to start agent. Please renew your subscription. | No active subscription for this instance |
Agent setup is not complete. Please fill in your credentials before starting. | Status is 6 — call Update to provide required credentials |
Agent is already running | Start called on an agent with status 3 or 4 |
Agent is already queued to start | Start called on an agent with status 2 |
Agent is already stopped | Stop called on an agent with status 0 |
Agent is currently stopping, please wait | Start called on an agent with status 1 |
Agent is in error state, use Start to retry | Stop called on an agent with status 5 |
No credits available. Please renew your subscription or purchase extra credits. | Monthly and extra credits are both exhausted |
Extra credits are available only for Pro plan subscribers. Please upgrade your plan. | CreateExtraCreditCheckout called on a Starter plan |
Invalid pack. Choose package1, package2, or package3. | CreateExtraCreditCheckout with invalid pack |
Active subscription required to purchase extra credits. | CreateExtraCreditCheckout without subscription |
Extra credit pack not available for this agent. | Agent pricing doesn't define the pack |
Categories cannot be empty | Update with empty categories |
Agent not found or access denied | Message endpoint with invalid useragentguid |
Agent is not running. Current status: {n} | Message/Send when not running |
Message not found | Detail/Cancel with invalid messageguid |
Message cannot be cancelled (status: {status}) | Cancel on completed message |
Invalid redirect URL | OAuth Connect with non-HTTPS URL |
Subscription is already active | RenewSubscription called when subscription is already active without pending cancel |
No expired subscription found to renew | RenewSubscription called with no expired subscription |
Insufficient wallet balance. Required: $X, Available: $Y | Prepaid operation with insufficient funds |
Cannot downgrade from Pro to Starter. Cancel your subscription instead. | UpgradePlan with downgrade attempt |
Subscription cancellation scheduled | CancelSubscription success |
Valid plan required when using prepaid (starter or pro) | Deploy with useprepaid but missing/invalid plan |
Pricing not available for this plan | Deploy with useprepaid for agent without pricing |
Renewal pricing not available | RenewSubscription for agent with zero pricing |
What's Next
- Agent Messaging — Send messages and receive responses from running agents
- Agent Credentials — Configure OAuth and API key credentials for your agent
- Authentication — API key setup and authentication methods
- Pricing — General pricing information
Agent Messaging
Send messages to AI agents and receive streaming responses in real time.
How It Works
Agent messaging follows the same async pattern as model runs:
- Send a message via REST → get an
agenttokenimmediately - Subscribe to WebSocket with the
agenttoken→ receive streaming response chunks - Or poll via the Detail endpoint to check status and fetch the completed response
- Or set a
callbackurlto receive a webhook notification when the agent finishes
This decoupled design means your application never blocks waiting for the agent to think. Send the message, hand the agenttoken to your frontend, and stream the response as it arrives.
Message Lifecycle
Every agent message progresses through a defined set of stages:
Message Statuses
| Status | Description |
|---|---|
agent_queue |
The message is queued and waiting to be picked up by the agent worker. Emitted once when the message enters the queue. |
agent_start |
The agent has accepted the message and begun processing. The underlying LLM call is being prepared. |
agent_output |
The agent is producing output. This event is emitted multiple times — each chunk of the response arrives as a separate agent_output event via WebSocket, enabling real-time streaming. |
agent_end |
The agent has finished generating the response. The full output is available in the response and debugoutput fields. This is the event you should listen for to get the final result. |
agent_error |
The agent encountered an error during processing. The debugoutput field contains the error message. |
agent_cancel |
The message was cancelled by the user before completion. Only messages in agent_queue, agent_start, or agent_output status can be cancelled. |
POST /UserAgent/Message/Send
Sends a user message to a deployed agent. The agent must be in running state (status 4). Returns immediately with an agenttoken that you use to track the response via WebSocket, polling, or webhook.
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid | string | Yes | The agent instance GUID (from Deploy or MyAgents). |
message | string | Yes | The user message text to send to the agent. |
sessionkey | string | No | Session identifier for conversation continuity. Defaults to "default". |
callbackurl | string | No | Webhook URL — the system will POST the final response to this URL when the agent finishes. |
Response
{
"result": true,
"errors": [],
"messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"status": "agent_queue"
}
| Field | Type | Description |
|---|---|---|
messageguid | string | Unique identifier for this message. Use it with Detail, History, or Cancel. |
agenttoken | string | Token for WebSocket subscription and polling. Equivalent to tasktoken in model runs. |
status | string | Initial status — always "agent_queue" on success. |
POST /UserAgent/Message/Detail
Retrieves the current status and content of a single message. You can query by either messageguid or agenttoken.
| Parameter | Type | Required | Description |
|---|---|---|---|
messageguid | string | No | The message GUID returned from Send. |
agenttoken | string | No | The agent token returned from Send (alternative to messageguid). |
messageguid or agenttoken.Response
{
"result": true,
"errors": [],
"data": {
"guid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"uuid": "user-uuid-here",
"sessionkey": "default",
"content": "What are the latest trends in AI?",
"response": "Here are the key AI trends for 2026...",
"debugoutput": "Here are the key AI trends for 2026...",
"status": "agent_end",
"metadata": "{\"thinking\":[],\"answer\":[\"Here are the key AI trends for 2026...\"],\"raw\":\"Here are the key AI trends for 2026...\"}",
"createdat": "1743350400",
"startedat": "1743350401",
"endedat": "1743350408"
}
}
| Field | Type | Description |
|---|---|---|
guid | string | Message GUID. |
uuid | string | The account UUID of the user who sent the message. |
sessionkey | string | The session this message belongs to. |
content | string | The original user message. |
response | string | The agent's full response text. Empty until agent_end. |
debugoutput | string | Accumulated output text. Updated during streaming, contains the full response after completion. |
status | string | Current message status (see Message Lifecycle). |
metadata | string | JSON string containing structured response data — thinking, answer, raw, speed metrics, and token/word counts. |
createdat | string | Unix timestamp when the message was created. |
startedat | string | Unix timestamp when the agent started processing. |
endedat | string | Unix timestamp when processing completed. |
POST /UserAgent/Message/History
Retrieves conversation history for a specific agent and session. Messages are returned newest-first with cursor-based pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid | string | Yes | The agent instance GUID. |
sessionkey | string | No | Session identifier. Defaults to "default". |
limit | number | No | Maximum number of messages to return. Defaults to 50, max 200. |
before | string | No | Message GUID to use as cursor — returns only messages created before this one. Omit for the most recent messages. |
Response
{
"result": true,
"errors": [],
"data": {
"messages": [
{
"guid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"content": "What are the latest trends in AI?",
"response": "Here are the key AI trends for 2026...",
"debugoutput": "Here are the key AI trends for 2026...",
"status": "agent_end",
"metadata": "{}",
"createdat": "1743350400"
}
],
"count": 1,
"hasmore": false
}
}
| Field | Type | Description |
|---|---|---|
messages | array | Array of message objects, newest first. |
count | number | Number of messages in this page. |
hasmore | boolean | true if there are older messages available. Pass the last message's guid as before to fetch the next page. |
Pagination
// Page 1: most recent messages
POST /UserAgent/Message/History { "useragentguid": "...", "limit": 50 }
// Page 2: pass the last message's guid as cursor
POST /UserAgent/Message/History { "useragentguid": "...", "limit": 50, "before": "a1b2c3d4-..." }
POST /UserAgent/Message/Sessions
Lists all conversation sessions for an agent. Returns each session's key, message count, last activity time, and the most recent message content.
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid | string | Yes | The agent instance GUID. |
Response
{
"result": true,
"errors": [],
"data": {
"sessions": [
{
"sessionkey": "default",
"messagecount": "24",
"updatedat": "1743350400",
"lastmessage": "What are the latest trends in AI?"
},
{
"sessionkey": "user-42-support",
"messagecount": "8",
"updatedat": "1743349200",
"lastmessage": "How do I reset my password?"
}
]
}
}
| Field | Type | Description |
|---|---|---|
sessionkey | string | The session identifier. |
messagecount | string | Total number of messages in this session. |
updatedat | string | Unix timestamp of the last activity in this session. |
lastmessage | string | The content (user message) of the most recent message. |
POST /UserAgent/Message/DeleteSession
Deletes a session and all its messages permanently. This action cannot be undone.
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid | string | Yes | The agent instance GUID. |
sessionkey | string | Yes | The session key to delete. |
Response
{
"result": true,
"errors": []
}
POST /UserAgent/Message/Cancel
Cancels an in-progress message. Only messages in agent_queue, agent_start, or agent_output status can be cancelled. Messages that have already reached agent_end, agent_error, or agent_cancel cannot be cancelled.
| Parameter | Type | Required | Description |
|---|---|---|---|
messageguid | string | No | The message GUID to cancel. |
agenttoken | string | No | The agent token to cancel (alternative to messageguid). |
messageguid or agenttoken.Response
{
"result": true,
"errors": []
}
On success, the message status changes to agent_cancel and all subscribed WebSocket clients receive an agent_cancel event.
Session Management
Sessions let you maintain separate conversation threads with the same agent:
- Each
sessionkeyrepresents a separate conversation — the agent remembers context within a session - The default session key is
"default"if you don't specify one - Use unique session keys per end-user for multi-tenant applications (e.g.
"user-42","customer-abc") - Sessions persist across API calls — send the same
sessionkeyto continue a conversation - Delete a session with
/UserAgent/Message/DeleteSessionto clear history and free resources
// User A's conversation
{
"useragentguid": "...",
"message": "Hello!",
"sessionkey": "user-alice"
}
// User B's separate conversation with the same agent
{
"useragentguid": "...",
"message": "Hello!",
"sessionkey": "user-bob"
}
Thinking & Answer Separation
Agent responses may include thinking blocks where the underlying model reasons through the problem before answering. The system automatically parses <think>...</think> tags and separates the output into structured arrays:
{
"thinking": ["Let me analyze the user's question...", "The key factors are..."],
"answer": ["Based on my analysis, here are the main points..."],
"isThinking": false,
"raw": "<think>Let me analyze the user's question...</think>Based on my analysis, here are the main points..."
}
thinking— array of reasoning/chain-of-thought blocks. May be empty if the model doesn't use thinking.answer— array of response chunks. This is the content to show the user.isThinking—truewhile the model is still in a thinking phase (the<think>tag is open but not yet closed),falseduring the answer phase.raw— the full accumulated raw output text including think tags.
Each agent_output WebSocket event contains the full accumulated arrays up to that point — not just the new chunk. Simply replace your displayed content with the latest arrays. Use isThinking to show a "thinking" indicator in your UI while the model reasons.
Tracking a Message
There are three ways to track message progress after sending:
1. WebSocket (Recommended)
Connect to WebSocket and subscribe with the agenttoken for real-time streaming. Each agent_output event delivers the growing response as it's generated.
// Subscribe to agent message updates
{ "type": "agent_info", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb" }
// Server confirms subscription with current status
{ "type": "agent_subscribed", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "status": "agent_queue", "result": true }
| Field | Type | Description |
|---|---|---|
message.type | string | Always "progressGenerate" for agent output events. |
message.speed | string | Generation speed (e.g. "12.4"). |
message.speedType | string | Unit for speed — "words/s" (words per second). |
message.elapsedTime | string | Elapsed time since generation started (e.g. "3.2s"). |
message.tokenCount | number | Number of tokens generated so far. |
message.wordCount | number | Number of words generated so far. |
message.raw | string | Full accumulated raw output text. |
message.thinking | string[] | Array of thinking/reasoning blocks. |
message.answer | string[] | Array of answer blocks — the content to display. |
message.isThinking | boolean | true while the model is in thinking phase. |
2. Polling via Detail
If you don't need real-time streaming, poll POST /UserAgent/Message/Detail at regular intervals until the status reaches a terminal state (agent_end, agent_error, or agent_cancel).
3. Webhook Callback
Pass a callbackurl when sending the message. The system will POST the final result to your URL when the agent finishes (up to 3 retry attempts):
// Webhook payload delivered to your callbackurl
{
"messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"status": "agent_end",
"content": "What are the latest trends in AI?",
"response": "Here are the key AI trends for 2026...",
"debugoutput": "Here are the key AI trends for 2026...",
"metadata": { "type": "progressGenerate", "raw": "...", "thinking": [], "answer": ["..."] },
"endedat": 1743350408
}
Agent WebSocket
Receive real-time agent response streaming via a persistent WebSocket connection.
Connection URL
wss://socket.wiro.ai/v1
Connect to this URL after calling the Message / Send endpoint. Use the agenttoken from the send response to subscribe to the agent session. This is the same WebSocket server used for model tasks — you can subscribe to both task events and agent events on the same connection.
Connection Flow
- Connect — open a WebSocket connection to
wss://socket.wiro.ai/v1 - Subscribe — send an
agent_infomessage with youragenttoken - Receive — listen for
agent_outputevents as the agent streams its response - Complete — the
agent_endevent fires when the response is finished - Close — disconnect after processing the final response
Subscribe message format:
{
"type": "agent_info",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb"
}
Event Types
| Event Type | Description |
|---|---|
agent_subscribed | Subscription confirmed. The server acknowledges your agenttoken and returns the current status. If the agent already started before you subscribed, accumulated text is included. |
agent_start | The agent has started processing your message. The underlying model is now generating a response. |
agent_output | A streaming response chunk. Emitted multiple times — each chunk contains the full accumulated text so far, plus real-time performance metrics. |
agent_end | Response complete. Contains the final accumulated text with total metrics. This is the event to listen for. |
agent_error | An error occurred during processing. The message field may be a plain string or a structured progress object depending on the error type. |
agent_cancel | The message was cancelled by the user before the agent finished responding. |
Message Format
Every WebSocket message is a JSON object with this base structure:
{
"type": "agent_output",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": { ... },
"result": true
}
The type field indicates the event. The message field varies by type — it's an empty string for lifecycle events, a structured progress object for output events, and a string or object for errors.
agent_subscribed
Sent immediately after the server accepts your subscription. The status field reflects where the agent currently is in its lifecycle.
{
"type": "agent_subscribed",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"status": "agent_queue",
"debugoutput": "",
"result": true
}
| Status | Meaning |
|---|---|
agent_queue | Message is queued, waiting for the agent to pick it up. |
agent_start | Agent has started processing. |
agent_output | Agent is actively streaming. debugoutput will contain accumulated text. |
agent_end | Agent already finished. debugoutput contains the complete response. |
agent_error | Agent encountered an error. debugoutput contains the error message. |
agent_cancel | Message was cancelled before completion. |
unknown | The agenttoken was not found in the database. |
agent_start
Signals that the agent has begun generating a response:
{
"type": "agent_start",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": "",
"result": true
}
agent_output (streaming)
Emitted multiple times as the agent generates its response. Each event contains the full accumulated text up to that point (not just the delta), along with real-time performance metrics:
{
"type": "agent_output",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": {
"type": "progressGenerate",
"task": "Generate",
"speed": "12.5",
"speedType": "words/s",
"elapsedTime": "2.4s",
"tokenCount": 35,
"wordCount": 28,
"raw": "Here is the accumulated response text so far...",
"thinking": [],
"answer": ["Here is the accumulated response text so far..."],
"isThinking": false
},
"result": true
}
The raw field contains the full response as a single string. The answer array contains the same text split into segments. To display streaming text, replace your UI content with raw (or join answer) on each event.
agent_end
Fires when the agent finishes responding. The structure is identical to agent_output — the message contains the final complete text with total metrics:
{
"type": "agent_end",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": {
"type": "progressGenerate",
"task": "Generate",
"speed": "14.2",
"speedType": "words/s",
"elapsedTime": "8.1s",
"tokenCount": 156,
"wordCount": 118,
"raw": "The complete agent response text...",
"thinking": [],
"answer": ["The complete agent response text..."],
"isThinking": false
},
"result": true
}
agent_error
An error occurred during processing. The message field can take two forms:
String error — a human-readable error description from exceptions:
{
"type": "agent_error",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": "Could not resolve agent endpoint",
"result": false
}
Common string errors include "Could not resolve agent endpoint", "OpenClaw returned HTTP 500", and "The operation was aborted.".
Structured error — when the model returns an invalid response (e.g. "..." or "Error: internal error"), the message contains the same progressGenerate object as agent_output:
{
"type": "agent_error",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": {
"type": "progressGenerate",
"task": "Generate",
"raw": "...",
"thinking": [],
"answer": ["..."],
"isThinking": false
},
"result": false
}
Always check the type of message — handle both string and object forms in your client.
agent_cancel
Sent when the user cancels a message before the agent completes its response:
{
"type": "agent_cancel",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": "The operation was aborted.",
"result": false
}
The result Field
Every event includes a result boolean:
| Value | Events |
|---|---|
true | agent_subscribed, agent_start, agent_output, agent_end |
false | agent_error, agent_cancel |
Use result to quickly determine whether the event represents a successful state. When result is false, inspect message for error details or cancellation context.
Streaming Metrics
Each agent_output and agent_end event includes real-time performance data in the message object:
| Field | Type | Description |
|---|---|---|
speed | string | Current generation speed (e.g. "12.5"). |
speedType | string | Speed unit — always "words/s" for agent responses. |
elapsedTime | string | Wall-clock time since the stream started (e.g. "2.4s"). |
tokenCount | number | Total tokens generated so far. |
wordCount | number | Total words in the accumulated response. |
These metrics update with every agent_output event, allowing you to display a live speed indicator or progress bar in your UI.
Thinking Model Support
When the agent is backed by a thinking-capable model (e.g. DeepSeek-R1, QwQ), the response may include thinking blocks alongside the answer:
{
"type": "agent_output",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb",
"message": {
"type": "progressGenerate",
"raw": "<think>Let me work through this step by step...</think>Based on my analysis...",
"thinking": ["Let me work through this step by step..."],
"answer": ["Based on my analysis..."],
"isThinking": true
},
"result": true
}
| Field | Description |
|---|---|
isThinking | true while the model is in a thinking phase, false when emitting the answer. |
thinking | Array of thinking block text segments. Empty if the model does not use thinking. |
answer | Array of answer text segments. This is the user-facing response. |
raw | The unprocessed output including <think> tags. Use thinking and answer instead for display. |
Rendering guidance:
- While
isThinkingistrue, show a "Thinking..." indicator or render thethinkingtext in a collapsible block. - When
isThinkingbecomesfalse, the model has finished reasoning and is now producing the answer. - On
agent_end, join theanswerarray for the final display text.
Full Integration Example
A typical integration follows this pattern: call the REST API to send a message, then subscribe via WebSocket to stream the response.
Step 1 — Send a message via REST:
curl -X POST https://api.wiro.ai/v1/UserAgent/Message/Send \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"useragentguid": "your-useragent-guid",
"message": "Explain quantum computing in simple terms",
"sessionkey": "user-42"
}'
Step 2 — Subscribe via WebSocket:
const ws = new WebSocket('wss://socket.wiro.ai/v1');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'agent_info',
agenttoken: 'aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb'
}));
};
Step 3 — Handle streaming events:
→ agent_subscribed { status: "agent_queue" }
→ agent_start { message: "" }
→ agent_output { message: { raw: "Quantum", wordCount: 1 } }
→ agent_output { message: { raw: "Quantum computing uses", wordCount: 3 } }
→ agent_output { message: { raw: "Quantum computing uses qubits...", wordCount: 28 } }
→ agent_end { message: { raw: "Quantum computing uses qubits that...", wordCount: 118 } }
Each agent_output contains the full accumulated text. Replace (don't append) your display content on each event.
Quick Reference
// Subscribe
{
"type": "agent_info",
"agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb"
}
// agent_subscribed
{
"type": "agent_subscribed",
"agenttoken": "aB3xK9...",
"status": "agent_queue",
"debugoutput": "",
"result": true
}
// agent_start
{
"type": "agent_start",
"agenttoken": "aB3xK9...",
"message": "",
"result": true
}
// agent_output (streaming — emitted multiple times)
{
"type": "agent_output",
"agenttoken": "aB3xK9...",
"message": {
"raw": "Accumulated text so far...",
"thinking": [],
"answer": ["Accumulated text so far..."],
"isThinking": false,
"speed": "12.5",
"speedType": "words/s",
"tokenCount": 35,
"wordCount": 28
},
"result": true
}
// agent_end (final response)
{
"type": "agent_end",
"agenttoken": "aB3xK9...",
"message": {
"raw": "Complete response text...",
"thinking": [],
"answer": ["Complete response text..."],
"isThinking": false,
"speed": "14.2",
"speedType": "words/s",
"tokenCount": 156,
"wordCount": 118
},
"result": true
}
// agent_error
{
"type": "agent_error",
"agenttoken": "aB3xK9...",
"message": "Bridge timeout",
"result": false
}
// agent_cancel
{
"type": "agent_cancel",
"agenttoken": "aB3xK9...",
"message": "The operation was aborted.",
"result": false
}
Agent Webhooks
Receive agent response notifications via HTTP callbacks.
How It Works
When you send a message to an agent via POST /UserAgent/Message/Send, include a callbackurl parameter. Once the agent finishes processing — whether it completes successfully, encounters an error, or the message is cancelled — Wiro sends a POST request to your URL with the result.
This lets you build fully asynchronous workflows: fire a message and let your backend handle the response whenever it arrives, without polling or maintaining a WebSocket connection.
Setting a Callback URL
Include callbackurl in your message request body:
POST /UserAgent/Message/Send
{
"useragentguid": "your-useragent-guid",
"message": "What are today's trending topics?",
"sessionkey": "user-123",
"callbackurl": "https://your-server.com/webhooks/agent-response"
}
The callback URL is stored per-message. You can use different URLs for different messages, or omit it entirely if you prefer polling or WebSocket.
Callback Payload
When the agent finishes, Wiro sends a POST request to your callbackurl with Content-Type: application/json. The payload contains the complete message result including structured metadata:
Successful Completion (agent_end)
{
"messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"status": "agent_end",
"content": "What are today's trending topics?",
"response": "Here are today's trending topics in tech...",
"debugoutput": "Here are today's trending topics in tech...",
"metadata": {
"type": "progressGenerate",
"task": "Generate",
"speed": "14.2",
"speedType": "words/s",
"elapsedTime": "8.1s",
"tokenCount": 156,
"wordCount": 118,
"raw": "Here are today's trending topics in tech...",
"thinking": [],
"answer": ["Here are today's trending topics in tech..."],
"isThinking": false
},
"endedat": 1712050004
}
Error (agent_error)
{
"messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"status": "agent_error",
"content": "What are today's trending topics?",
"response": "Could not resolve agent endpoint",
"debugoutput": "Could not resolve agent endpoint",
"metadata": {},
"endedat": 1712050004
}
Cancelled (agent_cancel)
{
"messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"status": "agent_cancel",
"content": "What are today's trending topics?",
"response": "The operation was aborted.",
"debugoutput": "The operation was aborted.",
"metadata": {},
"endedat": 1712050004
}
Field Reference
| Field | Type | Description |
|---|---|---|
messageguid | string | Unique identifier of the message. Use this to correlate with your records. |
status | string | Final status: agent_end, agent_error, or agent_cancel. |
content | string | The original user message you sent. |
response | string | The agent's full response text on success. For errors, contains the error message. For cancellation, contains the abort reason. |
debugoutput | string | Same as response — the full accumulated output text. Included for consistency with the polling API. |
metadata | object | Structured response data. Contains thinking/answer separation, performance metrics, and raw text. Empty object ({}) for error and cancel statuses. |
endedat | number | Unix timestamp (UTC seconds) when processing finished. |
The metadata Object
On successful completion (agent_end), the metadata object contains the structured response with thinking/answer separation and real-time metrics:
| Field | Type | Description |
|---|---|---|
type | string | Always "progressGenerate". |
task | string | Always "Generate". |
raw | string | The complete response text including any <think> tags. |
thinking | array | Array of reasoning/chain-of-thought blocks extracted from <think>...</think> tags. Empty if the model doesn't use thinking. |
answer | array | Array of response segments — the content to show the user. |
isThinking | boolean | Always false in webhooks (streaming is complete). |
speed | string | Final generation speed (e.g. "14.2"). |
speedType | string | Speed unit — "words/s". |
elapsedTime | string | Total generation time (e.g. "8.1s"). |
tokenCount | number | Total tokens generated. |
wordCount | number | Total words in the response. |
For agent_error and agent_cancel, metadata is an empty object {}. Always check status before accessing metadata fields.
Status Values
| Status | Description | response contains | metadata contains |
|---|---|---|---|
agent_end | Agent completed successfully | Full response text | Structured data with thinking, answer, metrics |
agent_error | An error occurred during processing | Error message string | Empty object {} |
agent_cancel | Message was cancelled before completion | Cancellation reason | Empty object {} |
Retry Policy
Wiro attempts to deliver each webhook up to 3 times:
- First attempt is immediate when the agent finishes
- 2-second delay between retries
- Your endpoint must return HTTP 200 to acknowledge receipt
- Any non-200 response triggers a retry
- After 3 failed attempts, the webhook is abandoned and the failure is logged server-side
The message result is always persisted in the database regardless of webhook delivery. You can retrieve it at any time via POST /UserAgent/Message/Detail.
Security Considerations
- Webhook calls do not include authentication headers — verify incoming requests by checking the
messageguidagainst your own records - Always use HTTPS endpoints in production
- Validate the payload structure before processing
- Consider returning 200 immediately and processing the payload asynchronously to avoid timeouts
Code Examples
Below are complete webhook receiver implementations and a sample request with a callback URL. Select your language from the code panel.
Agent Credentials & OAuth
Configure third-party service connections for your agent instances.
Overview
After deploying an agent, call POST /UserAgent/Detail to see its full configuration. The response includes:
configuration.credentials— which services the agent connects to and which fields are editable (_editable: true)configuration.custom_skills— configurable automated tasks the agent can run on a schedulesetuprequired— whether credentials still need to be filled before the agent can start
Use this response to build your credential form or automate setup. Then update credentials and skills via POST /UserAgent/Update.
Two Types of Credentials
- API Key credentials — set directly via
POST /UserAgent/Update(Telegram, WordPress, Gmail, Brevo, etc.) - OAuth credentials — redirect-based authorization flow via
POST /UserAgentOAuth/{Provider}Connect(Twitter, Instagram, Facebook, Google Ads, etc.)
Setting Credentials & Skills
Use POST /UserAgent/Update with configuration to set credentials and custom skills. You can update credentials, custom_skills, or both in a single request.
Request Format
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"<service>": {
"<field>": "value"
}
},
"custom_skills": [
{ "key": "skill-name", "enabled": true, "interval": "0 9 * * *" }
]
}
}
POST /UserAgent/Detail first to see which fields are available and editable (_editable: true). Only editable fields can be set — non-editable fields are silently ignored.Credential Configuration by Agent
Each agent requires different credentials. Select your agent below to see exactly which credentials to configure and the complete POST /UserAgent/Update request.
Manages social media accounts — posts, replies, scheduling across Twitter/X, Instagram, Facebook, LinkedIn, TikTok. OAuth providers are connected separately via the OAuth flow.
| Service | Type | Fields |
|---|---|---|
twitter | OAuth | Connected via XConnect. Set authMethod to "wiro" or "own". |
instagram | OAuth | Connected via IGConnect. |
facebook | OAuth | Connected via FBConnect. |
linkedin | OAuth | Connected via LIConnect. Also set organizationId. |
tiktok | OAuth | Connected via TikTokConnect. |
gmail | API Key | account, appPassword |
telegram | API Key | botToken, allowedUsers, sessionMode |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"gmail": {
"account": "[email protected]",
"appPassword": "xxxx xxxx xxxx xxxx"
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
gmail and telegram credentials.Publishes blog posts to WordPress, monitors a Gmail inbox for content requests.
| Service | Fields | Description |
|---|---|---|
wordpress | url, user, appPassword | WordPress site URL, username, and application password |
gmail | account, appPassword | Gmail address + Google App Password for inbox monitoring |
telegram | botToken, allowedUsers, sessionMode | Telegram bot for operator notifications |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"wordpress": {
"url": "https://blog.example.com",
"user": "WiroBlogAgent",
"appPassword": "xxxx xxxx xxxx xxxx"
},
"gmail": {
"account": "[email protected]",
"appPassword": "xxxx xxxx xxxx xxxx"
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
Monitors and replies to App Store and Google Play reviews.
| Service | Fields | Description |
|---|---|---|
appstore | keyId, issuerId, privateKeyBase64, appIds, supportEmail | App Store Connect API credentials |
googleplay | serviceAccountJsonBase64, packageNames, supportEmail | Google Play service account |
telegram | botToken, allowedUsers, sessionMode | Telegram bot for operator notifications |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"appstore": {
"keyId": "ABC1234DEF",
"issuerId": "12345678-1234-1234-1234-123456789012",
"privateKeyBase64": "LS0tLS1CRUdJTi...",
"appIds": ["6479306352"],
"supportEmail": "[email protected]"
},
"googleplay": {
"serviceAccountJsonBase64": "eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii...",
"packageNames": ["com.example.app"],
"supportEmail": "[email protected]"
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
Suggests and creates App Store in-app events based on holidays and trends.
| Service | Fields | Description |
|---|---|---|
appstore | keyId, issuerId, privateKeyBase64, appIds | App Store Connect API credentials |
telegram | botToken, allowedUsers, sessionMode | Telegram bot for operator notifications |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"appstore": {
"keyId": "ABC1234DEF",
"issuerId": "12345678-1234-1234-1234-123456789012",
"privateKeyBase64": "LS0tLS1CRUdJTi...",
"appIds": ["6479306352"]
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
Sends targeted push notifications via Firebase Cloud Messaging.
| Service | Fields | Description |
|---|---|---|
firebase | accounts[] | Array of Firebase projects. Each: appName, serviceAccountJsonBase64, apps (platform + id), topics (array of {topicKey, topicDesc}) |
telegram | botToken, allowedUsers, sessionMode | Telegram bot for operator notifications |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"firebase": {
"accounts": [{
"appName": "My App",
"serviceAccountJsonBase64": "eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii...",
"apps": [
{ "platform": "ios", "id": "6479306352" },
{ "platform": "android", "id": "com.example.app" }
],
"topics": [
{ "topicKey": "locale_en", "topicDesc": "English-speaking users" },
{ "topicKey": "tier_paid", "topicDesc": "Paid subscribers" }
]
}]
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
Creates and sends newsletters via Brevo, SendGrid, HubSpot, or Mailchimp.
| Service | Type | Fields |
|---|---|---|
brevo | API Key | apiKey |
sendgrid | API Key | apiKey |
hubspot | OAuth | Connected via HubSpotConnect |
mailchimp | OAuth/Key | OAuth via MailchimpConnect or set apiKey directly |
newsletter | Config | testEmail |
telegram | API Key | botToken, allowedUsers, sessionMode |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"brevo": { "apiKey": "xkeysib-abc123..." },
"sendgrid": { "apiKey": "SG.xxxx..." },
"newsletter": { "testEmail": "[email protected]" },
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
apiKey without OAuth.Finds leads and manages outreach campaigns via Apollo.io, Lemlist, and HubSpot.
| Service | Type | Fields |
|---|---|---|
apollo | API Key | apiKey, masterApiKey (optional, for sequences) |
lemlist | API Key | apiKey |
hubspot | OAuth | Connected via HubSpotConnect |
telegram | API Key | botToken, allowedUsers, sessionMode |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"apollo": {
"apiKey": "your-apollo-api-key",
"masterApiKey": "your-master-key"
},
"lemlist": { "apiKey": "your-lemlist-key" },
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
Manages Google Ads campaigns, keywords, and ad copy.
| Service | Type | Fields |
|---|---|---|
googleads | OAuth + Config | OAuth via GAdsConnect, then set customerId via GAdsSetCustomerId. Also set developerToken and managerCustomerId for "own" mode. |
website | Config | urls — array of { websiteName, url } |
appstore | Config | apps — array of { appName, appId } |
googleplay | Config | apps — array of { appName, packageName } |
telegram | API Key | botToken, allowedUsers, sessionMode |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"website": {
"urls": [{ "websiteName": "Main Site", "url": "https://example.com" }]
},
"appstore": {
"apps": [{ "appName": "My iOS App", "appId": "6479306352" }]
},
"googleplay": {
"apps": [{ "appName": "My Android App", "packageName": "com.example.app" }]
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
POST /UserAgentOAuth/GAdsSetCustomerId.Manages Meta (Facebook/Instagram) ad campaigns and creatives.
| Service | Type | Fields |
|---|---|---|
metaads | OAuth + Config | OAuth via MetaAdsConnect, then set ad account via MetaAdsSetAdAccount. Also set pageId for Facebook page association. |
website | Config | urls — array of { websiteName, url } |
appstore | Config | apps — array of { appName, appId } |
googleplay | Config | apps — array of { appName, packageName } |
telegram | API Key | botToken, allowedUsers, sessionMode |
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"website": {
"urls": [{ "websiteName": "Landing Page", "url": "https://example.com" }]
},
"appstore": {
"apps": [{ "appName": "My iOS App", "appId": "6479306352" }]
},
"googleplay": {
"apps": [{ "appName": "My Android App", "packageName": "com.example.app" }]
},
"telegram": {
"botToken": "123456:ABC-DEF1234ghIkl",
"allowedUsers": ["761381461"],
"sessionMode": [
{ "value": "private", "text": "Private — each user has their own conversation", "selected": true },
{ "value": "collaborative", "text": "Collaborative — all users share the same conversation", "selected": false }
]
}
}
}
}
POST /UserAgentOAuth/MetaAdsSetAdAccount.Credential Field Reference
Quick reference for all credential field names across services:
| Service Key | Editable Fields |
|---|---|
telegram | botToken, allowedUsers, sessionMode |
wordpress | url, user, appPassword |
gmail | account, appPassword |
brevo | apiKey |
sendgrid | apiKey |
apollo | apiKey, masterApiKey |
lemlist | apiKey |
newsletter | testEmail |
appstore | keyId, issuerId, privateKeyBase64, appIds — or apps array for ads agents |
googleplay | serviceAccountJsonBase64, packageNames — or apps array for ads agents |
firebase | accounts[]: appName, serviceAccountJsonBase64, apps [{platform,id}], topics [{topicKey,topicDesc}] |
website | urls array of { websiteName, url } |
twitter | OAuth — authMethod (own: + clientId, clientSecret) |
instagram | OAuth — authMethod (own: + appId, appSecret) |
facebook | OAuth — authMethod (own: + appId, appSecret) |
linkedin | OAuth — authMethod, organizationId (own: + clientId, clientSecret) |
tiktok | OAuth — authMethod (own: + clientKey, clientSecret) |
googleads | OAuth — authMethod, customerId, developerToken, managerCustomerId (own: + clientId, clientSecret) |
metaads | OAuth — authMethod, adAccountId, pageId (own: + appId, appSecret) |
hubspot | OAuth — authMethod (own: + clientId, clientSecret) |
mailchimp | OAuth — authMethod, apiKey (own: + clientId, clientSecret) |
Setup Required State
If an agent has required (non-optional) credentials that haven't been filled in, the agent is in Setup Required state (status 6) and cannot be started. After setting all required credentials via Update, the status automatically changes to 0 (Stopped) and you can call Start.
Check the setuprequired boolean in UserAgent/Detail or UserAgent/MyAgents responses to determine if credentials still need to be configured.
OAuth Authorization Flow
For services that require user authorization (social media accounts, ad platforms, CRMs), Wiro implements a full OAuth flow. The entire process is fully white-label — your end-users interact only with your app and the provider's consent screen. They never see or visit wiro.ai at any point.
redirectUrl you pass to the Connect endpoint is your own URL. After authorization, users are redirected back to your app — not to Wiro. Any HTTPS URL is accepted. Use http://localhost or http://127.0.0.1 for development.Supported OAuth Providers
| Provider | Connect Endpoint | Redirect Success Params | Redirect Error Params |
|---|---|---|---|
| Twitter/X | XConnect | x_connected=true&x_username=... | x_error=... |
| TikTok | TikTokConnect | tiktok_connected=true&tiktok_username=... | tiktok_error=... |
IGConnect | ig_connected=true&ig_username=... | ig_error=... | |
FBConnect | fb_connected=true&fb_pagename=... | fb_error=... | |
LIConnect | li_connected=true&li_name=... | li_error=... | |
| Google Ads | GAdsConnect | gads_connected=true&gads_accounts=[...] | gads_error=... |
| Meta Ads | MetaAdsConnect | metaads_connected=true&metaads_accounts=[...] | metaads_error=... |
| HubSpot | HubSpotConnect | hubspot_connected=true&hubspot_portal=...&hubspot_name=... | hubspot_error=... |
| Mailchimp | MailchimpConnect | mailchimp_connected=true&mailchimp_account=... | mailchimp_error=... |
Flow Diagram
Your App (Frontend) Your Backend Wiro API Provider (e.g. Twitter)
| | | |
(1) | "Connect Twitter" click | | |
|--------------------------->| | |
| | POST /XConnect | |
(2) | |--> { userAgentGuid, | |
| | redirectUrl, | |
| | authMethod } | |
| | | |
(3) | |<-- { authorizeUrl } | |
| | | |
(4) |<--- redirect to authorizeUrl | |
|--------------------------------------------------------> User sees Twitter |
| | | consent screen |
(5) | | |<-- User clicks Allow |
| | | |
(6) | | (invisible callback)| |
| | Wiro exchanges code |<-----------------------|
| | for tokens, saves | |
| | them to agent config| |
| | | |
(7) |<------- 302 redirect to YOUR redirectUrl ----------------------------------|
| https://your-app.com/settings?x_connected=true&x_username=johndoe |
| | | |
What the User Sees
| Step | User Sees | URL |
|---|---|---|
| 1 | Your app — "Connect Twitter" button | https://your-app.com/settings |
| 2–3 | (Backend API call — invisible to user) | — |
| 4–5 | Provider's consent screen (Twitter, TikTok, etc.) | https://x.com/i/oauth2/authorize?... |
| 6 | (Wiro's server-side callback — invisible 302 redirect) | — |
| 7 | Your app — "Connected!" confirmation | https://your-app.com/settings?x_connected=true |
Your users never visit wiro.ai. The only pages they see are your app and the provider's authorization screen.
Connect Endpoint
POST /UserAgentOAuth/{Provider}Connect
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
redirectUrl | string | Yes | Where to redirect after OAuth completes (HTTPS or localhost) |
authMethod | string | No | "wiro" (default) or "own" |
Response
{
"result": true,
"authorizeUrl": "https://x.com/i/oauth2/authorize?response_type=code&client_id=...",
"errors": []
}
Auth Methods — "wiro" vs "own"
Both modes produce the same white-label user experience. The only difference is whose OAuth app credentials are used for the authorization flow:
"wiro" (default) | "own" | |
|---|---|---|
| OAuth app credentials | Wiro's pre-configured app | Your own app from the provider's developer portal |
| Setup required | None — just call Connect | Create an app on the provider, set credentials via Update, register Wiro's callback URL |
| Consent screen branding | Shows "Wiro" as the app name | Shows your app name and branding |
| Redirect after auth | To your redirectUrl | To your redirectUrl |
| User sees wiro.ai? | No | No |
| Token management | Automatic by Wiro | Automatic by Wiro |
| Best for | Quick setup, prototyping, most use cases | Custom branding on consent screen, custom scopes |
"wiro" mode. It works out of the box with no configuration. Switch to "own" only if you need your brand name on the provider's consent screen or require custom OAuth scopes/permissions.To use "own" mode, first set your app credentials via POST /UserAgent/Update, then call Connect with authMethod: "own". Each provider requires different credential field names:
"own" Mode Credentials per Provider
| Provider | Credential Key | Required Fields | Request Example |
|---|---|---|---|
| Twitter/X | twitter |
clientId, clientSecret |
|
| TikTok | tiktok |
clientKey, clientSecret |
|
instagram |
appId, appSecret |
|
|
facebook |
appId, appSecret |
|
|
linkedin |
clientId, clientSecret, organizationId |
|
|
| Google Ads | googleads |
clientId, clientSecret, developerToken, managerCustomerId |
|
| Meta Ads | metaads |
appId, appSecret |
|
| HubSpot | hubspot |
clientId, clientSecret |
|
| Mailchimp | mailchimp |
clientId, clientSecret (or apiKey without OAuth) |
|
clientKey not clientId, Instagram/Facebook use appId/appSecret not clientId/clientSecret). Always use the exact field names from the table above."own" Mode Full Flow
// Step 1: Set your app credentials
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"credentials": {
"twitter": {
"clientId": "your-twitter-client-id",
"clientSecret": "your-twitter-client-secret"
}
}
}
}
// Step 2: Initiate OAuth with authMethod: "own"
POST /UserAgentOAuth/XConnect
{
"userAgentGuid": "your-useragent-guid",
"redirectUrl": "https://your-app.com/callback",
"authMethod": "own"
}
// Step 3: Redirect user to the returned authorizeUrl
// Step 4: User authorizes → redirected back to your redirectUrl
When using "own" mode, you must register Wiro's callback URL in your OAuth app settings on the provider's developer portal:
https://api.wiro.ai/v1/UserAgentOAuth/{Provider}Callback
Status Check
Check whether a provider is connected for a given agent instance.
POST /UserAgentOAuth/{Provider}Status
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
Response
{
"result": true,
"connected": true,
"username": "johndoe",
"connectedAt": "2025-04-01T12:00:00.000Z",
"tokenExpiresAt": "2025-04-01T14:00:00.000Z",
"refreshTokenExpiresAt": "2025-10-01T12:00:00.000Z",
"errors": []
}
| Field | Description | Providers |
|---|---|---|
connected | Whether the provider is connected | All |
username | Connected account name or identifier | Most providers |
linkedinName | LinkedIn profile name (replaces username) | LinkedIn only |
customerId | Google Ads customer ID (replaces username) | Google Ads only |
connectedAt | ISO timestamp of when the account was connected | All |
tokenExpiresAt | ISO timestamp of access token expiry | All except Mailchimp |
refreshTokenExpiresAt | ISO timestamp of refresh token expiry | Twitter/X, TikTok, LinkedIn |
Disconnect
Revoke access and remove stored tokens for a provider.
POST /UserAgentOAuth/{Provider}Disconnect
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
Response
{
"result": true,
"errors": []
}
Wiro attempts to revoke the token on the provider's side before clearing it from the configuration. The agent restarts automatically if it was running.
Token Refresh
Manually trigger a token refresh for a connected provider.
POST /UserAgentOAuth/TokenRefresh
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
provider | string | Yes | One of: twitter, tiktok, instagram, facebook, linkedin, googleads, metaads, hubspot |
Response
{
"result": true,
"accessToken": "new-access-token...",
"refreshToken": "new-refresh-token...",
"errors": []
}
The agent restarts automatically after a token refresh if it was running.
Extra Provider Endpoints
Google Ads — Set Customer ID
After connecting Google Ads via OAuth, you must set the Google Ads customer ID to target:
POST /UserAgentOAuth/GAdsSetCustomerId
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
customerId | string | Yes | Google Ads customer ID (e.g. "123-456-7890"). Non-digit characters are stripped automatically. |
Response
{
"result": true,
"customerId": "1234567890",
"errors": []
}
Meta Ads — Set Ad Account
After connecting Meta Ads via OAuth, set the ad account to manage:
POST /UserAgentOAuth/MetaAdsSetAdAccount
| Parameter | Type | Required | Description |
|---|---|---|---|
userAgentGuid | string | Yes | The agent instance GUID |
adAccountId | string | Yes | Meta Ads account ID (e.g. "act_123456789"). The act_ prefix is stripped automatically. |
adAccountName | string | No | Display name for the ad account |
Response
{
"result": true,
"errors": []
}
Custom Skills
Agents support configurable skills — preferences and scheduled tasks. See the dedicated Agent Skills page for the complete guide with examples.
Security
- Tokens are stored server-side in the agent instance configuration. The
TokenRefreshendpoint returns new tokens — all other endpoints (Status, Detail, Update) sanitize token fields before responding. - The
redirectUrlreceives only connection status parameters — no tokens, no secrets - API responses from Status, Detail, and Update endpoints are sanitized:
accessToken,refreshToken,clientSecret, andappSecretfields are stripped before returning - OAuth state parameters use a 15-minute TTL cache to prevent replay attacks
- Redirect URLs must be HTTPS (or localhost for development)
For Third-Party Developers
If you're building a product on top of Wiro agents and need your customers to connect their own accounts (e.g., their Twitter, their Google Ads), here's the recommended flow:
Architecture
- Deploy an agent instance per customer via
POST /UserAgent/Deploy - Connect — your backend calls
POST /UserAgentOAuth/{Provider}Connectwith the customer'suserAgentGuidand aredirectUrlpointing back to your app - Redirect — send your customer's browser to the returned
authorizeUrl - Authorize — customer logs in and authorizes on the provider
- Return — customer lands back on your
redirectUrlwith success/error query parameters - Verify — call
POST /UserAgentOAuth/{Provider}Statusto confirm connection
Your customers never interact with Wiro directly. The entire flow happens through your app, and Wiro handles token management behind the scenes.
Handling the Redirect in Your App
// Express route handling the OAuth redirect
app.get('/settings/social', (req, res) => {
const provider = req.query.provider;
if (req.query.x_connected === 'true') {
const username = req.query.x_username;
return res.redirect(`/dashboard?connected=${provider}&username=${username}`);
}
if (req.query.x_error) {
const error = req.query.x_error;
return res.redirect(`/dashboard?error=${provider}&reason=${error}`);
}
});
Error Values
OAuth redirect error parameters follow the pattern {provider_prefix}_error. Possible values:
| Error | Description |
|---|---|
authorization_denied | User declined the authorization |
token_exchange_failed | Provider accepted the code but token exchange failed |
useragent_not_found | The agent instance GUID is invalid or unauthorized |
invalid_config | Agent configuration doesn't have credentials for this provider |
internal_error | Unexpected server error during callback processing |
Agent Skills
Configure agent behavior with editable preferences and scheduled automation tasks.
Overview
Every agent has a set of custom skills that define its behavior. Skills come in two types:
| Type | Has Interval | Purpose | What You Can Change |
|---|---|---|---|
| Preferences | No (null) | Instructions that shape agent behavior — tone, style, targeting rules, content strategy | value, description, enabled |
| Scheduled Tasks | Yes (cron) | Automated actions that run on a schedule — scanning, reporting, dispatching | enabled, interval |
Call POST /UserAgent/Detail to discover an agent's skills. They appear in configuration.custom_skills.
Discovering Skills
POST /UserAgent/Detail
{
"guid": "your-useragent-guid"
}
// Response → configuration.custom_skills:
[
{
"key": "content-tone",
"value": "## Voice\nShort punchy lines, developer-friendly...",
"description": "Brand voice, hashtags, and posting style",
"enabled": true,
"interval": null,
"_editable": true
},
{
"key": "content-scanner",
"value": "",
"description": "What content to find and post about",
"enabled": true,
"interval": "0 * * * *",
"_editable": false
}
]
| Field | Type | Description |
|---|---|---|
key | string | Unique skill identifier. Use this in Update requests. |
value | string | Skill instructions/content. Visible only when _editable: true — otherwise empty string. |
description | string | Human-readable description of what the skill does. |
enabled | boolean | Whether the skill is active. |
interval | string | null | Cron expression for scheduled execution, or null for preference-only skills. |
_editable | boolean | If true, you can modify value and description. If false, only enabled and interval can be changed. |
Updating Preferences
Preference skills (_editable: true, interval: null) let you customize the agent's behavior by editing its instructions. Every agent has one — it controls tone, style, targeting, and strategy. Select your agent below to see its preference skill and a complete update request.
Skill key: content-tone — Controls brand voice, hashtags, and posting style per platform.
POST /UserAgent/Update
{
"guid": "your-social-manager-guid",
"configuration": {
"custom_skills": [
{
"key": "content-tone",
"value": "## Voice\nProfessional and informative. No slang.\n\n## Hashtags\nMax 3 per post. Always include #AI and #WiroAI.\n\n## Posting Style\nEvery post must include a link.\nUse bullet points for features.\n\n## Platform Rules\n- Twitter: Thread format for long content\n- Instagram: Carousel with square images only\n- LinkedIn: Professional tone, no emojis\n- TikTok: Short captions, trending sounds"
}
]
}
}
Preview: what the agent sees
Voice
Professional and informative. No slang.Hashtags
Max 3 per post. Always include #AI and #WiroAI.Posting Style
Every post must include a link. Use bullet points for features.Platform Rules
- Twitter: Thread format for long content - Instagram: Carousel with square images only - LinkedIn: Professional tone, no emojis - TikTok: Short captions, trending soundsThe value is markdown-formatted text. Use ## headings to organize sections. The agent reads these instructions before every action.
Skill key: content-strategy — Controls writing style, topics, research rules, and blog categories.
POST /UserAgent/Update
{
"guid": "your-blog-editor-guid",
"configuration": {
"custom_skills": [
{
"key": "content-strategy",
"value": "## Writing Style\n- Short simple sentences. Max 15-20 words.\n- Use active voice.\n- No filler phrases.\n\n## Topics\n- AI model comparisons\n- Prompt engineering tutorials\n- Industry trend analysis\n\n## Blog Types\nRotate between: Single Review, VS Comparison, Category Roundup, Prompt Showcase\n\n## WordPress Categories\nUse: AI, Tutorial, Comparison, News"
}
]
}
}
Preview: what the agent sees
Writing Style
- Short simple sentences. Max 15-20 words.
- Use active voice.
- No filler phrases.
Topics
- AI model comparisons
- Prompt engineering tutorials
- Industry trend analysis
Blog Types
Rotate between: Single Review, VS Comparison, Category Roundup, Prompt Showcase
WordPress Categories
Use: AI, Tutorial, Comparison, News
Skill key: review-preferences — Controls response tone, support channels, and custom rules for review replies.
POST /UserAgent/Update
{
"guid": "your-app-review-guid",
"configuration": {
"custom_skills": [
{
"key": "review-preferences",
"value": "## Response Tone\nCasual and natural. Not corporate.\nShow empathy for negative reviews.\n\n## Support Channels\nDirect users to: [email protected]\nFor billing: [email protected]\n\n## Rules\n- Always thank 5-star reviewers\n- For bugs: acknowledge and promise investigation\n- Never argue with users\n- Max response length: 3 sentences"
}
]
}
}
Preview: what the agent sees
Response Tone
Casual and natural. Not corporate. Show empathy for negative reviews.
Support Channels
Direct users to: [email protected]
For billing: [email protected]
Rules
- Always thank 5-star reviewers
- For bugs: acknowledge and promise investigation
- Never argue with users
- Max response length: 3 sentences
Skill key: event-preferences — Controls target regions, holiday priorities, and event style.
POST /UserAgent/Update
{
"guid": "your-app-event-guid",
"configuration": {
"custom_skills": [
{
"key": "event-preferences",
"value": "## Target Regions\nUS, TR, DE, GB\n\n## Holiday Priorities\nHigh: New Year, Black Friday, Christmas\nMedium: Valentine's Day, Halloween\nSkip: Regional holidays outside target regions\n\n## Event Style\nShort punchy titles (max 30 chars).\nAction-oriented descriptions.\nAlways tie events to app features."
}
]
}
}
Preview: what the agent sees
Target Regions
US, TR, DE, GB
Holiday Priorities
High: New Year, Black Friday, Christmas
Medium: Valentine's Day, Halloween
Skip: Regional holidays outside target regions
Event Style
Short punchy titles (max 30 chars). Action-oriented descriptions.
Skill key: push-preferences — Controls push notification tone, language, targeting, and holiday choices.
POST /UserAgent/Update
{
"guid": "your-push-agent-guid",
"configuration": {
"custom_skills": [
{
"key": "push-preferences",
"value": "## Push Tone\nFriendly and casual.\nTurkish for locale_tr users, English for locale_en.\n\n## Emoji Usage\nMax 1 emoji per push.\n\n## Holiday Preferences\nFocus on: New Year, Ramadan, Republic Day\nSkip: Valentine's Day, Halloween\n\n## Targeting\nAlways segment by locale.\nSend paid-tier users a premium version.\nFree users get engagement-focused pushes."
}
]
}
}
Preview: what the agent sees
Push Tone
Friendly and casual. Turkish for locale_tr users, English for locale_en.
Emoji Usage
Max 1 emoji per push.
Holiday Preferences
Focus on: New Year, Ramadan, Republic Day
Skip: Valentine's Day, Halloween
Targeting
Always segment by locale. Premium version for paid users.
Skill key: newsletter-strategy — Controls newsletter topics, tone, audience, frequency, and branding.
POST /UserAgent/Update
{
"guid": "your-newsletter-guid",
"configuration": {
"custom_skills": [
{
"key": "newsletter-strategy",
"value": "## Topics\nAI industry news, product updates, tutorials.\nPull content from: https://wiro.ai/blog\n\n## Tone\nFriendly and educational.\nUse first person plural (we, our).\n\n## Frequency\nWeekly on Mondays.\n\n## Branding\nSubject line prefix: [Wiro Weekly]\nAlways include unsubscribe link.\nMax 3 sections per newsletter."
}
]
}
}
Preview: what the agent sees
Topics
AI industry news, product updates, tutorials. Pull from: wiro.ai/blog
Tone
Friendly and educational. First person plural.
Frequency
Weekly on Mondays.
Branding
Subject line prefix: [Wiro Weekly]. Max 3 sections per newsletter.
Skill key: lead-strategy — Controls ICP definition, outreach tone, email templates, and scoring rules.
POST /UserAgent/Update
{
"guid": "your-leadgen-guid",
"configuration": {
"custom_skills": [
{
"key": "lead-strategy",
"value": "## Our Business\nCompany: Acme Corp\nProduct: AI-powered CRM\nWebsite: https://acme.com\nValue Proposition: 10x faster lead qualification\n\n## Ideal Customer Profile\nIndustry: SaaS, FinTech\nCompany size: 50-500 employees\nJob titles: VP Sales, Head of Growth, CTO\nLocation: US, UK, Germany\n\n## Outreach Tone\nCasual but professional.\nReference their recent LinkedIn posts.\nKeep emails under 100 words.\n\n## Disqualifiers\nCompanies with fewer than 10 employees.\nAgencies and consultancies."
}
]
}
}
Preview: what the agent sees
Our Business
Company: Acme Corp
Product: AI-powered CRM
Website: acme.com
Ideal Customer Profile
Industry: SaaS, FinTech. Size: 50-500. Titles: VP Sales, CTO. Location: US, UK, Germany.
Outreach Tone
Casual but professional. Reference LinkedIn posts. Under 100 words.
Disqualifiers
Under 10 employees. Agencies and consultancies.
Skill key: ad-strategy — Controls target audience, budget goals, KPI thresholds, and ad creative preferences.
POST /UserAgent/Update
{
"guid": "your-google-ads-guid",
"configuration": {
"custom_skills": [
{
"key": "ad-strategy",
"value": "## Target Audience\nCountries: US, GB, DE\nLanguages: English\nAge range: 25-54\nInterests: Technology, AI, SaaS\n\n## Budget & Goals\nMonthly budget limit: $5,000\nTarget CPA: $25\nTarget ROAS: 4.0\n\n## Creative Preferences\nTone: Professional, benefit-focused\nAlways include pricing in ads\nUse numbers and statistics in headlines\n\n## Negative Keywords\nfree, cheap, tutorial, how to"
}
]
}
}
Preview: what the agent sees
Target Audience
Countries: US, GB, DE. Age: 25-54. Interests: Technology, AI, SaaS.
Budget & Goals
Monthly limit: $5,000. Target CPA: $25. Target ROAS: 4.0.
Creative Preferences
Professional, benefit-focused. Include pricing. Use numbers in headlines.
Negative Keywords
free, cheap, tutorial, how to
Skill key: ad-strategy — Controls target audience, budget goals, KPI thresholds, and creative preferences for Meta campaigns.
POST /UserAgent/Update
{
"guid": "your-meta-ads-guid",
"configuration": {
"custom_skills": [
{
"key": "ad-strategy",
"value": "## Target Audience\nCountries: US, GB, DE\nLanguages: English\nAge range: 25-54\n\n## Budget & Goals\nMonthly budget limit: $5,000\nTarget CPA: $20\n\n## Creative Preferences\nVisual style: Clean, minimal, product-focused\nUse carousel ads for feature showcases\nVideo ads max 15 seconds\n\n## Campaign Types\nConversions: Main budget (70%)\nRetargeting: Website visitors (20%)\nBrand awareness: Lookalike audiences (10%)"
}
]
}
}
Preview: what the agent sees
Target Audience
Countries: US, GB, DE. Age: 25-54.
Budget & Goals
Monthly limit: $5,000. Target CPA: $20.
Creative Preferences
Clean, minimal, product-focused. Carousel for features. Video max 15s.
Campaign Types
Conversions: 70%. Retargeting: 20%. Brand awareness: 10%.
Managing Scheduled Tasks
Scheduled tasks run automatically on a cron schedule. You can enable/disable them and change their frequency.
Example: Change scanner frequency
POST /UserAgent/Update
{
"guid": "your-useragent-guid",
"configuration": {
"custom_skills": [
{ "key": "review-scanner", "enabled": true, "interval": "0 */4 * * *" },
{ "key": "content-scanner", "enabled": false }
]
}
}
This changes review-scanner to run every 4 hours and disables content-scanner entirely.
Common Cron Expressions
| Expression | Meaning |
|---|---|
*/30 * * * * | Every 30 minutes |
0 * * * * | Every hour |
0 */2 * * * | Every 2 hours |
0 */4 * * * | Every 4 hours |
0 */6 * * * | Every 6 hours |
0 9 * * * | Daily at 9:00 AM UTC |
0 10 * * * | Daily at 10:00 AM UTC |
0 9 * * 1 | Every Monday at 9:00 AM UTC |
0 10 * * 3 | Every Wednesday at 10:00 AM UTC |
Full Example: Push Notification Manager
Complete flow — fetch skills, then update preferences and schedules in one request.
Step 1 — Discover skills:
POST /UserAgent/Detail
{
"guid": "your-push-agent-guid"
}
// Response → configuration.custom_skills:
[
{
"key": "push-preferences",
"value": "## Push Tone\nWrite like a mobile growth expert...",
"description": "Push notification style, language, and targeting preferences",
"enabled": true,
"interval": null,
"_editable": true
},
{
"key": "push-scanner",
"value": "",
"description": "Scan holidays and craft push notification suggestions",
"enabled": true,
"interval": "0 9 * * *",
"_editable": false
},
{
"key": "push-dispatcher",
"value": "",
"description": "Send queued push notifications on schedule",
"enabled": true,
"interval": "0 * * * *",
"_editable": false
}
]
Step 2 — Update everything in one request:
POST /UserAgent/Update
{
"guid": "your-push-agent-guid",
"configuration": {
"custom_skills": [
{
"key": "push-preferences",
"value": "## Push Tone\nFriendly and casual. Turkish for locale_tr, English for locale_en.\n\n## Holiday Preferences\nFocus on: New Year, Ramadan, Republic Day.\nSkip: Valentine's Day, Halloween.\n\n## Targeting\nAlways segment by locale. Premium version for paid users."
},
{
"key": "push-scanner",
"enabled": true,
"interval": "0 9 * * 1"
},
{
"key": "push-dispatcher",
"interval": "0 */2 * * *"
}
]
}
}
This single request:
- push-preferences — rewrites targeting rules (editable skill,
valueupdated) - push-scanner — changes from daily to Mondays only (
intervalupdated) - push-dispatcher — changes from hourly to every 2 hours (
intervalupdated)
Available Skills by Agent
Preferences (Editable Instructions)
Every agent has exactly one editable preference skill that controls its behavior:
| Agent | Skill Key | What It Controls |
|---|---|---|
| Social Manager | content-tone | Brand voice, hashtags, posting style per platform |
| Blog Content Editor | content-strategy | Writing style, topics, research rules, blog types |
| App Review Support | review-preferences | Response tone, support channels, custom rules |
| App Event Manager | event-preferences | Event regions, holiday priorities, style |
| Push Notification | push-preferences | Push tone, language, targeting, holiday choices |
| Newsletter Manager | newsletter-strategy | Topics, tone, audience, frequency, branding |
| Lead Gen Manager | lead-strategy | ICP definition, outreach tone, scoring rules |
| Google Ads Manager | ad-strategy | Target audience, budget goals, KPI thresholds |
| Meta Ads Manager | ad-strategy | Target audience, budget goals, creative preferences |
Scheduled Tasks
Automated tasks that run on a cron schedule. Toggle enabled and adjust interval:
| Agent | Task Key | Description | Default Schedule |
|---|---|---|---|
| Social Manager | content-scanner | Scan for new AI models and prepare posts | Hourly |
| Social Manager | gmail-checker | Check inbox for content requests | Every 30 min |
| Blog Content | blog-scanner | Discover blog topics and write content | Daily 9 AM |
| Blog Content | gmail-checker | Check inbox for blog topic requests | Every 30 min |
| App Review | review-scanner | Scan stores for new reviews | Every 2 hours |
| App Event | app-event-scanner | Scan holidays, suggest App Store events | Monday 9 AM |
| Push Notification | push-scanner | Scan holidays, craft push suggestions | Daily 9 AM |
| Push Notification | push-dispatcher | Send queued push notifications | Hourly |
| Newsletter | newsletter-sender | Create and send scheduled newsletters | Monday 9 AM |
| Newsletter | subscriber-scanner | Daily subscriber list health check | Daily 10 AM |
| Lead Gen | prospect-scanner | Weekly prospect search and scoring | Monday 10 AM |
| Lead Gen | outreach-reporter | Daily outreach performance report | Daily 9 AM |
| Lead Gen | reply-handler | Check replies, analyze sentiment | Every 4 hours |
| Google Ads | performance-reporter | Daily performance report | Daily 9 AM |
| Google Ads | competitor-scanner | Weekly competitor analysis | Monday 10 AM |
| Google Ads | holiday-ad-planner | Scan holidays, suggest ad campaigns | Wednesday 10 AM |
| Meta Ads | performance-reporter | Daily performance report | Daily 9 AM |
| Meta Ads | audience-scanner | Weekly audience analysis | Monday 10 AM |
| Meta Ads | holiday-ad-planner | Scan holidays, suggest campaigns | Wednesday 10 AM |
Update Rules
| Field | Editable Skills (_editable: true) | System Skills (_editable: false) |
|---|---|---|
key | Read-only (used for lookup) | Read-only |
enabled | Can toggle on/off | Can toggle on/off |
interval | Can change cron schedule | Can change cron schedule |
value | Can rewrite instructions | Ignored (hidden in API response) |
description | Can update description | Ignored |
_editable | Read-only | Read-only |
- Include only the fields you want to change — omitted fields keep their current values
- New skills cannot be added — only existing skills (matched by
key) can be updated - Send empty string
""forintervalto clear the schedule (becomesnull) - You can update credentials and skills in the same
POST /UserAgent/Updaterequest
Agent Use Cases
Build products with autonomous AI agents using the Wiro API.
Two Deployment Patterns
Every product built on Wiro agents follows one of two patterns. Choosing the right one depends on whether your users need to connect their own third-party accounts.
Pattern 1: Instance Per Customer
Most agents interact with external services — posting to social media, managing ad campaigns, sending emails. These require OAuth tokens or API keys that belong to the end user. Deploy a separate agent instance for each of your customers.
Why: Each customer connects their own accounts. Credentials are bound to the instance, isolated from other customers.
How: Call POST /UserAgent/Deploy once per customer, then use the OAuth flow to connect their accounts.
Real-World Examples
| Your Product | Agent Type | Why Per-Customer |
|---|---|---|
| Digital marketing agency dashboard | Social Manager | Each client connects their own Twitter, Instagram, Facebook, TikTok, LinkedIn |
| Mobile app company | App Review Support | Each app has its own App Store / Google Play credentials |
| E-commerce platform | Google Ads Manager + Meta Ads Manager | Each advertiser connects their own ad accounts |
| Marketing SaaS | Newsletter Manager | Each customer connects their own Brevo/SendGrid/Mailchimp |
| Sales platform | Lead Gen Manager | Each sales team connects their own Apollo/Lemlist |
| Content agency tool | Blog Content Editor | Each client connects their own WordPress site |
| App publisher platform | App Event Manager | Each app has its own Firebase project |
| Customer engagement tool | Social Manager | Each brand manages their own social presence |
Pattern 2: Session Per User
For conversational agents that don't need per-user credentials. One agent instance serves many users, each identified by a unique sessionkey that isolates their conversation history.
Why: No third-party accounts to connect. The agent answers questions using its built-in knowledge or pre-configured data sources.
How: Deploy one instance via POST /UserAgent/Deploy, then send messages with different sessionkey values per user.
Real-World Examples
| Your Product | Use Case | Why Sessions |
|---|---|---|
| Knowledge base chatbot | Answer questions from documentation | No per-user credentials needed |
| Product recommendation advisor | Suggest products based on conversation | Same catalog for all users |
| Internal company assistant | HR policies, IT help, onboarding | Shared knowledge base |
| Customer support bot | Handle common support questions | No external service connections |
When to Use Which
| Question | Instance Per Customer | Session Per User |
|---|---|---|
| Does each user connect their own social/ad/email accounts? | Yes | No |
| Do credentials differ between users? | Yes | No |
| Is conversation the primary interaction? | Sometimes | Always |
| Does the agent perform actions on behalf of the user? | Yes | Rarely |
| How many instances do you need? | One per customer | One total (or a few) |
Building Your Product
White-Label Chat
Build a fully branded chat experience with no Wiro UI visible to your users.
- Deploy an agent via
POST /UserAgent/Deploy - Start the agent with
POST /UserAgent/Start - Build your own chat UI
- Send messages via
POST /UserAgent/Message/Send - Stream responses in real-time via WebSocket using the
agenttoken - Manage conversation history with
POST /UserAgent/Message/History
# Deploy
curl -X POST "https://api.wiro.ai/v1/UserAgent/Deploy" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"agentguid": "agent-template-guid",
"title": "Customer Support Bot",
"useprepaid": true,
"plan": "starter"
}'
# Send a message
curl -X POST "https://api.wiro.ai/v1/UserAgent/Message/Send" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"useragentguid": "deployed-useragent-guid",
"message": "How do I reset my password?",
"sessionkey": "user-456"
}'
Webhook-Driven Pipelines
For backend-to-backend integrations where you don't need real-time streaming.
- Send a message with a
callbackurl - Continue processing other work
- Receive the agent's response via HTTP POST to your webhook endpoint
- Chain the result into your next workflow step
See Agent Webhooks for payload format and retry policy.
Scheduled Automation
Combine agents with cron jobs for recurring tasks.
Cron (every Monday 9am)
→ POST /UserAgent/Message/Send (with callbackurl)
→ Agent processes the task
→ Webhook fires to your server
→ Your server emails the report / posts to Slack / updates dashboard
This pattern works well for weekly social media content planning, daily ad performance reviews, monthly newsletter generation, and automated lead enrichment pipelines.
Multi-Agent Orchestration
Deploy multiple specialized agents and coordinate them from your backend.
Your Backend
├── Research Agent → "Find trending topics in AI this week"
│ ↓ webhook response
├── Writing Agent → "Write a blog post about: {research results}"
│ ↓ webhook response
└── Publishing Agent → "Publish this post to WordPress and share on social media"
Each agent is an independent instance with its own credentials. Your backend passes output from one agent as input to the next.
Available Agents
Wiro provides pre-built agent templates you can deploy immediately. Each agent specializes in a specific domain and comes with the relevant skills and credential slots pre-configured.
| Agent | What It Does | Credentials |
|---|---|---|
| Social Manager | Create, schedule, and publish social media content | Twitter/X, Instagram, Facebook, TikTok, LinkedIn (OAuth) |
| Google Ads Manager | Create and optimize Google Ads campaigns | Google Ads (OAuth) |
| Meta Ads Manager | Manage Facebook and Instagram ad campaigns | Meta Ads (OAuth), Facebook (OAuth) |
| Newsletter Manager | Design and send email newsletters | Brevo, SendGrid, or Mailchimp (API key or OAuth) |
| Lead Gen Manager | Find and enrich leads, run outreach sequences | Apollo, Lemlist (API key) |
| Blog Content Editor | Write and publish blog posts | WordPress (API key) |
| App Review Support | Monitor and respond to app store reviews | App Store, Google Play (API key) |
| App Event Manager | Track and manage mobile app events | Firebase (API key) |
| HubSpot Manager | Manage CRM contacts, deals, and workflows | HubSpot (OAuth) |
Deploying an Agent
import requests
headers = {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json"
}
# List available agents
agents = requests.post(
"https://api.wiro.ai/v1/Agent/List",
headers=headers,
json={}
)
print(agents.json())
# Deploy an instance
deploy = requests.post(
"https://api.wiro.ai/v1/UserAgent/Deploy",
headers=headers,
json={
"agentguid": "social-manager-agent-guid",
"title": "Acme Corp Social Media",
"useprepaid": True,
"plan": "starter"
}
)
useragent_guid = deploy.json()["useragents"][0]["guid"]
# Connect Twitter via OAuth
connect = requests.post(
"https://api.wiro.ai/v1/UserAgentOAuth/XConnect",
headers=headers,
json={
"userAgentGuid": useragent_guid,
"redirectUrl": "https://your-app.com/settings?connected=twitter"
}
)
authorize_url = connect.json()["authorizeUrl"]
# Start the agent
requests.post(
"https://api.wiro.ai/v1/UserAgent/Start",
headers=headers,
json={"guid": useragent_guid}
)
# Send a message
message = requests.post(
"https://api.wiro.ai/v1/UserAgent/Message/Send",
headers=headers,
json={
"useragentguid": useragent_guid,
"message": "Create a thread about our new product launch",
"sessionkey": "campaign-q2"
}
)
print(message.json())
Browse available agents and their capabilities at Agent/List or in the Wiro dashboard.
Organizations & Teams
Collaborate with your team under a shared workspace with unified billing, access controls, and resource management.
Overview
Wiro supports three workspace contexts for organizing your resources:
- Personal — your default workspace. Projects, agents, and wallet are tied to your individual account.
- Organization — a parent entity that groups one or more teams. The organization owner controls the lifecycle of teams and their members.
- Team — a workspace under an organization with its own wallet, projects, agents, and member permissions. Team members share access to resources deployed within the team.
Personal Account
├── Personal Projects
├── Personal Agents
└── Personal Wallet
Organization (created by you)
├── Team A
│ ├── Team Wallet
│ ├── Team Projects
│ ├── Team Agents
│ └── Members (owner, admins, members)
├── Team B
│ ├── Team Wallet
│ ├── Team Projects
│ ├── Team Agents
│ └── Members
└── ...
Every user always has a personal workspace. Organizations and teams are optional — you can use Wiro entirely in personal mode without ever creating an organization.
Key Concepts
Workspaces and Context
When you make an API request or use the dashboard, you operate in one of two contexts:
| Context | Resources you see | Wallet charged | How to activate |
|---|---|---|---|
| Personal | Your personal projects, agents, tasks | Your personal wallet | Default — use a personal project API key |
| Team | Team projects, team agents, team tasks | Team wallet | Use a team project API key |
Switching context changes which projects, agents, and wallet you interact with. Resources in one context are isolated from the other — personal agents cannot see team projects, and team agents cannot access personal resources.
Resource Isolation
Each workspace is fully isolated:
- Projects belong to either your personal workspace or a specific team. A project's API key automatically resolves the correct context.
- Agents are deployed into a workspace. Team agents are visible to all team members; personal agents are visible only to you.
- Wallet transactions are recorded against the workspace that initiated them. Team tasks deduct from the team wallet; personal tasks deduct from your personal wallet.
- Tasks are tagged with the workspace context and only appear in the matching project usage and statistics views.
Transferring Resources
Projects and agents can be transferred between workspaces:
- Personal → Team — move a project or agent from your personal workspace into a team you have admin access to
- Team → Personal — move a project or agent from a team back to your personal workspace
- Team → Team — move a project or agent between teams you have admin access to
When a resource is transferred, its billing context changes immediately. Future tasks on a transferred project will be billed to the new workspace's wallet.
Important: Agents can only access projects in the same workspace. If you transfer a project out of a team, agents in that team can no longer use it.
Organizations vs Teams
An organization is a management container — it does not hold resources directly. All resources (projects, agents, wallets) live inside teams.
| Feature | Organization | Team |
|---|---|---|
| Holds projects and agents | No | Yes |
| Has a wallet | No | Yes |
| Has members | No (members belong to teams) | Yes |
| Can be created by | Any user | Organization owner |
| Can be deleted by | Organization owner | Organization owner |
| Can be restored | Yes (by owner) | Yes (when org is restored) |
Roles
| Role | Scope | Permissions |
|---|---|---|
| Owner | Organization | Create/delete teams, manage all team members, delete/restore organization, transfer agents and projects |
| Admin | Team | Manage team settings (spend limits, model access), invite/remove members, transfer agents and projects |
| Member | Team | Use team resources (run models, send agent messages), view spending summaries |
Getting Started
- Create an organization — go to your Dashboard and click "Create Organization"
- Create a team — inside the organization, create a team with a name
- Invite members — send email invitations to your teammates
- Fund the team wallet — deposit credits or redeem coupons in the team context
- Create projects — create API projects within the team to start running models
- Deploy agents — deploy agent instances within the team for shared access
For step-by-step instructions, see Managing Teams.
What's Next
- Managing Teams — Create organizations, invite members, manage roles and permissions
- Team Billing & Spending — Wallets, spend limits, model access controls, and budget alerts
- Team API Access — How workspace context works with API keys and context guards
Managing Teams
Create organizations, invite members, and manage roles and permissions.
Creating an Organization
- Go to your Dashboard
- Click Create Organization
- Enter an organization name
- Click Create
You become the organization owner automatically. Only you can create teams, delete the organization, or restore it after deletion.
POST /Organization/Create
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Organization name |
Creating a Team
Only the organization owner can create teams.
POST /Team/Create
| Parameter | Type | Required | Description |
|---|---|---|---|
organizationguid | string | Yes | Organization guid |
name | string | Yes | Team name |
Inviting Members
Organization owners and team admins can invite new members via email. Invitations expire after 7 days and can be resent.
POST /Team/Member/Invite
| Parameter | Type | Required | Description |
|---|---|---|---|
teamguid | string | Yes | Team guid |
email | string | Yes | Invitee email address |
role | string | Yes | Role: "admin" or "member" |
Invitation States
| Status | Description |
|---|---|
pending | Invitation sent, waiting for the user to accept |
active | User accepted the invitation and is an active member |
removed | Member was removed or invitation was cancelled |
Member Roles
| Role | Run models | Message agents | View spending | Manage settings | Invite members | Delete team |
|---|---|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | Yes | Yes | No |
| Member | Yes | Yes | Yes | No | No | No |
Transferring Agents
POST /Team/TransferAgent
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid | string | Yes | Agent instance guid |
targetteamguid | string | Yes | Target team guid, or empty string "" for personal |
Active subscriptions and credit purchases move with the agent. The agent is restarted with the new context.
Transferring Projects
POST /Team/TransferProject
| Parameter | Type | Required | Description |
|---|---|---|---|
projectapikey | string | Yes | Project API key |
targetteamguid | string | Yes | Target team guid, or empty string "" for personal |
Important: Agents can only access projects in the same workspace. Transferring a project may break agent workflows that depend on it.
Deleting & Restoring
Only the organization owner can delete teams and organizations. Deleting transfers all agents and projects to the owner's personal workspace.
Deleted organizations can be restored by the owner. This reactivates the organization, all its teams, and previously accepted members.
POST /Organization/Restore
What's Next
- Organizations & Teams Overview — Concepts and workspace hierarchy
- Team Billing & Spending — Wallets, spend limits, and model access controls
- Team API Access — How context works in API requests
Team Billing & Spending
Manage team wallets, set spend limits, control model access, and track usage across members.
Team Wallets
Each team has its own wallet, independent of members' personal wallets. When a task runs in a team context, the cost is deducted from the team wallet — never from the individual member's personal wallet.
Team wallets are funded the same way as personal wallets: deposits, coupons, and auto-pay. Switch to the team context in the dashboard and navigate to Wallet.
Spend Limits
| Limit Type | Set by | Applies to | Effect when reached |
|---|---|---|---|
| Team spend limit | Admin / Owner | Entire team | All tasks rejected for all members |
| Member spend limit | Admin / Owner | Individual member | Tasks rejected for that member only |
When a team's total spending reaches 80% of the team spend limit, admins receive an email alert.
Model Access Controls
Team admins can restrict which AI models team members are allowed to run. Every team has a modelaccess setting with three modes:
| Mode | modelaccess value | Behavior |
|---|---|---|
| All Models | "all" | No restrictions. Team members can run any model. This is the default. |
| Allowlist | "allowlist" | Only models in allowedmodelids can be run. All others are blocked. |
| Blocklist | "blocklist" | Models in blockedmodelids cannot be run. All others are allowed. |
You configure one mode at a time. Setting modelaccess back to "all" removes all restrictions.
POST /Team/Update
| Parameter | Type | Required | Description |
|---|---|---|---|
teamguid | string | Yes | Team guid |
modelaccess | string | No | Access mode: "all", "allowlist", or "blocklist". Default: "all" |
allowedmodelids | array | No | List of model IDs that are allowed. Used when modelaccess is "allowlist". |
blockedmodelids | array | No | List of model IDs that are blocked. Used when modelaccess is "blocklist". |
Where Access Controls Are Enforced
Model access is checked at the /Run endpoint — when a team member submits a task using a team project API key. Access controls do not affect browsing the model catalog or personal projects.
Error Response
{
"result": false,
"errors": [
{
"code": 0,
"message": "This model is not allowed in your team. Contact your team admin."
}
]
}
Spending Tracking
POST /Team/SpendingSummary
Returns team totals, your individual spending, and limit information:
{
"result": true,
"teamTotal": 45.23,
"playgroundTotal": 32.10,
"apiTotal": 13.13,
"memberSpent": {
"total": 12.50,
"playground": 8.30,
"api": 4.20
},
"spendLimit": 500.00,
"memberSpendLimit": 100.00
}
All team members can view the spending summary.
Coupons
| Coupon Scope | Who can redeem | Wallet credited |
|---|---|---|
| Everyone | Any user | The redeemer's active wallet (personal or team) |
| Team | Only members of the specified team | The team wallet |
| User | Only the specified user | The user's personal wallet |
What's Next
- Organizations & Teams Overview — Concepts and workspace hierarchy
- Managing Teams — Create organizations, invite members, manage roles
- Team API Access — How context works in API requests
- Pricing — General pricing information
Team API Access
How workspace context is resolved in API requests, and how access controls protect cross-context operations.
Context Resolution
Every authenticated API request resolves to a workspace context — either personal or a specific team.
The context is determined automatically by the project's assignment. You do not need to send any additional headers — the API key carries the context implicitly.
# Team project API key — team context is automatic
curl -X POST "https://api.wiro.ai/v1/Run/google/nano-banana" \
-H "x-api-key: YOUR_TEAM_PROJECT_API_KEY" \
-d '{"prompt": "Hello"}'
# Personal project API key — personal context is automatic
curl -X POST "https://api.wiro.ai/v1/Run/google/nano-banana" \
-H "x-api-key: YOUR_PERSONAL_API_KEY" \
-d '{"prompt": "Hello"}'
Create a project inside a team to get a team API key, or use a personal project for personal context. The same x-api-key header works for both — no extra configuration needed.
What Gets Filtered by Context
| Endpoint | Personal context returns | Team context returns |
|---|---|---|
Project/List | Personal projects only | Team projects only |
UserAgent/MyAgents | Personal agents only | Team agents only |
Task/List | Personal tasks only | Team tasks only |
Task/Stat | Personal task statistics | Team task statistics |
Wallet/List | Personal wallet | Team wallet |
Wallet/TransactionList | Personal transactions | Team transactions |
Agent Context Guards
Wiro enforces strict context isolation for agent operations. Your current workspace context must match the agent's workspace:
| Your context | Agent's workspace | Result |
|---|---|---|
| Personal | Personal | Allowed |
| Team A | Team A | Allowed |
| Personal | Team A | Blocked |
| Team A | Personal | Blocked |
| Team A | Team B | Blocked |
Protected Endpoints
Context guards are enforced on: Message/Send, Message/History, Message/Sessions, Message/Delete, Deploy, CreateExtraCreditCheckout, CancelSubscription, RenewSubscription, UpgradePlan.
Error Response
{
"result": false,
"errors": [
{
"code": 0,
"message": "This agent belongs to a team. Switch to the team context to access it."
}
]
}
Wallet Billing Flow
When a task runs in team context:
API Key → Project (teamguid) → Task (teamguid) → Wallet Transaction (uuid=teamguid)
For personal context, teamguid is null and billing uses the user's personal UUID.
Best Practices
- Separate projects by environment — create distinct team projects for development, staging, and production. The team context is resolved automatically from the API key.
- Check agent context before messaging — ensure the project and agent belong to the same workspace
- Transfer resources carefully — agents can only access projects in the same workspace
What's Next
- Organizations & Teams Overview — Concepts and workspace hierarchy
- Managing Teams — Create organizations, invite members, manage roles
- Team Billing & Spending — Wallets, spend limits, and model access controls
- Authentication — API key setup and authentication methods
- Projects — Project management and API credentials