Errors
The API does not yet expose one consistent documented error model for every operation. Explicit FastAPI HTTPException responses usually use {"detail":"…"}; request validation uses FastAPI's structured HTTPValidationError; provider and internal failures vary. Handle the HTTP status before relying on a body shape.
Verified errors
| Status | Verified current detail or behavior | Recovery |
| --- | --- | --- |
| 401 | Missing Authorization header, Invalid Authorization format, or Invalid API key. Firebase fallback can also return an Invalid token: … detail for a non-API Bearer token. | Send Authorization: Bearer $IVORLEAF_API_KEY with an issued ivl_ key. |
| 403 | API key revoked, API key expired, API client is not allowed for this key, or Forbidden for a session owned by another identity. | Replace/renew the key, use an allowed client, or correct session ownership. |
| 404 | Session not found, Not Found, Action not found, or Artifact not found, depending on the route. | Verify the identifier and use a workflow action from the published catalog. |
| 422 | FastAPI request validation failure described by HTTPValidationError. | Correct path parameters or JSON using the API Reference. |
| 429 | Request-rate, monthly session, monthly workflow, or provider rate limit. | Inspect usage; retry only transient provider/request limits. |
| 500 | Unexpected tutor failure uses Internal Server Error; persistence and some history paths can return other details. | Preserve context, retry transient failures with backoff, and contact support if repeated. |
| 502 | Verified provider/model-generation failures use route-specific details such as AI provider error. Try again shortly. | Retry with bounded backoff; contact support if persistent. |
Validation and workflow blocking
Human review is not consistently represented as an HTTP error. Orchestration can return success with an action marked status: "skipped" and a warning. Implementation packages can return preparation/review status in their content. Inspect validation metadata even after a 2xx response.
Defensive handler
const text = await response.text();
let body: unknown;
try { body = text ? JSON.parse(text) : null; } catch { body = text; }
if (!response.ok) {
throw new Error(`Ivorleaf HTTP ${response.status}: ${JSON.stringify(body)}`);
}