Errors and rate limits
Public read RPCs use the native Connect error envelope. Account-scoped REST
endpoints use RFC 7807 problem
details with Flan extensions (code, details).
Connect error format
Public read RPCs return JSON like:
{
"code": "invalid_argument",
"message": "work_mode must be remote, hybrid, or onsite"
}
The X-Request-ID response header always carries the request id — useful when
contacting support.
REST problem format
Account-scoped REST endpoints return Content-Type: application/problem+json
with a body of:
{
"title": "Invalid Request",
"status": 400,
"detail": "unknown field \"foo\" in request body",
"instance": "/v1/reports",
"code": "invalid_request",
"details": { "field": "foo" },
"request_id": "req_a1b2c3d4e5f6..."
}
| Field | Always present | Description |
| ------------ | -------------- | -------------------------------------------------------------- |
| title | yes | Short human-readable title. |
| status | yes | HTTP status code. |
| detail | yes | Specific explanation. |
| instance | no | The path that errored. |
| code | yes | Stable machine-readable code (see below). |
| details | no | Free-form object. For validation errors: { "field": "..." }. |
| request_id | no | Echoed from X-Request-ID if provided, else generated. |
Status codes
| Status | Code | Meaning |
| ------ | --------------------- | ------------------------------------------------------ |
| 400 | invalid_request | Malformed query/body, unknown fields, bad enum. |
| 401 | unauthorized | Missing or invalid credentials. |
| 403 | forbidden | Authenticated but not allowed (e.g. unverified email). |
| 404 | not_found | Resource does not exist. |
| 405 | method_not_allowed | Wrong HTTP method. Allow header is set. |
| 409 | conflict | Unique constraint violation. |
| 413 | payload_too_large | Request body exceeds 64 KiB. |
| 429 | rate_limited | Too many attempts. See Rate limits below. |
| 500 | internal_error | Unexpected backend failure. |
| 501 | not_implemented | Endpoint exists but is not yet available. |
| 503 | service_unavailable | Maintenance or capacity mode. |
Rate limits
Rate limiting is being rolled out in stages. Today:
- Auth flows (
/v1/auth/login,/v1/auth/password/forgot) are throttled per-email and per-user to prevent brute force. Exceeding returns429 rate_limitedwith the message "too many authentication attempts". - Public read RPCs (
JobsService,CompaniesService,MetaService) are not rate-limited during beta. - API-key rate limits (per the pricing page) will be enforced once API-key authentication is wired through. Until then, treat the limits documented there as targets, not hard caps.
When limits activate, 429 rate_limited responses will include a
Retry-After header (seconds).
Validation details
For request body validation, the server uses strict decoding:
unknown fields cause 400 invalid_request with details.field set to the
offending field. For nested objects, the field path is dot-separated
(e.g. filters.work_mode).
{
"status": 400,
"code": "invalid_request",
"detail": "unknown field \"foo\" in request body",
"details": { "field": "foo" }
}
Reporting bad data
If you find a job whose status appears wrong, or whose company/location is incorrect, please report it:
curl -X POST https://api.useflan.app/v1/reports \
-H "Content-Type: application/json" \
-d '{
"job_id": "018f3b21-...",
"type": "expired",
"message": "URL returns 404"
}'
Valid type values: expired, broken_apply, wrong_company,
wrong_location, duplicate, spam, bad_format, other. Requires a
cookie session (use the report form in the UI).

