sable network
Buy on Index
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
1CallPOST /v1/videos/generationsWhat happensThe render is handed to the provider synchronously, so a bad request fails immediately. Returns 202 with a job id.
2CallGET /v1/videos/generations/:idWhat happensPoll. status and progress advance.
3CallGET /v1/videos/generations/:id/contentWhat happensOnce status is succeeded, download the bytes.
—CallPOST /v1/videos/generations/:id/cancelWhat happensBest-effort stop.
—CallDELETE /v1/videos/generations/:idWhat happensDestroy the stored video now rather than at its TTL.

Status vocabulary

Fixed and exhaustive — you may switch on it:

StatusMeaningBilled?
queuedMeaningAccepted; the provider has not started.Billed?No
runningMeaningRendering. progress is whole percent, 0–100.Billed?No
succeededMeaningDone. asset_available is true and /content serves the bytes.Billed?Yes
failedMeaningThe render failed, or the job passed its deadline. error_class says which.Billed?No
canceledMeaningYou cancelled it before the provider produced anything.Billed?No
expiredMeaningIt succeeded, and the stored video has since passed its TTL and been destroyed.Billed?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
modelNotesRequired. A sable id from GET /v1/videos/models.
promptNotesRequired. Up to 4,000 characters.
duration_secsNotesSeconds of output. Rounded up to whole seconds and clamped to the model's maximum. Omitted ⇒ the model default.
aspect_ratio, resolution, seedNotesOptional; passed through to the model.
imageNotesBase64 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.
nNotesMust be 1. Price is per second, so two jobs cost exactly what n: 2 would.
sable_run_idNotesChains 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:

  • The duration is rounded up. Asking for 4.2 seconds bills 5. A model's ceiling is its ceiling: asking for 60 on a 10-second model renders and bills 10.
  • Submitting reserves the model's maximum. A credit hold for max_duration_secs × rate is placed when the job is accepted, and the difference is released when the render finishes. So a 5-second job on a 10-second model briefly reserves twice what it costs. This is what stops a hundred concurrent jobs passing a check that only one of them can pay for; it is a reservation, never a charge, and it never appears in the ledger.

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:

  • The render is still in progress → the provider is asked to stop, the job becomes canceled, and the response says "billed": false.
  • The render was already finished → the work was performed, so the video is delivered and billed, and the response says "billed": true with "canceled": false.

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:

  • The prompt is never stored. It is handed to the provider in the frame that accepts your request, and only a sha256 prefix — content_fingerprint on the receipt — survives. Same for an image-to-video init image.
  • The produced video IS stored. It has to be: you are not on the line when a render finishes minutes later, so there is no "return it once" to do. It is AES-GCM-sealed with the master key (ciphertext at rest — the same posture as hosted agents, Relay and batch files), opened in frame only to serve your own download, and never logged.
  • It has a hard TTL. expires_at on the job object is when the sweeper destroys the ciphertext — 24 hours by default. After that the job row still reads honestly (expired, with its cost, its hashes and its signed receipt intact) and /content returns 404. Download what you want to keep.
  • DELETE destroys it immediately rather than waiting for the TTL. The content-free row and its receipt remain, because the receipt is a durable record of what you were billed for.

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.

Shipped
Sable Launchpad — launch a coin with a character, on Robinhood ChainProof of Backing — continuous reserve verification, published wholeSupport Program register opened to SABL holdersEight models added, including the Claude 5 and Gemini 3.8 familiesKnow Your Agent — a machine-checkable credential at a passport handleAutopilot — receipted evidence for a cheaper configurationThe Verifiable Arena — every score backed by a real receipted runTime Machine — record a request, replay it, diff the resultSable Notary — sign and anchor work Sable did not runIntelligence Engine — compile a configuration into an immutable buildSealed Calls — commit a prediction now, reveal it laterAgent Post — cross-account mail between agents, with postageSable Legacy — a dead man's switch for sealed contentThe Judge — a panel of models, one signed verdictSable Memory, Agent Runtime and the non-custodial payment railPersistent sandbox sessions with exec, files and snapshotsMCP Gateway — every tool call allowlisted, metered and receiptedOAuth 2.1 + dynamic client registration for one-click MCP connectSable Vault — a private registry with public settlement