Node.js Library
Use Wiro AI models directly in your Node.js or TypeScript projects with a simple API client.
Overview
The @wiro-ai/wiro-mcp package exports a WiroClient class that you can use as a standalone API client, with no MCP setup required. It handles authentication (both signature-based and API key only), model discovery, execution, task polling, and file uploads.
Links
Installation
npm install @wiro-ai/wiro-mcp
Requires Node.js 20 or later.
Quick Start
import { WiroClient } from '@wiro-ai/wiro-mcp/client';
const client = new WiroClient('YOUR_API_KEY', 'YOUR_API_SECRET');
// Run an image generation model
const run = await client.runModel('google/nano-banana-pro', {
prompt: 'A futuristic city at sunset',
aspectRatio: '16:9',
resolution: '2K'
});
if (!run.result) {
console.log('Run failed:', run.errors);
process.exit(1);
}
// Wait for the result (polls Task/Detail until complete)
const result = await client.waitForTask(run.socketaccesstoken);
const task = result.tasklist[0];
if (task.pexit === '0') {
console.log('Output:', task.outputs[0].url);
} else {
console.log('Failed:', task.pexit);
}
Authentication
The client supports both Wiro authentication methods:
Signature-Based (Recommended)
Provide both API key and secret. HMAC-SHA256 signatures are generated automatically per request.
const client = new WiroClient('your-api-key', 'your-api-secret');
API Key Only
Omit the secret for simpler server-side usage.
const client = new WiroClient('your-api-key');
Custom Base URL
Override the API endpoint if needed (third parameter):
const client = new WiroClient('key', 'secret', 'https://custom-api.example.com/v1');
Your Own Request Headers
To send headers of your own with every API request, pass requestHeaders in the options (fourth parameter):
const client = new WiroClient('your-api-key', 'your-api-secret', undefined, {
requestHeaders: () => ({ 'x-app-name': 'my-app' })
});
The function runs for every request the client sends, JSON and multipart, and gets one object, { pathname, apiKey }, where pathname includes the base path, such as /v1/Tool/List. It returns a plain object of string values, and those headers are sent after the client's own. It cannot set x-api-key, x-signature, x-nonce or Content-Type, in any letter case: such a request fails before it is sent. Type it with RequestHeadersHook. This option replaces trustedProxy, which was removed in 1.5.0: a client created with trustedProxy throws.
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, signal?) |
Run a model. Returns task ID and socket access token. |
waitForTask(tasktokenOrReference, timeoutMs?, options?) |
Poll until completion. Default timeout: 120 seconds. Supports AbortSignal, stepped polling, progress callbacks, and transient retries. |
getTask({ tasktoken?, taskid? }, signal?) |
Get current task status and outputs. |
cancelTask(tasktokenOrReference, signal?) |
Cancel a queued task. Resolves a token to the task ID required by the API. |
killTask(tasktokenOrReference, signal?) |
Kill a running task using its socket access token or task ID. |
listTasks(params?, signal?) |
List the tasks of the key's workspace, newest first. |
uploadFile(url, fileName?) |
Upload a file from a URL for use as model input. |
post(path, body?, signal?) |
Call any other Wiro API endpoint with the same credentials, for example post('/Workflow/List', { scope: 'my' }). Wiro reports each outcome in the response body, so check result on the returned body. |
postForm(path, form, signal?) |
Same as post, for endpoints that take multipart/form-data. |
isOwnApiKey(value) |
Says whether a value is this client's API key, without handing the key out. |
If waitForTask exceeds its timeout, it throws TaskWaitTimeoutError and preserves lastDetail. Call it again with the same token or task ID; do not call runModel again for the same generation.
Examples
Search Models
const models = await client.searchModels({
search: 'image generation',
categories: ['text-to-image'],
limit: 5
});
for (const model of models.tool) {
console.log(`${model.cleanslugowner}/${model.cleanslugproject} — ${model.title}`);
}
Get Model Parameters
const detail = await client.getModelSchema('google/nano-banana-pro');
const model = detail.tool[0];
if (model.parameters) {
console.log('Parameters:');
for (const group of model.parameters) {
for (const param of group.items) {
console.log(` ${param.id} (${param.type}) ${param.required ? '— required' : ''}`);
}
}
}
Run an LLM
const run = await client.runModel('openai/gpt-5-2', {
prompt: 'Explain quantum computing in 3 sentences'
});
const result = await client.waitForTask(run.socketaccesstoken);
const task = result.tasklist[0];
if (task.pexit === '0') {
// Merged text
console.log(task.debugoutput);
// Structured thinking/answer
const output = task.outputs[0];
if (output.contenttype === 'raw') {
console.log('Thinking:', output.content.thinking);
console.log('Answer:', output.content.answer);
}
}
Upload a File and Use It
Tip: Most models take a public URL instead of an upload. For most image and video inputs you pass it in the field itself, such as inputImage: 'https://example.com/photo.jpg'; a fileinput takes it in its {id}Url field (such as inputImageUrl), so check the model's parameter types. Use uploadFile() when you need to reuse files across multiple runs.
// Upload an image
const upload = await client.uploadFile('https://example.com/photo.jpg');
const fileUrl = upload.list[0].url;
// Use it in a model run
const run = await client.runModel('wiro/virtual-try-on', {
inputImageHuman: fileUrl,
inputImageClothes: 'https://example.com/shirt.jpg'
});
const result = await client.waitForTask(run.socketaccesstoken);
console.log('Output:', result.tasklist[0].outputs[0].url);
Poll Manually
const run = await client.runModel('klingai/kling-v3', {
prompt: 'A drone shot over mountains',
duration: '5',
aspectRatio: '16:9'
});
// Poll manually instead of using waitForTask
const interval = setInterval(async () => {
const detail = await client.getTask({ tasktoken: run.socketaccesstoken });
const task = detail.tasklist[0];
console.log('Status:', task.status);
if (task.status === 'task_postprocess_end') {
clearInterval(interval);
if (task.pexit === '0') {
console.log('Video:', task.outputs[0].url);
}
}
}, 5000);
TypeScript
All types are exported from the package:
import { WiroClient } from '@wiro-ai/wiro-mcp/client';
import type {
RunModelResult,
TaskDetailResponse,
Task,
TaskOutput,
ToolListResponse,
ToolDetailResponse,
ToolListItem,
SearchModelsParams,
TaskReference,
WaitForTaskOptions,
RequestHeadersHook,
} from '@wiro-ai/wiro-mcp/client';
import {
TaskPollingError,
TaskWaitTimeoutError,
} from '@wiro-ai/wiro-mcp/client';
| Type | Description |
|---|---|
RunModelResult |
Response from runModel(): taskid, socketaccesstoken. |
TaskDetailResponse |
Response from getTask() / waitForTask(): contains tasklist array. |
Task |
Individual task object: status, pexit, outputs, debugoutput, etc. |
TaskOutput |
Output entry: file (name, url) or LLM (contenttype: "raw", content). |
ToolListResponse |
Response from searchModels(): contains tool array. |
ToolDetailResponse |
Response from getModelSchema(): contains model with parameters. |
SearchModelsParams |
Search parameters: search, categories, slugowner, limit, etc. |
TaskReference |
Existing task identifier: tasktoken or taskid. |
WaitForTaskOptions |
Polling options: signal, onPoll, pollIntervalMs, and retry limit. |
RequestHeadersHook |
The requestHeaders option: gets { pathname, apiKey } and returns the headers to add. |
TaskWaitTimeoutError |
Bounded wait expired; carries the last Task/Detail response. |
TaskPollingError |
Repeated or permanent status lookup failure; also carries the last response. |