Validating a key
The endpoint the SDKs call for every guarded request, and the eight checks behind it.
POST /api/v1/client/{client_uuid}?project_env=…&method_path=…This is what the SDKs call in server mode. You rarely call it yourself — but knowing
what it does explains every 401, 403 and 429 your consumers see.
Request
{
"name": "list-users",
"timestamp": "2026-09-16T10:00:00.000Z",
"headers": {
"x-api-key": "apikee_…",
"x-forwarded-for": "203.0.113.9"
},
"payload": { "any": "context to log" }
}| Field | Required | Meaning |
|---|---|---|
name | ✓ | The key's name, for the log entry |
timestamp | ✓ | When your service received the request, ISO 8601 with offset |
headers | ✓ | Incoming headers. The key is read from here; the caller IP comes from x-forwarded-for |
payload | Anything extra you want logged |
The eight checks
They run cheapest-first and stop at the first failure. Every outcome — accepted or rejected — is written to the request log with credentials redacted.
| # | Check | Failure |
|---|---|---|
| 1 | Timestamp is sane (not in the future) | 401 STALE_REQUEST |
| 2 | Key format, HMAC signature, embedded expiry | 401 (400 if malformed) |
| 3 | Key exists, is enabled, not expired in the database | 401 |
| 4 | Signed claims match the stored row; key belongs to this client | 401 |
| 5 | Owning client is active | 403 |
| 6 | Caller IP is in the client's allow-list | 403 |
| 7 | Request signature, when the environment has a public key | 401 |
| 8 | Template rate limits | 429 + Retry-After |
Steps 1–2 are exactly what your SDK already did locally, repeated because the platform does not trust the caller's word for it. Steps 3 onward are the things local mode cannot know.
Why the order matters
Rate limiting is last on purpose. An invalid or revoked key is rejected before it can consume any of a tenant's budget, so someone brute-forcing keys against one of your customers cannot exhaust that customer's quota as a side effect.
Check 4 is the one people forget: a key that is individually valid but belongs to a different client fails here. Keys are not transferable between clients.
Request signing
For environments needing more than a bearer credential, set a PEM public key on the environment. The SDK then signs
{clientId}|{method_path}|{timestamp}with the matching private key and sends it as x-apikee-signature (base64url).
Signed requests are additionally held to a five-minute replay window.
Configure the key with setPublicKey. In the SDKs, supply
privateKey.
Doing it yourself
curl -X POST \
"https://apikee.example.com/api/v1/client/0b7f…?project_env=payments-api-production&method_path=GET%3A%3A%3A%2Fv1%2Fusers" \
-H "x-api-key: apikee_..." \
-H "content-type: application/json" \
-d '{
"name": "list-users",
"timestamp": "2026-09-16T10:00:00.000Z",
"headers": { "x-api-key": "apikee_...", "x-forwarded-for": "203.0.113.9" }
}'If you are writing an SDK for a language Apikee does not cover, verify the signature locally first and call this endpoint asynchronously — off the response path. That is what the four official SDKs do, and it is why a slow or unreachable platform does not slow your API down.

