# Wiro API Documentation Complete API documentation for the Wiro AI platform — run AI models through a unified API. ## Table of Contents 1. [Introduction](#introduction) 2. [Authentication](#authentication) 3. [Projects](#projects) 4. [Models](#models) 5. [Run a Model](#run-a-model) 6. [Model Parameters](#model-parameters) 7. [Tasks](#tasks) 8. [LLM Streaming](#llm-streaming) 9. [WebSocket](#websocket) 10. [Realtime Voice](#realtime-voice) 11. [Files](#files) 12. [Concurrency Limits](#concurrency-limits) 13. [Error Reference](#error-reference) 14. [Code Examples](#code-examples) 15. [Pricing](#pricing) 16. [FAQ](#faq) 17. [MCP Server](#mcp-server) 18. [Self-Hosted MCP](#self-hosted-mcp) 19. [Node.js Library](#nodejs-library) 20. [n8n Wiro Integration](#n8n-wiro-integration) 21. [Agent Overview](#agent-overview) 22. [Agent Messaging](#agent-messaging) 23. [Agent WebSocket](#agent-websocket) 24. [Agent Webhooks](#agent-webhooks) 25. [Agent Credentials & OAuth](#agent-credentials--oauth) 26. [Agent Skills](#agent-skills) 27. [Agent Use Cases](#agent-use-cases) --- # Introduction Everything you need to get started with the Wiro AI platform. ## What is Wiro? 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 1. **Sign up** at [wiro.ai](https://wiro.ai/auth/signup) 2. **Create a project** in the [Dashboard](https://wiro.ai/panel/project/new) to get your API key 3. **Pick a model** from the [marketplace](https://wiro.ai/models) 4. **Make your first API call** — see [Code Examples](#code-examples) for full end-to-end samples ## Response Format Every API response returns JSON with a consistent structure: ```json { "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` — Success - `400` — Bad request (check parameters) - `401` — Unauthorized (invalid or missing API key) - `403` — Forbidden (signature mismatch or insufficient permissions) - `429` — Rate limit exceeded - `500` — 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](https://wiro.ai/panel/project/new) — it cannot be changed afterward. **Available methods:** Signature-Based (Recommended) | API Key Only (Simple) ## 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 1. Generate a **nonce** (unix timestamp or random integer) 2. Concatenate: `API_SECRET + NONCE` 3. Create an HMAC-SHA256 hash using your `API_KEY` as the secret key 4. Send the signature, nonce, and API key as 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 1. Go to your [Dashboard](https://wiro.ai/panel) 2. Navigate to [Projects](https://wiro.ai/panel/project) 3. Click [New Project](https://wiro.ai/panel/project/new) 4. Enter a project name 5. Select your [authentication method](#authentication): - **Signature-Based** — generates both an API key and API secret - **API Key Only** — generates only an API key 6. Click **Create** ## 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](https://wiro.ai/panel/project) 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 ```json { "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 ```json { "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": "
The Wiro Virtual Try-On AI model...
", "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 [Wiro model page](https://wiro.ai/models). --- # 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](#tasks), [WebSocket](#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](#models) 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: ```json { "result": true, "errors": [], "taskid": "2221", "socketaccesstoken": "eDcCm5yyUfIvMFspTwww49OUfgXkQt" } ``` ## Full Flow The typical workflow after calling the Run endpoint: 1. **Run** — call `POST /Run/{owner-slug}/{model-slug}` and receive a task ID 2. **Track** — connect via WebSocket or poll `POST /Task/Detail` 3. **Receive** — get outputs as the model produces them (streaming or final) 4. **Complete** — task reaches `end` status 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: ```json { "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 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: ```bash # 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: ```bash # 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 "inputDocumentMultiple=@doc1.pdf" \ -F "inputDocumentMultiple=@doc2.pdf" \ -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 "inputDocumentMultiple=@doc1.pdf" \ -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: ```bash # 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 "inputImageClothes=@shirt.jpg" \ -F "inputImageClothes=@pants.jpg" # 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 "inputImageClothes=@shirt.jpg" \ -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: ```json { "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: ```bash curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \ -H "x-api-key: YOUR_API_KEY" \ -F "inputImage=@photo.jpg" \ -F "scale=4" ``` ### Virtual Try-On Multiple image inputs — multipart with multiple files: ```bash curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \ -H "x-api-key: YOUR_API_KEY" \ -F "inputImageHuman=@person.jpg" \ -F "inputImageClothes=@shirt.jpg" ``` ### LLM / Document Processing Text prompt with optional document uploads: ```bash curl -X POST "https://api.wiro.ai/v1/Run/{owner-slug}/{model-slug}" \ -H "x-api-key: YOUR_API_KEY" \ -F "inputDocumentMultiple=@resume.pdf" \ -F "prompt=Extract the candidate name and skills" \ -F "outputType=json" \ -F "language=en" ``` > **Note:** LLM responses are available as structured content in `outputs` (with `contenttype: "raw"` containing `prompt`, `raw`, `thinking`, and `answer` fields) and as merged plain text in `debugoutput`. See [Tasks](/docs/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 [WebSocket](/docs/realtime-voice-conversation) after the task starts: Available realtime models: - [openai/gpt-realtime-mini](https://wiro.ai/models/openai/gpt-realtime-mini) - [openai/gpt-realtime](https://wiro.ai/models/openai/gpt-realtime) - [elevenlabs/realtime-conversational-ai](https://wiro.ai/models/elevenlabs/realtime-conversational-ai) ```json // 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: ```json { "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_queue` → `task_accept` → `task_preprocess_start` → `task_preprocess_end` → `task_assign` → `task_start` → `task_output` → `task_output_full` → `task_end` → `task_postprocess_start` → `task_postprocess_end` ## 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 files array. For non-LLM models, a successful run populates this with CDN URLs. If it's empty or missing, the task likely failed. > **Note:** For **LLM models**, `outputs` contains a structured entry with `contenttype: "raw"` and the response broken into `prompt`, `raw`, `thinking`, and `answer` fields. The merged plain text is also available in `debugoutput`. Always use `pexit` as the primary success check. ```json // Success (image/audio model): pexit "0", outputs present { "pexit": "0", "outputs": [{ "name": "0.png", "contenttype": "image/png", "size": "202472", "url": "https://cdn1.wiro.ai/.../0.png" }] } // Success (LLM model): pexit "0", structured response in outputs + merged text in debugoutput { "pexit": "0", "outputs": [{ "contenttype": "raw", "content": { "prompt": "Hello!", "raw": "Hello! How can I help you today?", "thinking": [], "answer": ["Hello! How can I help you today?"] } }], "debugoutput": "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`. ## LLM Models For LLM (Large Language Model) requests, the model's response is available in two places: `outputs` contains a structured entry with `contenttype: "raw"` and the response broken into `prompt`, `raw`, `thinking`, and `answer` fields; `debugoutput` contains the merged plain text. When polling with Task Detail, use either field depending on whether you need structured or plain-text access. For real-time streaming of LLM responses, use [WebSocket](#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 ```json { "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. 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. | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ------------------------------------ | | `tasktoken` | string | Yes | The task token (socketaccesstoken) | 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. ### Response ```json { "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 `outputfolderid` is 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: true` immediately (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 `outputs` (with `contenttype: "raw"`) and as merged plain text in `debugoutput` - Streaming `task_output` messages contain structured `thinking` and `answer` arrays — not plain strings - Multi-turn conversations are supported via `session_id` and `user_id` parameters - `pexit` is the primary success indicator Available LLM models include: - [openai/gpt-5-2](https://wiro.ai/models/openai/gpt-5-2) - [openai/gpt-oss-20b](https://wiro.ai/models/openai/gpt-oss-20b) - [qwen/qwen3-5-27b](https://wiro.ai/models/qwen/qwen3-5-27b) ## Session & Chat History Wiro maintains conversation history per session. By sending a `session_id` and `user_id` parameters: | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------------------------ | | `session_id` | string | No | UUID identifying the conversation session. Reuse for follow-up messages. | | `user_id` | string | No | UUID identifying the user. | | `prompt` | string | Yes | The user's message or question. | ```json // 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 UUID for `session_id` when starting a new conversation. Pass the same UUID for all follow-up messages to maintain context. ## 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 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: ```json { "thinking": [ "Let me break this into parts...", "Now let me verify my reasoning..." ], "answer": [ "Quantum computing uses qubits...", "To summarize: qubits can be 0, 1, or..." ] } ``` 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. When streaming via WebSocket, `task_output` messages for LLM models contain a structured object: ```json { "type": "task_output", "id": "534574", "tasktoken": "eDcCm5yy...", "message": { "type": "answer", "thinking": ["Let me analyze this step by step...", "The key factors are..."], "answer": ["Quantum computing uses qubits that can exist in superposition..."], "raw": "Quantum computing uses qubits that can exist in superposition...", "isThinking": false, "speed": "48.5", "speedType": "t/s", "elapsedTime": "3s" } } ``` | Field | Type | Description | | ------------------ | ---------- | --------------------------------------------------------------- | | `message.type` | `string` | Phase indicator: `"thinking"` during reasoning, `"answer"` during response. | | `message.thinking` | `string[]` | Array of reasoning/chain-of-thought chunks. May be empty. | | `message.answer` | `string[]` | Array of response chunks. This is the content to show the user. | | `message.raw` | `string` | The full accumulated raw output text (thinking + answer merged). | | `message.isThinking` | `boolean` | `true` while the model is in the thinking phase, `false` during the answer phase. | | `message.speed` | `string` | Generation speed (e.g. "12.4"). | | `message.speedType` | `string` | Unit for speed, typically `"t/s"` (tokens per second). | | `message.elapsedTime` | `string` | Elapsed time since generation started (e.g. "3s", "1m 5s"). | > **Note:** Standard (non-LLM) models send `message` as a plain string. LLM models send it as a `{ thinking, answer }` object. Check the type before parsing. ## Streaming Flow 1. **Run** the model with `prompt`, `session_id`, and `user_id` 2. **Connect** to WebSocket and send `task_info` 3. **Receive** `task_output` messages — each contains the growing `thinking` and `answer` arrays 4. **Display** the latest `answer` array content to the user (optionally show `thinking` in a collapsible section) 5. **Complete** — on `task_postprocess_end`, check `pexit` for success ## Polling Alternative If you don't need real-time streaming, poll `POST /Task/Detail` instead. The final response is available in both `outputs` (structured) and `debugoutput` (merged plain text): ```json { "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:** When polling, `outputs` contains the structured response with separate `thinking` and `answer` fields, while `debugoutput` contains the merged plain text. For real-time token-by-token delivery, use WebSocket streaming 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 1. **Connect** — open a WebSocket connection to `wss://socket.wiro.ai/v1` 2. **Register** — send a `task_info` message with your `tasktoken` 3. **Receive** — listen for messages as the task progresses through its lifecycle 4. **Close** — disconnect after the `task_postprocess_end` event (this is the final event with results) Registration message format: ```json { "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: ```json { "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 Lifecycle events (`task_accept`, `task_preprocess_start`, `task_preprocess_end`, `task_assign`, `task_start`, `task_end`, `task_postprocess_start`) have `message: null`. ### Output Events **Standard models** — `message` is a progress object or plain string: ```json { "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 } ``` **LLM models** — `message` is a structured object with thinking/answer arrays (see LLM & Chat Streaming section). ### Full Output Events `task_output_full` and `task_error_full` are sent once after the process exits. `message` is `{ raw: "..." }` for standard models, or `{ raw, thinking, answer }` for LLM models. ### Final Result `task_postprocess_end` — `message` contains the `outputs` array (file URLs for standard models, structured raw content for LLM models). ### Realtime Events `task_stream_ready`, `task_stream_end` have no `message` field. `task_cost` includes `turnCost`, `cumulativeCost`, and `usage` fields. ## 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: ```json { "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: 1. **Run** the realtime model via [POST /Run](#run-a-model) to get a `socketaccesstoken` 2. **Connect** to the WebSocket and send `task_info` with your token 3. **Wait** for `task_stream_ready` — the model is ready to receive audio 4. **Stream** microphone audio as binary frames 5. **Receive** AI audio as binary frames and play them 6. **End** the session with `task_session_end` ## Connection & Registration After running the task, connect to the WebSocket and register with `task_info` : ```javascript 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: 1. Request microphone with `getUserMedia` (enable echo cancellation and noise suppression) 2. Create an `AudioContext` at 24,000 Hz sample rate 3. Use an AudioWorklet to buffer and convert samples to Int16 4. Send each chunk as `tasktoken|pcm_data` binary frame ## Receiving AI Audio AI responses arrive as binary WebSocket frames in the same PCM Int16 24 kHz format. To play them: 1. Check if the message is a `Blob` (binary) before parsing as JSON 2. Find the pipe `|` separator and extract audio data after it 3. Convert Int16 → Float32 and create an `AudioBuffer` 4. 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 said - `TRANSCRIPT_AI:` — what the AI said ```json { "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`: ```json { "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. --- # 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 (letters, numbers, hyphens, underscores only) | | `parentid` | string | No | Parent folder ID for nested structure (omit for root) | ### Response ```json { "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. Max file size: 100 MB. | 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) | ### Response ```json { "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. ```json { "imageUrl": "https://files.wiro.ai/...", "scale": 4 } ``` --- # Pricing Understand how billing works for AI model runs on Wiro. ## Billing Methods Fixed-rate: `cpr` (per request), `cps` (per second), `cpo` (per output), `cpt` (per token). Usage-based: `cp-pixel` (per pixel tier), `cp-audiosecondslength` (per audio second), `cp-promptlength` (per character), `cp-outputVideoLength` (per video second). Special: `cp-realtimeturn` (per realtime voice turn), `cp-readoutput` (model-reported cost). Fallback: when no `dynamicprice`, cost = elapsed_seconds × `cps`. `approximatelycost` = average_elapsed_seconds × cps. ## Dynamic Pricing Many models have dynamic pricing — cost varies based on input parameters (e.g. resolution, duration). Returned in the `dynamicprice` field of Tool/List and Tool/Detail: ```json [{"inputs": {"resolution": "720p", "duration": "5"}, "price": 0.13, "priceMethod": "cpr"}] ``` Empty `inputs` (`{}`) = flat rate for all configurations. Specific `inputs` = price for that parameter combination only. ## What You Pay For Successful runs only (`pexit: "0"`). Cost recorded in `totalcost` field of Task/Detail. Server errors, queue time, and cancelled tasks are never billed. Pricing page: https://wiro.ai/product/pricing --- # Concurrency Limits Understand and manage how many requests you can run simultaneously on Wiro. ## 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**. | 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** | Formula: `max(1, floor(balance_usd * 0.10))`. Error code `96` is returned when the limit is reached. ### API Response When you hit the limit, the Run endpoint returns: ```json { "result": false, "errors": [{ "code": 96, "message": "You have reached your concurrent task limit..." }] } ``` | Code | Meaning | |------|---------| | `96` | Concurrent task limit reached | | `97` | Insufficient balance | --- # Error Reference Understand API error responses, error codes, and how to handle them. ## Error Codes | Code | Category | Description | |------|----------|-------------| | `0` | General | Server-side errors, validation failures, missing parameters | | `1` | Not Found | Resource not found or not accessible | | `96` | Concurrency Limit | Too many concurrent tasks for your balance | | `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 | ## Error Response Format ```json { "result": false, "errors": [{ "code": 97, "message": "Insufficient balance" }] } ``` All API responses return HTTP `200`. Auth errors return HTTP `401`. ## Run Errors (POST /Run) | Code | Message | Action | |------|---------|--------| | `96` | Concurrent task limit reached | Wait for task to finish or add funds | | `97` | Insufficient balance | Add funds ($0.50 min, $10 for training) | | `98` | Sign in required | Model requires registered account | | `0` | Parameter required/invalid | Fix request parameters | | `1` | Model not found/accessible | Check model slug | ## Task Errors (POST /Task) | Code | Message | Endpoint | |------|---------|----------| | `1` | Task not found | Detail, Cancel, Kill | | `1` | Not cancellable | Cancel | | `1` | Kill failed | Kill | | `0` | Missing identifier | Detail | --- # 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. - **curl** — Shell scripting with bash - **Python** — Using the `requests` library - **Node.js** — Using `axios` - **PHP** — Using cURL functions - **C#** — Using `HttpClient` (.NET 6+) - **Swift** — Using async/await `URLSession` - **Dart** — Using the `http` package - **Kotlin** — Using `java.net.http` - **Go** — Using the standard library `net/http` ## Full Examples All examples perform the same steps: 1. Set up authentication headers 2. Run a model (`POST /Run/{owner-slug}/{model-slug}`) 3. Poll the task status (`POST /Task/Detail`) 4. Print the final output ### Python ```python import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.wiro.ai/v1" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } # 1. Run a model print("Starting model run...") run_resp = requests.post( f"{BASE_URL}/Run/{owner-slug}/{model-slug}", headers=headers, json={ "prompt": "A cyberpunk cityscape at night", "width": 1024, "height": 1024 } ) run_data = run_resp.json() task_token = run_data["data"]["taskid"] print(f"Task ID: {task_token}") # 2. Poll for results while True: task_resp = requests.post( f"{BASE_URL}/Task/Detail", headers=headers, json={"tasktoken": task_token} ) task = task_resp.json()["data"] status = task["status"] print(f"Status: {status}") if status == "end": print("Done! Output:", task.get("output")) break elif status in ("error", "cancel"): print("Task failed or cancelled") break time.sleep(3) ``` ### Node.js ```javascript const axios = require("axios"); const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.wiro.ai/v1"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json", }; async function main() { // 1. Run a model console.log("Starting model run..."); const runResp = await axios.post( `${BASE_URL}/Run/{owner-slug}/{model-slug}`, { prompt: "A cyberpunk cityscape at night", width: 1024, height: 1024, }, { headers }, ); const taskToken = runResp.data.data.taskid; console.log("Task ID:", taskToken); // 2. Poll for results while (true) { const taskResp = await axios.post(`${BASE_URL}/Task/Detail`, { tasktoken: taskToken }, { headers }); const { status, output } = taskResp.data.data; console.log("Status:", status); if (status === "end") { console.log("Done! Output:", output); break; } if (status === "error" || status === "cancel") { console.log("Task failed or cancelled"); break; } await new Promise(r => setTimeout(r, 3000)); } } main(); ``` ### curl ```bash #!/bin/bash API_KEY="YOUR_API_KEY" BASE_URL="https://api.wiro.ai/v1" # 1. Run a model RUN_RESPONSE=$(curl -s -X POST "$BASE_URL/Run/{owner-slug}/{model-slug}" \ -H "Content-Type: application/json" \ -H "x-api-key: $API_KEY" \ -d '{"prompt": "A cyberpunk cityscape at night", "width": 1024, "height": 1024}') TASK_TOKEN=$(echo $RUN_RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['taskid'])") echo "Task ID: $TASK_TOKEN" # 2. Poll for results while true; do TASK_RESPONSE=$(curl -s -X POST "$BASE_URL/Task/Detail" \ -H "Content-Type: application/json" \ -H "x-api-key: $API_KEY" \ -d "{\"tasktoken\": \"$TASK_TOKEN\"}") STATUS=$(echo $TASK_RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['status'])") echo "Status: $STATUS" if [ "$STATUS" = "end" ]; then echo "Done!" echo $TASK_RESPONSE | python3 -m json.tool break elif [ "$STATUS" = "error" ] || [ "$STATUS" = "cancel" ]; then echo "Task failed or cancelled" break fi sleep 3 done ``` --- # MCP Server Connect AI coding assistants to Wiro's AI models via the Model Context Protocol. ## What is MCP? [Model Context Protocol](https://modelcontextprotocol.io/) (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. ## Setup ### Cursor Open MCP settings (`Cmd+Shift+P` → "Open MCP settings") and add: ```json { "mcpServers": { "wiro": { "url": "https://mcp.wiro.ai/v1", "headers": { "Authorization": "Bearer YOUR_API_KEY:YOUR_API_SECRET" } } } } ``` ### Claude Code ```bash claude mcp add --transport http wiro \ https://mcp.wiro.ai/v1 \ --header "Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET" ``` ## Authentication Signature-Based: `Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET` API Key Only: `Authorization: Bearer YOUR_API_KEY` ## Available Tools **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`. | Tool | Description | |------|-------------| | `search_models` | Search models by keyword, category, or owner | | `get_model_schema` | Get parameter schema and pricing for any model | | `recommend_model` | Describe a task, get model recommendations by relevance | | `explore` | Browse curated models by category | | `run_model` | Run any model, wait or get task token | | `get_task` | Check task status and outputs | | `get_task_price` | Get the cost of a completed task | | `cancel_task` | Cancel a queued task | | `kill_task` | Kill a running task | | `upload_file` | Upload a file from URL for use as model input | | `search_docs` | Search Wiro documentation | --- # Self-Hosted MCP Run the Wiro MCP server locally on your own machine using npx. ## Quick Start ```json { "mcpServers": { "wiro": { "command": "npx", "args": ["-y", "@wiro-ai/wiro-mcp"], "env": { "WIRO_API_KEY": "your-api-key", "WIRO_API_SECRET": "your-api-secret" } } } } ``` ## 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](https://github.com/wiroai/Wiro-MCP) - npm: [@wiro-ai/wiro-mcp](https://www.npmjs.com/package/@wiro-ai/wiro-mcp) --- # 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`](https://www.npmjs.com/package/@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, model discovery, execution, task polling, and file uploads. ## Installation ```bash npm install @wiro-ai/wiro-mcp ``` ## Quick Start ```javascript import { WiroClient } from '@wiro-ai/wiro-mcp/client'; const client = new WiroClient('YOUR_API_KEY', 'YOUR_API_SECRET'); const run = await client.runModel('google/nano-banana-pro', { prompt: 'A futuristic city at sunset', aspectRatio: '16:9', resolution: '2K' }); const result = await client.waitForTask(run.socketaccesstoken); const task = result.tasklist[0]; if (task.pexit === '0') { console.log('Output:', task.outputs[0].url); } ``` ## 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: 120s. | | `getTask({ tasktoken?, taskid? })` | Get current task status and outputs. | | `cancelTask(tasktoken)` | Cancel a queued task. | | `killTask(tasktoken)` | Kill a running task. | | `uploadFile(url, fileName?)` | Upload a file from URL for use as model input. | --- # FAQ Common questions about using the Wiro API. **How do I get an API key?** 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. **Which authentication method should I use?** Signature-Based is recommended for client-side apps. API Key Only is simpler for server-side. **Do I pay for failed tasks?** No. Only successfully completed tasks (pexit "0") are billed. **How do LLM responses work?** LLM models return their response in `outputs` (structured, with `contenttype: "raw"` containing `prompt`, `raw`, `thinking`, `answer`) and as merged text in `debugoutput`. **Can I send a URL instead of uploading a file?** Yes. Most models accept direct URLs in file parameters. For `combinefileinput`, pass an array of URLs directly. See Model Parameters. **Can I use a webhook instead of polling?** Yes. All models support an optional `callbackUrl` parameter. --- # n8n Wiro Integration Use all Wiro AI models directly in your n8n workflows — video, image, audio, LLM, 3D, and more. The **Wiro AI community node** (`@wiro-ai/n8n-nodes-wiroai`) gives you access to all Wiro AI models as individual nodes you can drag and drop into any workflow. ## Installation Install via n8n UI: **Settings → Community Nodes → Install → `@wiro-ai/n8n-nodes-wiroai`** Or via command line: `npm install @wiro-ai/n8n-nodes-wiroai` ## Links - [npm: @wiro-ai/n8n-nodes-wiroai](https://www.npmjs.com/package/@wiro-ai/n8n-nodes-wiroai) - [GitHub: wiroai/n8n-nodes-wiroai](https://github.com/wiroai/n8n-nodes-wiroai) --- # 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](#authentication). ## Agent Lifecycle Deploying and running an agent follows this flow: 1. **Browse** — call `POST /Agent/List` to discover available agents in the catalog 2. **Subscribe** — subscribe to a Starter or Pro plan using your prepaid wallet balance 3. **Deploy** — call `POST /UserAgent/Deploy` with the agent's guid and a title 4. **Configure** — if the agent requires credentials (API keys, OAuth tokens), call `POST /UserAgent/Update` to provide them. See [Agent Credentials](#agent-credentials) for details 5. **Start** — call `POST /UserAgent/Start` to queue the agent for launch 6. **Running** — the agent's container starts and the agent becomes available for conversation 7. **Chat** — send messages via `POST /UserAgent/Message/Send`. See [Agent Messaging](#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 **starting** (status `3`) or **running** (status `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 ### Browse the Catalog #### **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 ```json { "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"`) | > **Note:** You must provide either `guid` or `slug`. If both are provided, `slug` takes priority. ##### Response ```json { "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" } ] } ``` ### Deploy & Manage All endpoints below require authentication. #### **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. Requires `plan`. | | `plan` | string | Yes | Plan tier: `"starter"` or `"pro"`. Required when `useprepaid` is `true`. | | `pinned` | boolean | No | Whether the agent appears in the pinned agents list. Defaults to `true`. Set to `false` when deploying agents programmatically for end users (e.g. bulk provisioning). | ##### Response ```json { "result": true, "errors": [], "useragents": [ { "id": 47, "guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "agentid": 5, "title": "My Instagram Bot", "description": null, "configuration": { "credentials": { "instagram": { "_editable": { "authMethod": true }, "optional": false, "authMethod": "", "igUsername": "", "connectedAt": "" } } }, "status": 2, "createdat": "1714608000", "updatedat": "1714608000", "queuedat": "1714608000" } ] } ``` ##### Prepaid Deploy When `useprepaid` is `true`, the wallet is charged immediately. The response includes the created agent with status `6`: ```json // Request { "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "My Lead Gen Agent", "useprepaid": true, "plan": "starter", "pinned": false } // Response { "result": true, "errors": [], "useragents": [ { "guid": "new-guid-here", "title": "My Lead Gen Agent", "status": 6, "pinned": false, "setuprequired": true, "subscription": { "plan": "agent-starter", "status": "active", "amount": 49, "currency": "usd", "provider": "prepaid" } } ] } ``` > **Tip:** When deploying agents programmatically for your end users (e.g. one instance per customer), set `"pinned": false` to keep your own dashboard clean. Users can pin agents manually later. #### **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 ```json { "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 ```json { "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, or description. If the agent is currently running, 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 | | `categories` | array | No | Updated categories. Cannot be empty if provided. | | `configuration` | object | No | Updated credentials. Format: `{ "credentials": { "key": "value" } }` | > **Note:** If the agent's status is `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 ```json { "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 ```json { "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. No redirect needed. | ##### Response ```json // Request {"useragentGuid": "your-guid", "pack": "package2", "useprepaid": true} // Response {"result": true, "url": null, "errors": []} ``` When `result` is `true`, the credits are added immediately from your wallet balance. 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 ```json { "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 ```json { "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) ```json { "result": true, "action": "renewed", "plan": "agent-starter", "amount": 49, "errors": [] } ``` ##### Response (undo cancel) When called on an active subscription with a pending cancellation: ```json { "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 | ## Code Examples ### curl ```bash # List available agents (no auth required) curl -X POST "https://api.wiro.ai/v1/Agent/List" \ -H "Content-Type: application/json" \ -d '{"limit": 10}' # Get agent details by slug (no auth required) curl -X POST "https://api.wiro.ai/v1/Agent/Detail" \ -H "Content-Type: application/json" \ -d '{"slug": "instagram-manager"}' # Deploy a new agent instance (prepaid, pinned by default) curl -X POST "https://api.wiro.ai/v1/UserAgent/Deploy" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "My Instagram Bot", "useprepaid": true, "plan": "starter" }' # Deploy for an end user (unpinned, won't clutter your dashboard) curl -X POST "https://api.wiro.ai/v1/UserAgent/Deploy" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "Customer #1234 Bot", "useprepaid": true, "plan": "starter", "pinned": false }' # Cancel a subscription (cancels at end of billing period) curl -X POST "https://api.wiro.ai/v1/UserAgent/CancelSubscription" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321"}' # Upgrade starter to pro curl -X POST "https://api.wiro.ai/v1/UserAgent/UpgradePlan" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "plan": "pro"}' # Renew expired subscription curl -X POST "https://api.wiro.ai/v1/UserAgent/RenewSubscription" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321"}' # Buy extra credits with prepaid wallet curl -X POST "https://api.wiro.ai/v1/UserAgent/CreateExtraCreditCheckout" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"useragentGuid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "pack": "package1", "useprepaid": true}' # Start an agent curl -X POST "https://api.wiro.ai/v1/UserAgent/Start" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321"}' # Get agent instance details curl -X POST "https://api.wiro.ai/v1/UserAgent/Detail" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321"}' # Update credentials on a running agent (triggers automatic restart) curl -X POST "https://api.wiro.ai/v1/UserAgent/Update" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "configuration": { "credentials": { "instagram": { "authMethod": "wiro" } } } }' # Stop an agent curl -X POST "https://api.wiro.ai/v1/UserAgent/Stop" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"guid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321"}' ``` ### Python ```python import requests headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" } # List available agents (no auth required) catalog = requests.post( "https://api.wiro.ai/v1/Agent/List", json={"limit": 10} ).json() for agent in catalog["agents"]: print(f"{agent['title']} ({agent['slug']})") # Get agent details by slug detail = requests.post( "https://api.wiro.ai/v1/Agent/Detail", json={"slug": "instagram-manager"} ).json() agent = detail["agents"][0] print(f"Credentials needed: {list(agent['configuration']['credentials'].keys())}") # Deploy a new instance deploy = requests.post( "https://api.wiro.ai/v1/UserAgent/Deploy", headers=headers, json={ "agentguid": agent["guid"], "title": "My Instagram Bot", "useprepaid": True, "plan": "starter" } ).json() instance_guid = deploy["useragents"][0]["guid"] print(f"Deployed: {instance_guid}") # Update credentials requests.post( "https://api.wiro.ai/v1/UserAgent/Update", headers=headers, json={ "guid": instance_guid, "configuration": { "credentials": { "instagram": { "authMethod": "wiro" } } } } ) # Start the agent requests.post( "https://api.wiro.ai/v1/UserAgent/Start", headers=headers, json={"guid": instance_guid} ) # Check status import time while True: resp = requests.post( "https://api.wiro.ai/v1/UserAgent/Detail", headers=headers, json={"guid": instance_guid} ).json() status = resp["useragents"][0]["status"] print(f"Status: {status}") if status == 4: print("Agent is running!") break if status == 5: print("Agent errored") break time.sleep(5) # List your deployed agents my_agents = requests.post( "https://api.wiro.ai/v1/UserAgent/MyAgents", headers=headers, json={"limit": 50} ).json() for ua in my_agents["useragents"]: print(f"{ua['title']} - status: {ua['status']}") # Stop the agent requests.post( "https://api.wiro.ai/v1/UserAgent/Stop", headers=headers, json={"guid": instance_guid} ) ``` ### Node.js ```javascript const axios = require('axios'); const headers = { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; async function main() { // List available agents (no auth required) const catalog = await axios.post( 'https://api.wiro.ai/v1/Agent/List', { limit: 10 } ); catalog.data.agents.forEach(a => console.log(`${a.title} (${a.slug})`)); // Get agent details by slug const detail = await axios.post( 'https://api.wiro.ai/v1/Agent/Detail', { slug: 'instagram-manager' } ); const agent = detail.data.agents[0]; // Deploy a new instance const deploy = await axios.post( 'https://api.wiro.ai/v1/UserAgent/Deploy', { agentguid: agent.guid, title: 'My Instagram Bot', useprepaid: true, plan: 'starter' }, { headers } ); const instanceGuid = deploy.data.useragents[0].guid; console.log('Deployed:', instanceGuid); // Update credentials await axios.post( 'https://api.wiro.ai/v1/UserAgent/Update', { guid: instanceGuid, configuration: { credentials: { instagram: { authMethod: 'wiro' } } } }, { headers } ); // Start the agent await axios.post( 'https://api.wiro.ai/v1/UserAgent/Start', { guid: instanceGuid }, { headers } ); // Poll until running while (true) { const resp = await axios.post( 'https://api.wiro.ai/v1/UserAgent/Detail', { guid: instanceGuid }, { headers } ); const status = resp.data.useragents[0].status; console.log('Status:', status); if (status === 4) { console.log('Agent is running!'); break; } if (status === 5) { console.log('Agent errored'); break; } await new Promise(r => setTimeout(r, 5000)); } // Stop the agent await axios.post( 'https://api.wiro.ai/v1/UserAgent/Stop', { guid: instanceGuid }, { headers } ); } main(); ``` ### PHP ```php 10])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $catalog = json_decode(curl_exec($ch), true); curl_close($ch); // Deploy a new instance $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://api.wiro.ai/v1/UserAgent/Deploy"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "x-api-key: $apiKey" ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ "agentguid" => "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title" => "My Instagram Bot", "useprepaid" => true, "plan" => "starter" ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $deploy = json_decode(curl_exec($ch), true); curl_close($ch); echo "Deployed: " . $deploy["useragents"][0]["guid"]; ``` ### C# ```csharp using var client = new HttpClient(); client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY"); // Deploy a new instance var deployContent = new StringContent( JsonSerializer.Serialize(new { agentguid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890", title = "My Instagram Bot", useprepaid = true, plan = "starter" }), Encoding.UTF8, "application/json"); var deployResp = await client.PostAsync( "https://api.wiro.ai/v1/UserAgent/Deploy", deployContent); var deployResult = await deployResp.Content.ReadAsStringAsync(); Console.WriteLine(deployResult); // Start the agent var startContent = new StringContent( JsonSerializer.Serialize(new { guid = "f8e7d6c5-b4a3-2190-fedc-ba0987654321" }), Encoding.UTF8, "application/json"); var startResp = await client.PostAsync( "https://api.wiro.ai/v1/UserAgent/Start", startContent); Console.WriteLine(await startResp.Content.ReadAsStringAsync()); ``` ### Go ```go package main import ( "bytes" "encoding/json" "fmt" "net/http" "io" ) func main() { // Deploy a new instance body, _ := json.Marshal(map[string]interface{}{ "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "My Instagram Bot", "useprepaid": true, "plan": "starter", }) req, _ := http.NewRequest("POST", "https://api.wiro.ai/v1/UserAgent/Deploy", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-api-key", "YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) fmt.Println(string(data)) } ``` ### Swift ```swift import Foundation let url = URL(string: "https://api.wiro.ai/v1/UserAgent/Deploy")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("YOUR_API_KEY", forHTTPHeaderField: "x-api-key") request.httpBody = try! JSONSerialization.data( withJSONObject: [ "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "My Instagram Bot", "useprepaid": true, "plan": "starter" ]) let (data, _) = try await URLSession.shared .data(for: request) print(String(data: data, encoding: .utf8)!) ``` ### Kotlin ```kotlin import java.net.HttpURLConnection import java.net.URL val url = URL("https://api.wiro.ai/v1/UserAgent/Deploy") val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "POST" conn.setRequestProperty("Content-Type", "application/json") conn.setRequestProperty("x-api-key", "YOUR_API_KEY") conn.doOutput = true conn.outputStream.write("""{ "agentguid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "My Instagram Bot", "useprepaid": true, "plan": "starter" }""".toByteArray()) val response = conn.inputStream.bufferedReader().readText() println(response) ``` ### Dart ```dart import 'dart:convert'; import 'package:http/http.dart' as http; final response = await http.post( Uri.parse('https://api.wiro.ai/v1/UserAgent/Deploy'), headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY', }, body: jsonEncode({ 'agentguid': 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'title': 'My Instagram Bot', 'useprepaid': true, 'plan': 'starter', }), ); print(response.body); ``` ## What's Next - [Agent Messaging](#agent-messaging) — Send messages and receive responses from running agents - [Agent Credentials](#agent-credentials) — Configure OAuth and API key credentials for your agent - [Authentication](#authentication) — API key setup and authentication methods - [Pricing](#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](#run-a-model): 1. **Send** a message via REST → get an `agenttoken` immediately 2. **Subscribe** to [WebSocket](#agent-websocket) with the `agenttoken` → receive streaming response chunks 3. **Or poll** via the Detail endpoint to check status and fetch the completed response 4. **Or set** a `callbackurl` to 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: `agent_queue` → `agent_start` → `agent_output` → `agent_end` ### 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 ```json { "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). | > **Note:** You must provide at least one of `messageguid` or `agenttoken`. ### Response ```json { "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 ```json { "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" }, { "guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "content": "Tell me more about multimodal models", "response": "Multimodal models combine...", "debugoutput": "Multimodal models combine...", "status": "agent_end", "metadata": "{}", "createdat": "1743350300" } ], "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 To paginate through a long conversation: ``` // 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 ```json { "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 ```json { "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). | > **Note:** You must provide at least one of `messageguid` or `agenttoken`. ### Response ```json { "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 `sessionkey` represents 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 `sessionkey` to continue a conversation - Delete a session with `/UserAgent/Message/DeleteSession` to clear history and free resources ```json // 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 `