How to embed AI video generation in your product with Sume Formats
To embed AI video generation, your server holds one Sume API key and runs a Format per customer, with a derived Idempotency-Key, spend cap, and webhook.

To embed AI video generation in your product, add a button in your UI that calls your own server, and have that server run a Sume Format with POST /v1/formats/{handle}/{slug}/runs for the customer who clicked. Your customer never talks to Sume: your server holds one Sume API key, runs Formats on their behalf, and maps the results back onto your own records.
This walkthrough follows Sume's Embed a Format in your product cookbook, with details from Create a run and the TypeScript SDK page, read on 2026-09-25.
Where should the Sume API key live?
On your server only. Sume has no per-end-user credential and no browser-safe key, and the key spends your credits: anyone holding it can run any Format you own, up to your caps. The cookbook's key custody rules:
- Keep it in your server's environment, never in client JavaScript, a mobile bundle, or a
NEXT_PUBLIC_*variable. - Proxy the call, not the key. Your endpoint accepts your customer's identifiers and builds the Sume request itself.
- Add your own authorization check. Sume authenticates you, not your customer.
- Create the key at API keys with
formats:readandformats:write. Scopes cannot be added later, and service-account keys cannot create Format runs. - A team Format needs a key created in that team's workspace; a personal key gets
403 workspace_key_required.
How do I start a run for one customer?
Use the official TypeScript SDK, @sume-com/sdk (0.2.0+), or one plain HTTP POST. The SDK needs only fetch and WebCrypto, so it runs on Node 18+, Bun, Deno, or Cloudflare Workers. subscribeFormatRun creates the run and waits for the terminal receipt in one call; it polls rather than streams, and its default timeout is 20 minutes.
The client sends x-api-key only. Do not add an Authorization header as well: a request carrying both credentials fails with 401 unauthorized.
import { createSumeClient, subscribeFormatRun } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
const run = await subscribeFormatRun({
client,
path: { handle: "acme", slug: "product-promo" },
idempotencyKey: runKey(customer, order),
body: {
input: { product_url: order.productUrl },
generation_spend_cap_usd: spendCapForPlan(customer.plan),
},
});
if (run.status === "completed") await attachOutputs(order.id, run);How do I stop a double-click from starting two paid runs?
Derive the Idempotency-Key from the thing being made, not the moment of asking: hash your tenant id, order id, Format slug, and a version you bump when you deliberately want a re-run. A uuidv4() per request makes the header decorative, and a key built only from the order id lets two tenants with colliding ids share a run. Store the returned run id before you answer the browser. More in idempotency keys for AI video APIs.
| Request | Result |
|---|---|
| Same key, same body | 200 with the original receipt and idempotency_hit: true. No second run, no second charge. |
| Same key, different body | 409 idempotency_conflict. Nothing runs. |
| No key | Every call starts a new paid run. |
How do I cap what one customer's run can spend?
Set generation_spend_cap_usd on each request, for example from your customer's plan tier. It goes up to the platform maximum of $500: a number above the Format's own cap is honored, anything above $500 is a 400, and leaving it out inherits the Format's cap, which defaults to $400.
The receipt reports spend against that cap as usage.billable_amount_usd_micros. It excludes the agent's own LLM turn, so it is not the run's total cost or an invoice; bill your customer from your own records and reconcile against GET /v1/usage. More in spend caps for unattended AI agents.
How does my server get the finished video?
Register communication.webhook_url and Sume POSTs the signed terminal receipt to it. Keep polling status_url, or subscribeFormatRun, wired as the backup for the day your endpoint is down.
- Verify the
sume-v1signature, HMAC-SHA256 over<timestamp>.<raw_body>, against the raw body before parsing. The SDK'sverifyWebhookdoes this. - Return
2xxfast, then work. The delivery attempt budget is 10 seconds. - Dedupe on
request_id; retries repeat it. - A receipt over 1 MiB arrives with
payload: null; fetcherror.result_urlinstead. - Register the final HTTPS URL. Redirects are not followed, so a
3xxis not a delivery.
What should I store and show?
A completed receipt carries primary_output_url (the one thing to show), artifacts[] (every generated file, with id, type, url, content_type, size_bytes, width, height, duration_ms, and checksum_sha256), and output, the structured result. Every URL is a durable media.sume.com HTTPS URL that does not expire, so you can store it against your record.
A durable URL is also a public URL, as the cookbook's artifacts section warns. If customer A must never see customer B's output, proxy the bytes through your own authenticated route, or copy them into your own storage when the webhook arrives.
Which failures should my UI tell apart?
- At submit: a
4xxmeans nothing ran and nothing was charged. Fix the key or the call; retrying403 insufficient_scopeforever is a common and expensive mistake. - At run:
statusisfailed.unattended_blockedmeans the run hit a gate it could not pass without a person;format_run_failedis the generic failure. Retry with a new idempotency key. - Terminal, not failed:
canceledandskippedruns never deliver a webhook. - At delivery: the run is fine and your endpoint was not. Fetch the receipt from
result_url.
Sources
Related posts
Written by Sume