503 service_disabled. Watch this page.Pay-per-call media processing.
Convert a video with one HTTP call — supply a source URL, or upload the file directly. Payment is $0.05 per conversion in USDC over the x402 protocol. No account, no API key, no invoice.
# 1. ask for the job. no payment yet, nothing charged. $ curl -sS -D - -o /dev/null -X POST https://ffpipe.dev/v1/convert \ -H 'content-type: application/json' \ -d '{"sourceUrl":"https://storage.example.com/clip.mov"}' HTTP/2 402 payment-required: eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W3si… # $0.05 USDC · base # 2. your x402 client signs that challenge and retries the same request. $ curl -sS -X POST https://ffpipe.dev/v1/convert \ -H 'content-type: application/json' \ -H "payment-signature: $SIGNED_PAYLOAD" \ -d '{"sourceUrl":"https://storage.example.com/clip.mov"}' HTTP/2 202 {"jobId":"9f2c…","status":"queued","statusUrl":"https://ffpipe.dev/v1/jobs/9f2c…?t=…"} # 3. poll the statusUrl (free) until finished, then download resultUrl.
Machine-readable everything: openapi.json · llms.txt
Pricing
| Endpoint | Price | What you get |
|---|---|---|
| POST /v1/convert | $0.05 | One video conversion, scaled to fit 1920×1080 by default. Input ≤ 100 MB. |
| GET /v1/jobs/:id | free | Status polling, unmetered. Docs, health and this page are free too. |
Priced in USDC on base (eip155:8453), x402 exact scheme. Per job, no subscription, no minimum, no account. Failed jobs are refunded as a free retry — see the FAQ.
x402 flow
Ask
POST the endpoint with no payment header. You get
402and aPAYMENT-REQUIREDheader: base64 JSON naming the scheme, network, asset, amount and our address.Sign
Your wallet signs an EIP-3009
TransferWithAuthorizationfor exactly that amount. Nothing moves on-chain yet, and nothing is held.Retry with the signature
Send the identical request again with
payment-signature. Any x402 client library does steps 1–3 for you in one call.We verify, queue, then settle
The facilitator verifies the payment, we validate your input, stage your source and queue the job — and only then does the payment settle.
Poll and collect
202returns a tokenizedstatusUrl. Poll it at no charge untilfinished, then downloadresultUrlwithin 24 hours.
Code
# pay + submit (SIGNED_PAYLOAD comes from your x402 client / wallet) $ curl -sS -X POST https://ffpipe.dev/v1/convert \ -H 'content-type: application/json' \ -H "payment-signature: $SIGNED_PAYLOAD" \ -d '{"sourceUrl":"https://storage.example.com/clip.mov"}' # poll (free). keep the whole statusUrl: the ?t= token is your receipt. $ curl -sS "https://ffpipe.dev/v1/jobs/JOB_ID?t=TOKEN" # if we failed the job, its status carries a retryVoucher — spend it free: $ curl -sS -X POST https://ffpipe.dev/v1/convert \ -H 'content-type: application/json' \ -H "x-retry-voucher: UUID.HMAC" \ -d '{"sourceUrl":"https://storage.example.com/clip.mov"}'
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const pay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});
// one call: 402 challenge, signature and retry all happen inside
const res = await pay("https://ffpipe.dev/v1/convert", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ sourceUrl, maxWidth: 1280, maxHeight: 720 }),
});
const { statusUrl } = await res.json();
let job;
do {
await new Promise((r) => setTimeout(r, 3000));
job = await (await fetch(statusUrl)).json(); // free
} while (job.status === "queued");
if (job.status === "finished") console.log(job.resultUrl);
else if (job.retryVoucher) /* free retry: send x-retry-voucher */;
// remote MCP server at https://ffpipe.dev/mcp (streamable HTTP, no session needed) // tools: convert_video ($0.05) · get_job_status (free) import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { withX402Client } from "agents/x402"; import { privateKeyToAccount } from "viem/accounts"; const client = new Client({ name: "my-agent", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL("https://ffpipe.dev/mcp"))); // the x402 challenge, signature and paid retry all happen inside callTool const paid = withX402Client(client, { account: privateKeyToAccount(process.env.PRIVATE_KEY), network: "eip155:8453", }); const res = await paid.callTool(null, { name: "convert_video", arguments: { sourceUrl, maxWidth: 1280, maxHeight: 720 }, }); // → { jobId, statusUrl } — poll get_job_status(jobId, token) for free
FAQ
What happens if a job fails?
You get an automatic retry voucher. We charge when we accept a job, so if processing then fails, the failed job's status response carries retryVoucher. Send it back as x-retry-voucher and the retry is free — no payment, no signature. Single use, same endpoint kind, chained up to 3 retries per job. It's the refund; there are no on-chain refunds at these prices. Failures that happen before acceptance (bad body, unfetchable or oversized source) return a non-2xx and are never charged in the first place.
How long do results last?
Download your result within 24 hours of completion. resultUrl and thumbnailUrl are served by this gateway and are re-downloadable at no charge within that window. ffpipe is a processing service, not storage — retrieve what you need promptly.
Which URLs can I send?
Any https URL we can fetch, up to 100 MB. We fetch from datacenter IP ranges, so hosts that block datacenter traffic answer 403 and you get ingest_failed — prefer a presigned URL from storage you control. No IP literals, no internal hostnames, at most 3 redirects. Or upload bytes directly — no hosting needed: send {"upload": true} instead of a sourceUrl and PUT your file to the uploadUrl in the 202 (you have an hour by default; the details are in the spec).
Do I need an account or an API key?
No. The payment is the authorization. Bring a wallet with USDC on base and an x402 client; nothing else exists to sign up for.
How long does a job take?
Jobs are asynchronous — typically a few minutes for a short clip. There is no SLA. If no completion arrives within 6 hours the job is marked error; contact us with the job id if that happens.