MCP Server
Xora ships a remote MCP server at
https://api.xora.sh/v1/mcp (Streamable HTTP). It authenticates with the same
API keys as the REST API and its tools call the same job routes — plan limits,
billing, and structured errors behave identically whether a job comes from
curl or from an agent’s tool call.
Connect
Section titled “Connect”Two ways to authenticate:
- OAuth — clients with OAuth support (claude.ai custom connectors, Claude Desktop connectors) discover the flow automatically: add the endpoint URL, sign in with your Xora dashboard account, and approve the consent prompt.
- API key — an
Authorization: Bearerheader carrying a Xora API key (create one in the dashboard), for headless agents, CI, and any client that can set a request header.
Settings → Connectors → Add custom connector, then paste:
https://api.xora.sh/v1/mcpThe OAuth flow starts automatically: you’ll sign in to your Xora dashboard (if you aren’t already) and approve the access prompt. No key to copy. Revoke any time from the consent screen’s dashboard link.
claude mcp add --transport http xora https://api.xora.sh/v1/mcp \ --header "Authorization: Bearer YOUR_API_KEY"Add to ~/.cursor/mcp.json (or .cursor/mcp.json in your project):
{ "mcpServers": { "xora": { "url": "https://api.xora.sh/v1/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } }}import { Client } from '@modelcontextprotocol/sdk/client/index.js';import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const transport = new StreamableHTTPClientTransport( new URL('https://api.xora.sh/v1/mcp'), { requestInit: { headers: { authorization: `Bearer ${process.env.XORA_API_KEY}` } } });const client = new Client({ name: 'my-agent', version: '1.0.0' });await client.connect(transport);
const { tools } = await client.listTools();// create_upload, create_job, get_job, list_recipes, cancel_jobconsole.log(tools.map((t) => t.name));To verify a connection without writing code:
npx @modelcontextprotocol/inspector --cli https://api.xora.sh/v1/mcp \ --transport http \ --header "Authorization: Bearer YOUR_API_KEY" \ --method tools/list| Tool | What it does |
|---|---|
create_job | Create an asynchronous job — mode: "recipe" (named operation), mode: "ffmpeg" (raw argument array with {{in_1}}/{{out_1}} placeholders), or mode: "probe" (ffprobe metadata only). Returns { id, state } immediately. |
get_job | Fetch a job by id: state, progress, signed download URLs when completed, probe metadata for probe jobs, structured error for failed or rejected jobs. |
create_upload | Get a presigned URL for a local file that has no public address. Returns { uploadId, url, expiresAt, maxBytes }; the agent PUTs the bytes, then passes uploadId to create_job. |
list_recipes | Static catalog of the nine recipes with parameters, constraints, and output formats. Costs nothing to call. |
cancel_job | Cancel a queued or running job. |
The server also publishes instructions during the MCP handshake, so most agents
discover the workflow on their own: call list_recipes once, create_job,
then poll get_job until the state is terminal
(completed | failed | rejected | cancelled).
Working with local files
Section titled “Working with local files”create_job needs an input Xora can read. When the agent is holding a file
path rather than a URL, create_upload provides the destination:
- Call
create_uploadwith{ "sizeBytes": 734003200, "filename": "interview.mov" }. The declared size is checked against the plan’s per-file limit up front. PUTthe raw file bytes to the returnedurl— a plain HTTP request the agent makes itself, with noAuthorizationheader and no form encoding. The URL is valid for one hour.- Call
create_jobwith"input": { "uploadId": "…" }in place of"input": { "url": … }.
Uploads survive for three days and can back any number of jobs in that window, so an agent producing several outputs from one source should upload once and reuse the id. See direct uploads for limits and the REST equivalent.
The agent loop
Section titled “The agent loop”A typical create_job tool call:
{ "mode": "recipe", "input": { "url": "https://example.com/interview.mov" }, "output": { "format": "mp4" }, "recipe": { "name": "compress", "crf": 23 }}…returns { "id": "01JXYZ…", "state": "queued" }. Polling get_job with that
id eventually returns the terminal record:
{ "id": "01JXYZ1234ABCDEF56789000", "state": "completed", "progress": 100, "output": { "signedUrl": "https://cdn.xora.sh/...signed-url..." }}Signed URLs expire, so agents should download promptly (or configure
BYOB delivery so outputs land in your own
bucket with nothing to collect). Pass webhookUrl on create_job to receive a
webhook on the terminal state instead of polling.
Errors agents can branch on
Section titled “Errors agents can branch on”Tool failures come back as MCP error results whose text is the API’s structured error JSON, plus the HTTP status:
{ "httpStatus": 402, "error": { "code": "LIMIT_EXCEEDED", "message": "Plan credits exhausted", "feature": "credits", "retryable": false }}error.code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | The request body is wrong — the message says which field. |
LIMIT_EXCEEDED | 402 | Plan credits or storage exhausted; new jobs are refused until reset or upgrade. |
PLAN_REQUIRED | 403 | The feature (for example BYOB) isn’t on the current plan. |
NOT_FOUND | 404 | No job with that id on this account. |
Failed and rejected jobs bill nothing — see error handling for the difference between the two.
The Xora skill
Section titled “The Xora skill”For Claude specifically, there’s also an Agent Skill
— judgment the tool schemas can’t carry: which recipe fits which ask, when to
probe first, how to handle a local file, cost-saving habits like
stageInput: false, and how to branch on error codes. It complements the MCP
server (and covers plain REST when MCP isn’t connected).
Install it for Claude Code:
mkdir -p ~/.claude/skills/xora && \ curl -fsSL https://xora.sh/skills/xora/SKILL.md -o ~/.claude/skills/xora/SKILL.mdUse .claude/skills/xora/SKILL.md inside a repo to share it with a project
instead, or upload it as a custom skill in claude.ai.