Skip to main content

Errors & Status Codes

Every error response — from the FiveM Bridge, the API credential surface, or the token exchange — uses one uniform shape:

{
"error": "SOME_ERROR_CODE",
"message": "Human-readable text you can log or surface directly.",
"details": { }
}

details is optional and only present on a few error kinds (see below). message is safe to show a developer but isn't localized — build your own user-facing copy around error if you need something friendlier.

This page is about transport-level failures — a bad request, an auth problem, a rate limit. A successful 200 response that represents a business-level "no" (an unlinked player, an off-duty unit, a BOLO that doesn't exist) is a different thing — those use the { "<verb>": false, "reason": "..." } shape documented on each endpoint in FiveM Bridge Endpoints and aren't errors from an HTTP point of view.

Status codes you'll actually see

StatusWhenerror code(s)
400Malformed JSON body, or a body that fails schema validationMALFORMED_JSON, VALIDATION_FAILED
401Bad/expired/revoked token, or a bad clientId/clientSecret at token exchangeINTEGRATION_TOKEN_INVALID, INTEGRATION_INVALID_CLIENT_CREDENTIALS
403Valid credential, but missing the scope/permission the route requiresPERMISSION_DENIED
404The resource in the URL doesn't exist (or isn't visible to this credential)Resource-specific, e.g. UNIT_NOT_FOUND, CALL_NOT_FOUND, BOLO_NOT_FOUND
413Request body too largePAYLOAD_TOO_LARGE
429Rate limit hit — see Rate Limits & SecurityRATE_LIMITED
500Something went wrong on Kestrel's endINTERNAL_ERROR

A 500 never leaks internal detail beyond the generic message above — if you get one repeatedly, that's worth reporting rather than debugging from the response alone.

Validation errors (400 VALIDATION_FAILED)

{
"error": "VALIDATION_FAILED",
"message": "Request validation failed.",
"details": {
"formErrors": [],
"fieldErrors": {
"dateOfBirth": ["Expected YYYY-MM-DD"]
}
}
}

details.fieldErrors is keyed by field name, each value an array of human-readable messages for that field. formErrors holds errors that aren't tied to one specific field. Loop over both when surfacing this to a user rather than assuming one or the other is always empty.

Permission errors (403 PERMISSION_DENIED)

{
"error": "PERMISSION_DENIED",
"message": "You do not have permission to perform this action.",
"details": { "required": ["records.bolo.create"], "missing": ["records.bolo.create"] }
}

details.required lists every scope the route needs; details.missing narrows that to just the ones your credential doesn't have. For an API credential this means: go back to Admin → Integrations and add the missing scope. For a FiveM credential, only three routes enforce a scope at all — see Rate Limits & Security.

Rate limit errors (429 RATE_LIMITED)

{ "error": "RATE_LIMITED", "message": "Too many requests. Please wait and try again." }

No details — the response doesn't tell you which window or how long to back off for. Build your integration to retry with backoff on 429 rather than parsing anything out of the body.

What this isn't

None of the above applies to the discriminated { "reason": "..." } responses documented per-endpoint (not_linked, not_on_duty, call_not_found, and so on) — those are ordinary 200 responses your integration branches on in application code, not HTTP-level failures.