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_secret is the secret you saw exactly once when you created the API key. Treat it like a password — never commit it.
  • timestamp must be within 5 minutes of server time. Older or future requests are rejected.
  • raw_request_body is 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, raw

Replay 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

MethodPathScope required
POST/v1/validateFULL, READ_ONLY, VALIDATE_ONLY
POST/v1/activateFULL, VALIDATE_ONLY
POST/v1/deactivateFULL, VALIDATE_ONLY
POST/v1/heartbeatFULL, READ_ONLY, VALIDATE_ONLY
POST/v1/issueFULL, ISSUE_ONLY