Pay per call
The keyless, pay-per-call way for agents to use CueFrame — over MCP (recommended) or REST. No account, no API key.
The usual way in — MCP, CLI, REST — assumes a human signed in once and minted a key. Pay-per-call doesn't. An agent pays per request with a wallet signature: no signup, no key, no account to provision. The payment is the request.
It's built on x402 — the HTTP 402 Payment Required
standard. For agents, the best way in is MCP: you get CueFrame's typed tools
plus discovery plus per-call payment in one session, and your MCP client handles
the pay-and-retry for you. There's also a plain REST path if you
prefer raw HTTP. Same wallet, same USDC-on-Base payment either way.
Recommended: over MCP
Point an x402-aware MCP client at the open transport:
https://api.cueframe.ai/v1/mcp/x402Connecting and listing tools is free — no bearer, no signup. When you call a paid
tool, it returns a payment-required result carrying the price quote
(accepts[]); your client signs the payment, puts it in the tool call's
_meta["x402/payment"], and retries. Libraries like @x402/mcp's client wrap
all of that, so you just call the tool:
import { wrapMCPClientWithPaymentFromConfig } from "@x402/mcp";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
// Your agent's wallet — the only credential it needs. Fund it with USDC on Base.
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);
// Wrap an MCP client pointed at /v1/mcp/x402; paid tools sign + retry automatically.
const paid = wrapMCPClientWithPaymentFromConfig(mcpClient, {
schemes: [{ network: "eip155:*", client: new ExactEvmScheme(account) }],
});
// No key, no signup — the wallet pays per tool call.
await paid.callTool("compose", { projectId });Why MCP is the recommended path: the tools are typed and self-describing, so
an agent discovers what to call from tools/list instead of hand-building HTTP
requests; discovery, payment, and the call all live in one session; and the
client library turns a payment-required into a signed retry automatically. It's
the most agent-native way to drive CueFrame.
(The key-authenticated MCP endpoint /v1/mcp — browser login or a cf_* key —
is unchanged; reach for /v1/mcp/x402 when you want keyless pay-per-call.)
What you pay
In USDC — the same prices whichever path you use. Reads (list, get, poll a job)
are $0.01.
Work is measured: you pay for what a call actually consumed, not a flat fee per call. A ten-second clip does not cost what a ten-minute 4K export costs.
| Call | Endpoint / tool | Price |
|---|---|---|
| Import footage | import_media · POST /v1/media | $0.01 |
| Detect faces + transcript | POST /v1/media/:id/detect-subjects (REST) | $0.10 |
| Suggest clips | suggest_briefs · POST /v1/media/:id/suggestions | measured — the run's LLM spend |
| Compose a video (Director) | compose · POST /v1/projects/:id/compose | measured — the run's real spend |
| Render to MP4 | create_render · POST /v1/projects/:id/renders | $0.50 per output-minute (1080p; 4K ×2, 720p ×½) |
| Generate media | generate_media · POST /v1/media/generate | measured — the model's cost |
| Score a composition | score_composition · POST /v1/projects/:id/score-composition | measured — the judge pass's spend |
| Transcription | folded into ingest | $0.03 per audio-minute |
| Reads (list / get / poll) | list_media, … · GET /v1/… | $0.01 |
How a measured call is quoted and settled
For the measured routes the payment-required carries a ceiling, not a final
price — the scheme is upto. You authorize at most that much; we settle what the
work actually cost, which is normally less.
402 payment-required → accepts[].amount = the CEILING you authorize
work runs, we measure → settle = the ACTUAL, never moreSo a client must never hardcode a price, and should not assume it will be charged
the quoted amount either — read accepts[].amount, authorize it, and expect
the settled figure to be lower. GET /v1/usage lists every call with what it
measured and what it cost.
You pay for delivered work
Async calls — compose, render, import — are charged on delivery, not on enqueue. If the job fails, you aren't charged: the payment releases and nothing settles on-chain. You only pay when the asset is actually produced.
Also on REST
Prefer raw HTTP? The same payment works directly on the REST API — hit a priced
endpoint, get a 402, sign, and retry with a PAYMENT-SIGNATURE header:
your agent ──▶ POST /v1/projects/:id/renders
◀── 402 { accepts: [ { asset: USDC, amount, payTo } ] }
your agent ──▶ POST … + PAYMENT-SIGNATURE (signed USDC transfer)
◀── 200 { render job }import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);
const pay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:*", client: new ExactEvmScheme(account) }],
});
const res = await pay("https://api.cueframe.ai/v1/projects/PROJECT_ID/renders", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ /* … */ }),
});Same wallet, same prices as the table above — only the carrier differs (an HTTP
header instead of a JSON-RPC _meta field). Everything else is the plain REST
API reference.
Long-running calls
Compose and render return a job. Poll it or register a webhook — exactly like every other surface. See the Quickstart for the ingest → compose → render loop, which works identically whether you authenticate with a key or pay per call.
Quickstart
Intent to rendered video in three steps over MCP.
Analyse a media item's transcript POST
Ask ONE editorial question of a transcript and get the answer as text: `summary` (what is actually said, plus the takeaways), `chapters` (where the subject changes), or `highlights` (the moments that stand alone as a short). PRECISION: timestamps are ABSOLUTE seconds from the start of the media and every one is verified against the transcript before you see it (a fabricated timestamp fails the call rather than shipping) — but they are LINE-START markers roughly 15s apart, so a boundary can sit up to 15s BEFORE the moment you asked for. Treat every span as APPROXIMATE: use it to LOCATE the moment, then refine the in/out against the word timings from GET /v1/media/{id}/context before you cut. Feeding a raw span straight into a clip trim will start it mid-sentence. Pass `window` to analyse one slice; omit it for the whole thing. This is a METERED LLM call, not a read: it bills on measured token spend, and a recording too long to analyse in one pass is rejected with 422 `transcript_too_long` (over ~100k characters, roughly two hours of speech) rather than silently summarising only its first half — narrow it with `window`. 422 `media_no_transcript` means the item has no speech on file (or none inside your window); GET /v1/media/{id}/context shows transcript state, and GET /v1/media/{id}/transcript returns the raw words.