Browser voice app that starts Sume jobs: keep the key on your server
Voice apps run in the browser over WebRTC, but Sume keys belong on a server. A route handler that holds the key, allowlists models, reuses idempotency keys.

If a voice app runs in the browser and can start Sume generation jobs, the browser must not hold the Sume API key. Sume's authentication docs say browser and mobile clients should call your backend, and the backend attaches the key.
OpenAI's Realtime guide puts browser sessions on WebRTC, so the page already talks to two parties. Only one of them, your server, should know the Sume key.
Who holds what
From the Sume authentication docs and the OpenAI Realtime guide, read 2026-10-03.
| Piece | Lives in | Rule |
|---|---|---|
| Voice session in the browser | Browser | WebRTC, per the Realtime guide |
| Voice session on a server | Your server | WebSocket, per the Realtime guide |
| Sume API key | Server environment variable | Never in frontend JavaScript or mobile apps |
| Job submit | Your route handler | Attaches the key; validates input first |
| Job result | Your route handler or a webhook | Poll status or accept a signed webhook |
A route handler that holds the key
The handler below follows the proxy shape in Sume's docs, with three additions. It allowlists the models the page may ask for, it forwards only model and prompt, and it takes the idempotency key from the client instead of minting a new one per request. A fresh random key on every call defeats idempotency, because a retry of the same click would create a second paid job.
const ALLOWED = new Set(["sume/auto"]);
export async function POST(request: Request) {
const key = request.headers.get("idempotency-key");
if (!key) return new Response("idempotency-key required", { status: 400 });
const { model, prompt } = await request.json();
if (!ALLOWED.has(model) || typeof prompt !== "string" || prompt.length > 2000) {
return new Response("bad request", { status: 400 });
}
// enforce your own user authorization here before forwarding
const upstream = await fetch("https://api.sume.com/v1/images", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify({ model, prompt, mode: "async" }),
});
return new Response(await upstream.text(), {
status: upstream.status,
headers: { "Content-Type": "application/json" },
});
}The browser side of the contract
Small rules that keep the split honest.
- The page generates one idempotency key per user intent and reuses it for retries of that intent.
- The page polls your status route, not Sume. Your route reads
GET /v1/jobs/:id/statuswith the key attached. - There is no push channel from Sume: the Developer API has no SSE or WebSocket transport, so the page polls or your server relays a webhook.
- Rotate the key if it ever appears in logs or chat history, as the authentication docs advise.
Webhook alternative
If you would rather not poll from the page, submit with mode: "webhook" and a public HTTPS webhook_url on your server. Sume sends terminal events only, signed with HMAC SHA 256, and your receiver should refuse to run with an empty secret. Keep polling available for deliveries that never arrive, as the webhook docs recommend.
Sources
Related posts
More in Developers
- Sume bulk queue 404 format_run_queue_not_found: three causes
A Sume bulk queue poll returned 404 format_run_queue_not_found. The id is wrong or the queue is another owner's. How to tell which, and what to do next.
- Bulk run 409 idempotency_conflict: find the first queue by queue_id
A Sume bulk create that reuses a key with a different payload returns 409 with details.queue_id. How to read the original queue and decide what to resend.
- Burn text onto a silent video: fixing caption_no_speech
A silent clip fails Sume's speech-to-captions path with caption_no_speech. Pass authored cues with text, start and end to burn overlay copy with no ASR.
- C2PA 2.2: file types that can carry credentials vs Sume outputs
C2PA 2.2 manifests can be embedded in JPEG, PNG, WebP, SVG, MP4, MOV and more. How that list lines up with the formats Sume image and video jobs return.
Written by Sume