Errors
The TattooBooking API returns errors as RFC 9457problem+json — a stable, well-defined schema that's easier to consume than ad-hoc error envelopes.
Error envelope
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "/errors/validation_failed",
"title": "Validation failed",
"status": 422,
"detail": "email must be a valid email address",
"code": "validation_failed",
"instance": "/v1/clients"
}type— URI reference identifying the error classtitle— short human-readable summarystatus— HTTP status code (matches the response status)detail— human-readable explanation specific to this occurrencecode— machine-readable error code (the one you switch on)instance— URI of the specific endpoint that errored
Some errors include extra fields. For example, a 403 fromrequireScope includes required_scopes and granted_scopes so you know what to fix.
Status code reference
| Status | Meaning | Action |
|---|---|---|
| 400 | Bad request (malformed) | Fix client; not retryable. |
| 401 | Auth missing/invalid | Check the bearer token. |
| 403 | Forbidden — scope, plan, or IP | Read code + extra. |
| 404 | Not found | Cross-tenant resources also return 404. |
| 409 | Conflict (duplicate, racing update) | Reconcile; not retryable. |
| 422 | Validation failed | Fix the payload. |
| 429 | Rate limited | Wait and retry — see Retry-After header. |
| 500 | Server error | Retry with exponential backoff. |
| 503 | Service unavailable (Redis, etc.) | Retry. |
Common codes
The code field is the stable contract — switch on this in code, not on title or detail:
missing_api_key,invalid_api_key,revoked_api_key,expired_api_key,inactive_api_key,ip_not_allowedinvalid_access_token,revoked_access_token,expired_access_token,revoked_grant,app_suspendedinsufficient_scope— auth ok, but the key/grant lacks the required scopeplan_limit_reached— the tenant's plan caps this resource (also:feature_not_available)validation_failed,validation_error,invalid_cursor,invalid_id— some endpoints report simple 400 field problems asvalidation_error; treat both validation codes the same wayrate_limit_exceeded— seeRetry-Afterduplicate_email— POST /v1/clients with an email that already exists; the body includesexisting_idso you can reconcileconflict— the update conflicts with the resource's current state (409). Example: changing an appointment's status away fromCANCELLED/NO_SHOWwhile a charged cancellation fee exists — refund the fee first. Not retryable without changing state.resource_not_found
Validation errors
Validation failures return 422 with detail explaining which field failed and why. If multiple fields fail, the response lists them in extra.errors.
{
"type": "/errors/validation_failed",
"title": "Validation failed",
"status": 422,
"code": "validation_failed",
"extra": {
"errors": [
{ "path": "email", "message": "must be a valid email address" },
{ "path": "budget_range_min", "message": "must be ≥ 0" }
]
}
}