Errors
EUnifyer returns errors in the RFC 9457 Problem Details format using Content-Type: application/problem+json.
Error shape
{
"type": "https://problems.eunifyer.com/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "Required scope: drive:read",
"code": "forbidden",
"instance": "/api/v1/drive/browse"
}| Field | Type | Description |
|---|---|---|
type | URI | Stable machine-readable identifier for this error class |
title | string | Short human-readable summary |
status | integer | HTTP status code repeated in the body |
detail | string | Specific explanation for this occurrence |
code | string | Shorter machine-readable code (same as last path segment of type) |
instance | string | The request path that produced this error |
All fields except instance are always present. Additional fields may appear for specific error types.
HTTP status codes
| Status | Code | When |
|---|---|---|
400 Bad Request | validation_error | Request body or query parameter fails validation |
401 Unauthorized | unauthorized | Missing, expired, revoked, or malformed token |
403 Forbidden | forbidden | Token is valid but lacks the required scope or permission |
404 Not Found | not_found | Resource does not exist or is not visible to this token |
409 Conflict | conflict | Idempotency key collision with a different request body |
410 Gone | gone | Endpoint was deprecated and removed |
412 Precondition Failed | precondition_failed | If-Match header did not match the current ETag |
422 Unprocessable Entity | unprocessable_entity | Business logic violation (e.g. seat limit exceeded) |
429 Too Many Requests | rate_limit_exceeded | Rate limit hit — see Retry-After header |
500 Internal Server Error | internal_error | Something went wrong on the server |
Validation errors (400)
Validation errors include a errors extension listing field-level problems:
{
"type": "https://problems.eunifyer.com/validation_error",
"title": "Validation Error",
"status": 400,
"detail": "Request body failed validation",
"code": "validation_error",
"errors": [
{ "field": "name", "message": "field required" },
{ "field": "scopes", "message": "value is not a valid list" }
]
}Rate limit errors (429)
{
"type": "https://problems.eunifyer.com/rate_limit_exceeded",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded",
"code": "rate_limit_exceeded",
"limit": 300,
"reset_in_seconds": 42
}The response also includes a Retry-After: 42 header. Wait that many seconds before retrying.
See Rate Limits for limits by plan.
Gone (410)
Removed endpoints return 410 with a successor field:
{
"type": "https://problems.eunifyer.com/gone",
"title": "Gone",
"status": 410,
"detail": "This endpoint was removed on 2026-10-01. Use /api/v2/sites instead.",
"code": "gone",
"successor": "/api/v2/sites"
}Handling errors in code
async function apiFetch(path: string, token: string) {
const resp = await fetch(`https://api.eunifyer.com/api/v1${path}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!resp.ok) {
const err = await resp.json();
if (resp.status === 429) {
const retryAfter = parseInt(resp.headers.get("Retry-After") ?? "60");
throw new RateLimitError(err.detail, retryAfter);
}
throw new ApiError(err.code, err.detail, resp.status);
}
return resp.json();
}import httpx
def handle_response(r: httpx.Response) -> dict:
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", "60"))
raise RateLimitError(retry_after)
if not r.is_success:
err = r.json()
raise ApiError(code=err["code"], detail=err["detail"], status=r.status_code)
return r.json()Correlation IDs
Every response includes X-Correlation-ID. Include this header value when contacting support — it ties your request to a specific trace in EUnifyer’s logs.