All errors follow a consistent shape:
{
"error": {
"code": "not_found",
"message": "Authorization not found"
}
}
Validation errors include a fields array:
{
"error": {
"code": "validation_error",
"message": "Invalid request",
"fields": [
{ "field": "body.expires_at", "message": "expires_at must be in the future" }
]
}
}
Error codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | bad_request | Malformed request |
| 401 | unauthorized | Missing or invalid API key |
| 402 | payment_required | Billing setup, paid tier, or quota/payment gate required |
| 403 | forbidden | Valid key but not permitted |
| 404 | not_found | Resource does not exist |
| 409 | conflict | State conflict (e.g. already revoked) |
| 410 | gone | Nonce expired or consumed |
| 422 | validation_error | Request body failed validation |
| 500 | internal_error | Unexpected server error |
| 503 | service_unavailable | Service temporarily unavailable |
Budget note: creating a budgeted authorization on Free/Starter returns 402 budget_plan_required. Missing estimated_cost_micros on a budgeted authorization, or sending multiple scopes on a budgeted check, returns 422 validation_error. An over-budget action is not an API error; /check returns decision: "deny" with reason: "budget_exceeded" and writes a receipt.
SDK error handling
Python
from allowly import Allowly, AllowlyAPIError
allowly = Allowly(api_key="allowly_l1_s001_2y6r..._k9m3...")
try:
result = await allowly.check(authorization_id="auth_...", scopes=["x"])
except AllowlyAPIError as e:
print(e.status) # 401
print(e.code) # "unauthorized"
print(str(e)) # "Invalid or revoked API key"
TypeScript
import { Allowly, AllowlyAPIError } from "@allowly/sdk";
const allowly = new Allowly({ apiKey: "allowly_l1_s001_2y6r..._k9m3..." });
try {
const result = await allowly.check({ authorizationId: "auth_...", scopes: ["x"] });
} catch (e) {
if (e instanceof AllowlyAPIError) {
console.log(e.status); // 401
console.log(e.code); // "unauthorized"
console.log(e.message); // "Invalid or revoked API key"
}
}