Errors
API error envelope, error codes, validation error shape, and SDK error classes.
Updated 2026-05-20
Every error response uses the same envelope. The code field is stable across versions and safe to match against; the message field is human-friendly and may change.
Envelope
{
"error": {
"code": "validation_failed",
"message": "Required field 'title' was missing.",
"details": { "fieldErrors": { "title": ["String must contain at least 1 character"] } },
"traceId": "trc_8X3FpQk"
}
}
code: machine-readable, stable, snake_casemessage: human-readable, may evolvedetails: optional structured context (field-level errors, conflicting slug, etc.)traceId: same value as thex-faq-trace-idresponse header; quote this to support
Common error codes
| Code | HTTP | When |
|---|---|---|
unauthorized | 401 | Missing / malformed Authorization header |
invalid_api_key | 401 | API key doesn’t match any active key |
insufficient_scope | 403 | Key’s scopes don’t include the required one |
not_found | 404 | Resource doesn’t exist or belongs to a different org |
slug_conflict | 409 | A resource with this slug already exists |
validation_failed | 400 | Request body / params failed Zod validation |
invalid_json | 400 | Body wasn’t valid JSON |
rate_limit_exceeded | 429 | Quota or burst limit hit; see Retry-After header |
plan_limit_reached | 402 | Org hit a plan limit (questions, keys, requests, etc.) |
internal_error | 500 | Server-side bug; the traceId is the key to finding it |
Resource-specific codes (e.g. question_not_found, category_not_empty) are documented per endpoint.
Validation errors
Zod-validated endpoints return field-level details:
{
"error": {
"code": "validation_failed",
"message": "1 invalid field",
"details": {
"fieldErrors": { "answer": ["String must contain at least 1 character"] },
"formErrors": []
}
}
}
fieldErrors keys map directly to your request body’s shape. formErrors carries issues that apply to the request as a whole (e.g. mutually exclusive fields).
SDK error classes
@faqapp/core throws typed error classes so you can handle each case explicitly:
import {
FAQAPIError,
FAQValidationError,
FAQNotFoundError,
FAQRateLimitError,
FAQNetworkError,
FAQTimeoutError
} from "@faqapp/core";
try {
await faq.questions.create({ title: "", answer: "" });
} catch (err) {
if (err instanceof FAQValidationError) {
console.error(err.errors); // ValidationIssue[]
} else if (err instanceof FAQNotFoundError) {
// 404, resource doesn't exist
} else if (err instanceof FAQRateLimitError) {
await sleep(err.retryAfter * 1000);
} else if (err instanceof FAQAPIError) {
// any other 4xx/5xx; err.code, err.status, err.traceId available
} else if (err instanceof FAQNetworkError) {
// DNS / connection refused / no route
} else if (err instanceof FAQTimeoutError) {
// exceeded the SDK's configured timeout
}
}
Best practices
- Match on
code, notmessage. Codes are stable; messages are not. - Log the
traceId. Quote it in any support email; we look up the exact request in seconds. - Backoff on 429. Use the
Retry-Aftervalue; exponential backoff for repeated failures. - Don’t show raw
messageto end users. Map codes to UI copy you control. - Treat network errors as different from API errors. A timeout is not a
500; retry strategy differs.