Reference
Errors & rate limits
Every failure comes back as JSON in one shape, with a status code that tells you the category and a message that tells you the fix. Nothing throws HTML at you — the API surface always negotiates JSON.
The error envelope
{
"error": {
"type": "invalid_request_error",
"message": "The amount field must be a non-zero integer.",
"fields": {
"amount": ["The amount field must be a non-zero integer."]
}
}
}
type and message are always present. fields
appears only on validation errors (422) and maps each rejected field to its messages.
Status codes
| Code | Type | Meaning |
|---|---|---|
202 | — | Event accepted and queued (ingest and /v1/events). |
200 / 201 | — | Read succeeded; resource created. |
204 | — | No content (e.g. participant erasure). |
401 | authentication_error | Missing, unrecognised, invalid or revoked credential. |
403 | permission_error | Valid key without the required ability, or a blocked IP. |
404 | not_found_error | Unknown resource or inactive ingest endpoint. |
422 | invalid_request_error | Malformed or failing validation. |
429 | — | Rate limit exceeded. |
X-Request-Id
Every response carries an X-Request-Id. If you send one, it's echoed back;
otherwise Flyhalf generates one. Log it and quote it in support requests — it's the fast
path to the exact request in our logs.
Rate limits
| Surface | Limit | Keyed by |
|---|---|---|
| Ingest | 1,200 / min | Per endpoint key |
| Authenticated API | 300 / min | Per API key |
Ingest is deliberately generous — accept-and-queue must never be the bottleneck — and
keyed per endpoint so one flooding integration can't starve another. Exceeding a limit
returns 429; back off and retry. Because ingest is idempotent, a retried
event after a 429 is safe.
Monthly quotas
On top of per-minute rate limits, each plan has monthly quotas. Metered dimensions are events, messages, ai_generations and exports. You get a soft warning at 80% and a hard cap at 100%.
| Plan | Events / mo | Messages | AI generations |
|---|---|---|---|
| Starter | 10,000 | 500 | 50 |
| Growth | 250,000 | 10,000 | 500 |
| Scale | Custom | Custom | Custom |
Sandbox doesn't count against quota
Only live events are metered — events sent with a fh_test_ key or
?sandbox=1 are free. Load-test your flows in sandbox without spending your
monthly allowance. See Sandbox mode.