Project API keys
Lifecycle: mint, rotate, revoke
Inbound caller authentication is project-level: one key works on every endpoint in the project.
Each key carries a mode (live or test) — that picks the credit pool.
Mint
Plaintext is shown exactly once at creation. Store it before the next call.
POST https://api.echorelay.dev/keys
{ "name": "Production webhook", "mode": "live", "ttlDays": 90 }
Rotate
Mints a linked successor immediately. The predecessor stays valid until its own
expiresAt — use Revoke to kill it instantly.
Manual rotation returns the new plaintext in the response; you cannot recover it later.
POST https://api.echorelay.dev/keys/<KEY_ID>/rotate
Revoke (hard kill)
No grace period — the next caller request gets a 401 key_revoked.
DELETE https://api.echorelay.dev/keys/<KEY_ID>
Auto-rotation
A scheduler runs daily. Seven days before expiresAt, it mints a linked successor and
stores the new plaintext encrypted-at-rest. You reveal it exactly once:
POST https://api.echorelay.dev/keys/<PREDECESSOR_ID>/reveal-successor
Second call returns 410 Gone. If you miss the window, manually rotate the live key.
After expiresAt, the predecessor gets a 24-hour grace window — Framework
accepts the request and sets X-Echorelay-Key-Status: expired_grace
plus X-Echorelay-Key-Successor-Available: true if a usable successor exists.
After the 24 h, it returns 401 key_expired.
Status code summary
| Code | Reason | Headers |
|---|---|---|
| 200 | accepted, active | — |
| 200 | accepted, in grace | X-Echorelay-Key-Status: expired_grace |
| 401 | key_revoked | — |
| 401 | key_expired | — |
| 404 | unknown key (or no key against a non-public tenant) | — |
Policy
Owner-only project settings that constrain what editors may mint:
defaultKeyTtlDays— default lifetime applied to new keys (null = no default expiry)maxKeyTtlDays— hard cap for editors (owners ignore the cap)editorsMayCreateNonExpiringKeys— when false, editors cannot mint a key without an expiry