Beta. The Video API is in beta. Rates below are the current beta rate card and may be adjusted during the beta with notice; the mechanics (integer seconds, no multipliers, $0 rejects) are the contract.
The formula
Jobs run on shared
cpu infrastructure; a legacy tier field is still accepted on requests but never needs to be sent.
- Only ffmpeg wall time is metered for compute.
execution_wall_secondsis the time the media command actually runs. The time spent on input staging, queueing, and artifact upload is never metered. This is whymetrics.predict_time— the prediction’s full wall time, which includes those phases plus the platform’s ~5 s status-check quantization — is not the billed quantity and essentially always differs from it (it can even read lower than the billed seconds when a short job bills the 5 s floor). - 200 MB of output delivery included. Every job includes the first 0.2 GB of delivered output free; only the excess bills, at $0.30/GB. An output of exactly 0.2 GB is inside the allowance. Most capabilities emit far less than 200 MB and never pay a delivery cent, and the analysis capabilities (
probe,scene_detect,silence_detect,audio_analysis) produce no artifact at all — the delivery term only bites on genuinely large outputs. - No multipliers. Resolution, codec, container, and input duration never multiply the price — you pay for execution seconds, plus delivery only beyond the included 200 MB.
- Integer seconds, floored and capped. Wall time is rounded up to whole seconds, floored at 5 seconds (a 1.2 s job bills 5 s), and hard-capped at 3600.
- Reproduce your bill from the response. On video-api predictions,
metricscarries the meter fields —tier,billed_seconds, andoutput_gb— alongsidecostandpredict_time.metrics.costis the authoritative charge, and on a success it always equals the formula applied to those two meter fields. Failed predictions carry onlycostandpredict_time— the meter fields are absent even on the classes the engine meters (exec_error,billed_seconds_cap), so reproduce a bill from asuccessonly. - Worked example. A cpu job whose ffmpeg run takes 190.3 s bills
ceil(190.3) = 191seconds; say it delivers a 1.404 GB output. Cost =191 × 0.0015 + (1.404 - 0.2) × 0.30=0.2865 + 0.3612= **0 and it would bill $0.2865 flat. (predict_timemight read194.86— the extra ~4 s is queueing and poll quantization on top of the metered 191 s.)
When you pay nothing — guaranteed $0
Failed compute
A failure caused by your input after compute starts is metered by the engine. Metering records what the engine measured; settlement is what you are charged — during the beta, failed compute meters normally but settles atmetrics.cost: 0. Two failure cases work this way:
Beta settlement. Today, every prediction that ends in any status other than
success settles with metrics.cost: 0 — including exec_error, billed_seconds_cap, and cancelled (verified against production; see the captured exec_error example below). The Engine meters column is what the engine records for these outcomes and the reserved charge basis for them; if charging failed compute activates during the beta it will be announced with notice, per the note at the top of this page. Do not design flows that rely on failed compute staying free — the cap kill in particular is designed to bill exactly 3600 s.Error catalog
Every failure surfaces on the polled prediction asstatus: "error" — never "failed" — and output carries the error envelope; metrics.cost carries what it billed. Submitting a prediction essentially always succeeds with a prediction ID: errors arrive when you poll.
The error envelope
Onstatus: "error", output is an object with these fields (captured from production below) — except on a dispatch failure at submit, which writes a minimal envelope carrying only error, set to the dispatch error text:
A real $0 validation reject, captured from production, exactly as polled:
exec_error — a crop window larger than the source frame, which passes the probe gate and fails in ffmpeg), captured from production; note the failure class arrives in error_message, the code is PROVIDER_FAILED, and the beta settlement is cost: 0:
The public error codes
Engine submit rejects — synchronous, all $0
The engine validates every submit synchronously. The platform submits on your behalf, so you see these witherror_step: "Submit Job" and the engine’s body {code, message, retryable, stage} returned verbatim as a JSON string in error_message (verified against production — e.g. a tier: "gpu" request polls with error_message: {"code":"invalid_command","message":"tier: must be one of cpu","retryable":false,"stage":"validate"}). An engine 401 or 403 surfaces as PROVIDER_AUTH_ERROR, a 429 as PROVIDER_RATE_LIMIT, a 502 as PROVIDER_SERVER_ERROR, and every other 4xx (400, 404, 409) as PROVIDER_BAD_REQUEST. Submit-stage rejects additionally carry diagnostic fields (http_status, root_cause, causes, timing) not present on post-accept failures.
Failure classes after the engine accepts a job — asynchronous, at poll time
platform_fault is the one class where retrying the same request can succeed, but nothing in the envelope tells you that: the engine marks the class retryable internally and that flag is not carried through, so every failure class in this table reaches you with retryable: false. Read the class from error_message. Every other failure class is deterministic for the same request; the load-dependent throttling codes (PROVIDER_RATE_LIMIT, rate_limited) are the carve-out — see Retries.
platform_drift, defined
platform_drift is an engine-side delivery defect detected after a run: the job’s compute finished, but what the engine needed to assemble your result is missing or malformed. The causes are internal and deterministic — the same request will drift again, so the class is $0 and non-retryable:
- the metadata sidecar an analysis run must produce is absent, or parses to zero events (ffmpeg print-format drift);
- a
silence_splitplan whose split stage lost its computed cut times; - a
segmentfirst_framesplit that produced more than 999 clips (the numbered names stop pairing with their stills — raisesegment_seconds).
platform_fault, which is transient and safe to retry as-is.
Cancelling a prediction
Cancel withPOST https://api.eachlabs.ai/v1/prediction/{id}/cancel (Authorization: Bearer header) — the exact URL is returned on every poll as urls.cancel. A cancel is accepted while the prediction has not reached a terminal status and answers {"status": "success", "message": "Prediction canceled successfully", ...}; the prediction then polls as cancelled with metrics.cost: 0 — a cancelled prediction never bills (verified against production).
Rate limits & concurrency
- No per-organization rate limit on submits today (beta).
POST /v1/predictionis not throttled per key or per org. - One global engine submit bucket — 50 requests/s, burst 100, shared across all organizations — protects the engine. You never see it as an HTTP 429: the platform absorbs it and retries the submit internally (3 attempts, 2 s base backoff). A persistently throttled submit surfaces as the prediction erroring with
PROVIDER_RATE_LIMITat $0. - In-flight execution caps exist per provider on the platform side. A job held back by a cap is re-enqueued with backoff — you observe a prolonged
createdstatus, not an error. If the platform exhausts its re-enqueue budget, the prediction errors withPROVIDER_RATE_LIMITat $0. - Worker concurrency is bounded platform-wide (32 concurrent job actions per worker); bursts queue rather than fail — a 100-job burst of short transcodes drains in ~15-30 s.
Limits
Retries — read before automating
A retried submit creates a second, separately billed job — the prediction API does not deduplicate submits today. Submit once, persist the returnedpredictionID, and drive all retry logic through polling that ID. Gateway-level idempotency keys are on the beta roadmap.
For a failed prediction, three rules cover every class. Apply them in order: the retryable field alone is not sufficient, because the engine’s one transient class arrives marked retryable: false.
- Retry the same request once for any post-accept
PROVIDER_FAILED. Platform faults — $0, and the only transient class after accept — hide inside this code, and no envelope field isolates them:retryablereadsfalse, and the failure class reaches you only througherror_message, which carries the engine’s own reason where it has one (execution_deadline_exceededfor a job its runtime deadline cut off) and the class name (platform_fault) otherwise. Do not test for a fixed string. One retry settles it — a platform fault usually clears, anything else fails the same way — and it is free today, because every post-accept failure settles atmetrics.cost: 0during the beta. A submit-timeplatform_faultis different: it is an engine 502 and reaches you asPROVIDER_SERVER_ERROR, under the next rule. - Resubmit after backoff for anything the envelope marks
retryable: true— atruemarks a load-dependent failure rather than a request defect, so a later resubmit can succeed. Branch on the field itself, never on a set of codes you maintain: the value is not a lookup onerror_code, and which codes carrytrueis not part of the contract. - Everything else — every code left after the two rules above, plus any
PROVIDER_FAILEDthat failed identically on its one retry — is deterministic and will fail again: fix the request or the input instead of retrying.platform_driftalways ends here.
Raw ffmpeg (run_ffmpeg)
Beyond the named capabilities there is a raw-ffmpeg mode for long-tail cases, available to beta organizations today. The full contract — parameter table, output_format allowlist, placeholder binding, and the enforced safety boundary — lives in the capabilities reference. Every run_ffmpeg job bills under the same formula as any capability. Access may move to per-key grants later in the beta.
Policies: Acceptable Use · Abuse & Takedown · Versioning & Deprecation