Errors

RFC 9457 problem details with stable machine-readable codes

Every error — from any endpoint, any status — is a flat RFC 9457 body with content type application/problem+json:

1{
2 "type": "https://api.rray.app/errors/email.not_found",
3 "title": "Email not found",
4 "status": 404,
5 "code": "email.not_found",
6 "request_id": "a35c9c6d1745403d...",
7 "instance": "/v1/emails/em_01J8..."
8}

Match on code, not on text

code is the stable contract: dotted, namespaced (<domain>.<outcome>), and never reused for a different meaning. title is for humans and may be reworded; status alone is too coarse (three different conflicts all map to 409).

1if problem["code"] == "rate_limit.exceeded":
2 sleep(int(response.headers["Retry-After"]))
3elif problem["code"].startswith("idempotency."):
4 ...

Every type URL resolves

Follow the type link (or browse GET /errors) to the public error catalog — the same registry the API itself raises from, so it is never out of date. Each API Reference endpoint also documents exactly the errors it can return, with examples.

Validation errors

422 validation.request_invalid adds an errors array with per-field issues:

1{
2 "code": "validation.request_invalid",
3 "status": 422,
4 "errors": [
5 {"field": "body.to", "message": "field required", "kind": "missing"}
6 ]
7}

When you contact support

Quote the request_id — it links your response to our internal logs exactly.