Errors
The RedPennon API returns standard HTTP status codes and a predictable JSON shape for failures.
Error response shape
Authentication errors (401) return only the error field:
{ "error": "Invalid or missing API key." }
Governance errors (403, 429, 400 governance) always include a code field for stable programmatic matching:
{
"error": "Rate limit exceeded. Retry after a short delay.",
"code": "rate_limit_exceeded"
}
error— human-readable message.code— stable machine identifier; present on governance errors only. Auth (401) responses do not includecode.
Branch on code (or status) in SDKs — do not pattern-match on error text.
Governance errors (POST /v1/variables, /v1/variables batch, /v1/events)
| Status | code | Meaning |
|---|---|---|
| 403 | organisation_suspended | Organisation suspended by staff |
| 403 | organisation_inactive | Organisation is not on the Free plan and has no active subscription |
| 403 | monthly_active_users_exceeded | Plan MAU cap for this calendar month |
| 429 | rate_limit_exceeded | Per-organisation rate limit (shared across API workers via Postgres; retry with backoff) |
| 400 | batch_too_large | Too many keys in a batch evaluate request |
| 400 | events_batch_too_large | Too many events in one ingest request |
When governance returns 403 or 429, the request is rejected before evaluation — use your code default for flags; do not treat these as fail-open value: null responses.
Evaluation endpoints
| Status | Typical cause |
|---|---|
| 200 | Success (including fail-open value: null for unknown keys) |
| 400 | Invalid JSON, bad user shape, missing keys / events, batch too large |
| 401 | Missing or invalid X-API-Key |
| 403 | Governance (see table above) |
| 405 | Wrong HTTP method |
| 429 | Rate limit |
Code references (POST /v1/code-references)
The code references ingest endpoint authenticates with an organisation API token, not the X-API-Key, so its error shape differs:
| Status | Typical cause |
|---|---|
| 200 | Snapshot accepted ({accepted, matched, unknown_keys}) |
| 400 | Invalid JSON, missing project/branch, malformed repository, non-github provider, bad references entry |
| 401 | Missing, malformed, unknown, or revoked organisation API token (rpa_…) |
| 402 | Organisation's plan does not include code references |
| 404 | Unknown project slug |
Health
| Endpoint | 200 | 503 |
|---|---|---|
GET /health | Yes | Yes |
503 is only used by /health when the database is unreachable.
To disable a cap (local development only), set the corresponding _DISABLED environment variable to true — setting the numeric variable to 0 does not disable enforcement; non-positive values fall back to the built-in default instead.
| Cap | Disable with |
|---|---|
EVAL_API_RATE_LIMIT_PER_MINUTE | EVAL_API_RATE_LIMIT_DISABLED=true |
EVAL_API_MAX_BATCH_KEYS | EVAL_API_MAX_BATCH_KEYS_DISABLED=true |
EVAL_API_MAX_EVENTS_BATCH | EVAL_API_MAX_EVENTS_BATCH_DISABLED=true |
Evaluation endpoints fail open for unknown variable keys (200 with value: null and a reason) so SDKs can use code defaults. Governance errors above are not fail-open.
Anonymous evaluation traffic (no user.id or user.email) counts as a single shared MAU slot (anon:unidentified) per organisation per month.
400and405are caller errors. Don't retry — fix the request.401is a credential problem. Don't retry — refresh the API key.403fororganisation_inactiveororganisation_suspended— fix billing or contact support; don't retry.403formonthly_active_users_exceeded— upgrade plan or wait for next month; returning users already counted this month are not blocked.429— retry with jittered backoff.5xxand connection errors are safe to retry with jittered backoff. Evaluations are read-only, so retries are idempotent for the same user context.