Revoked a Sume API key and it still works? Up to 15 seconds
After you revoke a Sume API key, the API can keep accepting it for up to 15 seconds by default, because the key lookup is cached. What that means for a leak.

Yes: in current Sume code a revoked API key can keep working for up to 15 seconds, because the API caches the key lookup and the cache is not cleared when you press Revoke. After that window the key is refused for good, and the Authentication docs list a missing, malformed or revoked key as a 401.
That gap is small, but it matters if the reason you are revoking is a leak. This post explains where the 15 seconds comes from, what a caller can do inside it, and how to measure it on your own key. The figures come from apps/api/src/api-key-cache.ts and apps/api/src/auth.ts on the main branch, read 2026-10-10. The deployment can set its own value, so treat 15 seconds as the code default, not as a promise.
Why a revoked key is still accepted
Every authenticated request has to look the key up in the database. That lookup was the hottest query in the API, so the API keeps a short-lived in-memory cache of the result, keyed by a hash of the key. A cache entry is written when the key is looked up and expires a fixed time later. It does not slide forward when the key is used again.
The code comment states the trade directly: caching a credential trades revocation latency for throughput, so a key revoked in the dashboard stays accepted until its entry expires. The window is a security setting, not a speed setting, and setting it to 0 turns the cache off. Revoked records are cached as well, so once the API has seen a key as revoked it does not keep re-reading the database to learn that again.
| Setting | Default | What it controls |
|---|---|---|
| Known key TTL | 15,000 ms | How long a looked-up key record, including a revoked one, is reused |
| Unknown key TTL | 5,000 ms | How long a key the ledger did not find is remembered as unknown |
| Max entries | 5,000 | Size cap on the in-memory map; expired entries go first, then the oldest |
| TTL set to 0 | Cache off | Every request reads the ledger again, so revocation is immediate |
What a caller can do in the window
Anything the key's scopes allow, until the entry expires. That includes paid submits. A job accepted at second 10 is a real job with a real charge, and revoking the key afterwards does not undo it. The window is at most one TTL from the moment the API last filled the entry, so the worst case is revoking right after a fill.
Two details narrow the risk. First, revocation is not a refund path in either direction: spend made by the key stays attributed to it, as covered in revoked key spend stays attributed. Second, the cache lives in the memory of each API process, so you cannot assume every instance expires at the same second. Plan on the longest case.
Measure the window on your own key
Use a throwaway key, not a production one. The script calls GET /v1/me, which the Authentication docs show as the key check, prints the status every second, and stops when the API answers 401 or 403. Each poll is a read, so it uses the read budget, not your submit budget. Run it, revoke the key in the API Keys dashboard, press Enter, and read how many seconds pass before the refusal.
import os, time, urllib.request, urllib.error
def status(key):
req = urllib.request.Request(
"https://api.sume.com/v1/me",
headers={"x-api-key": key},
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status
except urllib.error.HTTPError as err:
return err.code
def main():
key = os.environ.get("SUME_API_KEY", "")
if not key:
raise SystemExit("Set SUME_API_KEY to the key you will revoke")
input("Revoke that key in the dashboard, then press Enter: ")
start = time.monotonic()
while True:
code = status(key)
print(f"{time.monotonic() - start:5.1f}s HTTP {code}")
if code in (401, 403):
break
time.sleep(1)
main()A rotation order that does not depend on the window
The documented rotation is: create a replacement key, deploy it, verify it with GET /v1/me, then revoke the old key. Because the old key may live for one more TTL, finish the steps below in order and do not count revocation as instant.
- Stop the code that uses the old key first, so nothing legitimate is in flight when you revoke.
- Revoke, then wait at least 15 seconds before you call the old key dead. Check it with the script above or one
GET /v1/me. - If the key leaked, list recent jobs after the wait and cancel anything queued that you did not start. Exposed API key: what to do covers the usage and spend check.
- Do not rotate in a loop because of a
503 api_key_auth_unavailable. That is a Sume-side condition, not a bad key, as explained in 503 api_key_auth_unavailable.
Sources
Related posts
More in Developers
- Sume public_reason: generation_rejected vs temporary error
How Sume picks generation_rejected, temporary_generation_error or generation_failed on a failed job from the provider HTTP status and the retryable flag.
- Sume "Generation could not start": rejected request or outage
Generation could not start is the fallback when a provider rejects a submit. A 4xx gives generation_rejected_request; anything else gives submit_failed.
- Sume job error details.input_field: the parameter that failed
When a provider rejects one request field, Sume publishes its name as details.input_field beside provider_error_message. Map it back to your body and fix it.
- Sume job failed artifact_too_large: shrink the output
A finished file that storage refuses with HTTP 413 fails as artifact_too_large and is not retryable. Shorten the cut, lower the resolution or the bitrate.
Written by Sume