Documentation
Authentication
The KeyStack public API uses HMAC request signing instead of plain bearer tokens. This is more setup, but it gives you replay protection and tamper-evidence on every call — which matters when the endpoint is on the internet, hit by every install of your product.
Anatomy of an authenticated request
Every public API call needs three headers:
Authorization: Bearer ak_live_<api-key-id>
X-KeyStack-Timestamp: 1731600000
X-KeyStack-Signature: <hex-encoded HMAC-SHA256>The signature is computed as:
signature = HMAC_SHA256(
api_key_secret,
timestamp + "." + raw_request_body
)
api_key_secretis the secret you saw exactly once when you created the API key. Treat it like a password — never commit it.timestampmust be within 5 minutes of server time. Older or future requests are rejected.raw_request_bodyis the byte-for-byte body string. Re-serialise carefully —{"foo":1}and{ "foo": 1 }produce different HMACs.
Worked example (Node)
import crypto from 'node:crypto';
function sign(apiKeyId: string, apiKeySecret: string, body: unknown) {
const ts = Math.floor(Date.now() / 1000).toString();
const raw = JSON.stringify(body);
const sig = crypto
.createHmac('sha256', apiKeySecret)
.update(`${ts}.${raw}`)
.digest('hex');
return {
headers: {
Authorization: `Bearer ${apiKeyId}`,
'X-KeyStack-Timestamp': ts,
'X-KeyStack-Signature': sig,
'Content-Type': 'application/json',
},
body: raw,
};
}Worked example (Python)
import hmac, hashlib, json, time, requests
def sign(api_key_id, api_key_secret, body):
ts = str(int(time.time()))
raw = json.dumps(body, separators=(",", ":"))
sig = hmac.new(
api_key_secret.encode(), f"{ts}.{raw}".encode(), hashlib.sha256
).hexdigest()
return ts, sig, rawReplay protection
KeyStack remembers the nonce (api_key_id, signature) in Redis for 10 minutes. A duplicate is rejected with HTTP 401 api/timestamp-replay. This means you don't have to add a separate nonce — your signature already serves that role.
Dashboard / admin authentication
The dashboard and admin endpoints use a different scheme — JWT bearer tokens managed by the BFF route handlers in the Next.js app. You don't need to think about these unless you're writing custom integrations against the private API.
Endpoint summary
| Method | Path | Scope required |
|---|---|---|
| POST | /v1/validate | FULL, READ_ONLY, VALIDATE_ONLY |
| POST | /v1/activate | FULL, VALIDATE_ONLY |
| POST | /v1/deactivate | FULL, VALIDATE_ONLY |
| POST | /v1/heartbeat | FULL, READ_ONLY, VALIDATE_ONLY |
| POST | /v1/issue | FULL, ISSUE_ONLY |