Portal
Documentation: all sections

Video

Sable Video renders text-to-video and image-to-video jobs through the same key, the same balance, the same budgets, and the same signed receipts as every other billable call.

It is asynchronous, and that is the one thing to internalise before writing any code against it. A render takes roughly 30 seconds to 5 minutes. Unlike image generation, which hands you the picture on the response, video returns a job id immediately and you poll for the result.

Availability

Video is off by default and config-gated. It is live only on a deployment where an operator has set SABLE_VIDEO_PROVIDERS and configured that provider's credentials. No video provider is enabled on the production deployment today. Where it is not enabled, the model list comes back empty and a generation call returns a configuration error rather than a video.

GET /v1/videos/models is public and is the authoritative check for what, if anything, is enabled here. An empty data array means video is not available on this deployment.

# Public: what video models are enabled here (empty when off)
curl https://api.buildsable.com/v1/videos/models
{
  "object": "list",
  "data": [
    {
      "id": "sable-video",
      "kind": "video",
      "mode": "text-to-video",
      "price_usd_per_second": 0.4,
      "max_duration_secs": 10,
      "default_duration_secs": 5,
      "default_resolution": "1080p",
      "durations": [3, 5, 8, 10],
      "resolutions": ["720p", "1080p"],
      "aspect_ratios": ["16:9", "9:16", "1:1"]
    }
  ]
}

The job lifecycle

StepCallWhat happens
1POST /v1/videos/generationsThe render is handed to the provider synchronously, so a bad request fails immediately. Returns 202 with a job id.
2GET /v1/videos/generations/:idPoll. status and progress advance.
3GET /v1/videos/generations/:id/contentOnce status is succeeded, download the bytes.
POST /v1/videos/generations/:id/cancelBest-effort stop.
DELETE /v1/videos/generations/:idDestroy the stored video now rather than at its TTL.

Status vocabulary

Fixed and exhaustive — you may switch on it:

StatusMeaningBilled?
queuedAccepted; the provider has not started.No
runningRendering. progress is whole percent, 0100.No
succeededDone. asset_available is true and /content serves the bytes.Yes
failedThe render failed, or the job passed its deadline. error_class says which.No
canceledYou cancelled it before the provider produced anything.No
expiredIt succeeded, and the stored video has since passed its TTL and been destroyed.Yes (at the time)

The spelling is succeeded — not ok, not complete.

Generating

curl -X POST https://api.buildsable.com/v1/videos/generations \
-H "authorization: Bearer $SABLE_API_KEY" \
-H 'content-type: application/json' \
-d '{
  "model": "sable-video",
  "prompt": "a slow pan over a black lake at dusk",
  "duration_secs": 5,
  "aspect_ratio": "16:9",
  "resolution": "1080p"
}'

Request fields

FieldNotes
modelRequired. A sable id from GET /v1/videos/models.
promptRequired. Up to 4,000 characters.
duration_secsSeconds of output. Rounded up to whole seconds and clamped to the model's maximum. Omitted ⇒ the model default.
aspect_ratio, resolution, seedOptional; passed through to the model.
imageBase64 or a data: URL. Required on an image-to-video model, and refused on a text-to-video one — silently ignoring it would bill you for a video that had nothing to do with your image.
nMust be 1. Price is per second, so two jobs cost exactly what n: 2 would.
sable_run_idChains this job's receipt into an agent run.

Pricing: per second, not per job

Video is priced per second of produced output, because duration is yours to choose. price_usd_per_second on the model listing is the rate.

Two consequences worth knowing before your first bill:

A job that fails, times out, or is cancelled before the provider produced anything bills nothing and releases its reservation.

Cancelling

POST /v1/videos/generations/:id/cancel polls the provider once before deciding, because the honest answer depends on what actually happened:

Read billed; do not assume.

Storage, the TTL, and §3

Sable's privacy contract says prompts, completions and submitted code are never persisted. Video needs one disclosed exception, and here it is plainly:

Some backends return a URL rather than bytes. Sable fetches it server-side into the sealed store and never hands you the provider's link. Passing it through would leak your request to a third party outside Sable's contract, and it would expire on that provider's schedule instead of the TTL published here.

The receipt

Every settled job mints a signed, metadata-only receipt, stored like every other and verifiable at the public POST /v1/receipts/verify.

{
  "v": 1,
  "kind": "video",
  "request_id": "vid_…",
  "model": "sable-video",
  "engine": "…",
  "provider": "video:…",
  "unit": "video_seconds",
  "quantity": 5,
  "duration_secs": 5,
  "resolution": "1080p",
  "content_fingerprint": "…",
  "output_sha256": "…",
  "cost_micro_usd": 2000000,
  "latency_ms": 41230,
  "created_at": "…"
}

output_sha256 is the hash of the exact bytes produced, so a receipt and the file can be checked against each other years later.

Each video also carries a signed provenance manifest, the same content-free shape images use, verifiable at POST /v1/videos/verify (an alias for the image verifier — one verifier, one answer). It reports "embedded": false for video: MP4 and WebM are recognised but the manifest is deliberately not written into the container, because a half-correct box produces files some players reject. The manifest travels beside the file instead, and Sable never claims an embedding it did not do.

From an agent

The routes are key-authed, so a hosted agent's injected key reaches them with no extra plumbing. Over MCP the tools are sable_generate_video (returns the job id immediately — an MCP call must never block for five minutes) and sable_video_status (poll it).

Limits and failure classes

In-flight jobs per account4 by default; over it returns 429 with Retry-After. One caller cannot monopolize the poller.
Per-job deadline15 minutes by default. Past it the job is failed with error_class: "timed_out" and nothing is billed.
Asset ceiling64 MiB by default, enforced while downloading.

error_class is always a fixed identifier — provider_failed, no_output, timed_out, download_failed, job_not_found, provider_unconfigured — never provider prose, which can echo your prompt back at you.