Format run 401 with a valid key: Bearer and x-api-key both sent
A Sume Format run returns 401 unauthorized when a request carries two credentials at once. Send Authorization Bearer or x-api-key, never both.

A Sume Format run answers 401 unauthorized when the request carries two credentials at once. The call contract allows exactly one of Authorization: Bearer $SUME_API_KEY or x-api-key: $SUME_API_KEY, never both, and the fix lives in the headers, not in the body.
The key itself can be perfectly valid. That is what makes this 401 confusing: you paste the same key into a working curl command and it runs, then your application fails with the same key. The difference is usually one extra header added by a client, wrapper or proxy layer. This post shows how to confirm that and how to make the call send a single credential.
What exactly makes a Format run return 401?
The errors page lists the causes of unauthorized at create: no key, a malformed key, a revoked key, a key for the other host, or two credentials at once. All of them are the same HTTP status and the same code, so the status alone does not tell you which one you hit.
Because a 4xx at create means nothing ran and nothing was charged, a wrong header costs you nothing. The error envelope also gives you next_action: authenticate, which confirms the problem is the credential and not the payload.
| Cause | What to check |
|---|---|
| No key | The header is missing entirely |
| Malformed key | Truncated or quoted value, stray whitespace or newline |
| Revoked or unknown key | The key was deleted or rotated in the dashboard |
| Key for the other host | A dev key sent to api.sume.com, or the reverse |
| Two credentials at once | Both Authorization and x-api-key are present |
How do I see which headers my client really sends?
Print the final request, not the one you meant to build. Many HTTP clients merge default headers from a session object, an environment-level setting or a gateway in front of your service, so the request that leaves the machine can differ from the dictionary in your code.
The simplest check is a verbose curl against the same URL. If curl works with one header and your application fails, diff the headers. Do not paste the key into logs or tickets while you do this; log header names only.
curl -sS -v -X POST "https://api.sume.com/v1/formats/acme/live-commerce/runs" \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8823-lc-v1" \
-d '{"instruction":"Make one 9:16 clip."}' 2>&1 | grep -i '^> 'How do I send exactly one credential from code?
Pick one spelling and build the headers in one place. The sample below sets Authorization and refuses to build the request if an x-api-key header is also present, so a duplicate fails on your side before it reaches Sume. It uses only the Python standard library and does not send the request until you uncomment the last line.
The same single-credential rule applies to bulk queues and to reads, because they share the key rules of a single Format run.
import json, os, urllib.request
def build(handle, slug, body, extra=None):
headers = {
"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": body.pop("_key"),
}
headers.update(extra or {})
names = {h.lower() for h in headers}
if {"authorization", "x-api-key"} <= names:
raise ValueError("send one credential, not two")
url = f"https://api.sume.com/v1/formats/{handle}/{slug}/runs"
return urllib.request.Request(url, json.dumps(body).encode(), headers)
req = build("acme", "live-commerce", {"_key": "order-8823-lc-v1", "instruction": "One clip."})
print(req.full_url, sorted(req.headers))
# urllib.request.urlopen(req) # uncomment to sendWhat if the single header still returns 401?
Work down the remaining causes. Confirm the key was minted for the host you call: staging uses api.dev.sume.com and production uses api.sume.com, and a key for one is refused by the other. Then check the key has not been revoked.
Do not confuse this with a 403. A key that authenticates but lacks formats:write gets 403 insufficient_scope, and a team Format called with a personal key gets 403 workspace_key_required. Those are covered in the 403 post and the team key post. A 401 means Sume could not establish who you are; a 403 means it did and said no.
What should I log so the next 401 is quick?
Log the response error.code, the x-sume-request-id header (also error.request_id), the host and the header names you sent. Quote the request id if you contact support. Never retry a 401 in a loop: nothing about the request changes between attempts, and the answer will be the same.
Sources
Related posts
More in Formats
- Format run agent_reported_failure vs deliverable_missing: retry or not
agent_reported_failure means the run said it did not deliver; deliverable_missing means it made no media at all. Both leave a failed run, but the retry differs.
- The built-in Format output schema: sume/action-run-output/v1
No output_schema on a Format run returns sume/action-run-output/v1: text plus four media arrays, filled without a model, so it cannot fail like a custom schema.
- Format run stalled or just slow? Read the events phase timeline
GET /v1/format-runs/{run_id}/events shows preparing, running and finalizing phases. If the last at stops moving for minutes, the run is stalled, not slow.
- Format run failed provider_unavailable or mcp_unavailable: retry rules
provider_unavailable and mcp_unavailable are Sume-side Format run failures: retry with a new Idempotency-Key. provider_credits_exhausted waits.
Written by Sume