Formats

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.

6 min readSume
All posts

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:read and formats: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.

Replay semantics, from Embed a Format in your product, read 2026-09-25.
RequestResult
Same key, same body200 with the original receipt and idempotency_hit: true. No second run, no second charge.
Same key, different body409 idempotency_conflict. Nothing runs.
No keyEvery 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-v1 signature, HMAC-SHA256 over <timestamp>.<raw_body>, against the raw body before parsing. The SDK's verifyWebhook does this.
  • Return 2xx fast, 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; fetch error.result_url instead.
  • Register the final HTTPS URL. Redirects are not followed, so a 3xx is 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 4xx means nothing ran and nothing was charged. Fix the key or the call; retrying 403 insufficient_scope forever is a common and expensive mistake.
  • At run: status is failed. unattended_blocked means the run hit a gate it could not pass without a person; format_run_failed is the generic failure. Retry with a new idempotency key.
  • Terminal, not failed: canceled and skipped runs 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