{success: false} JSON envelope on a 2xx, raises a typed exception
instead of returning normally. All error classes are exported from the package root:
The error classes
Every exception is a normal Python exception:
str(err) (or err.args[0]) is the human-readable
message from the envelope, safe to log or (after your own review) surface to a user. There’s also a
__repr__ that includes message, status_code, and code.
code mirrors the backend’s own machine-readable error code (e.g. VALIDATION_ERROR,
UNAUTHORIZED, PROJECT_NOT_ACTIVE, NOT_FOUND, EXTERNAL_USER_REQUIRED) — check the
API Reference for each endpoint’s documented error responses.
What causes each error
Handling errors
except PersonaApiError alone catches all three — order
matters if you also list the subclasses first.
Common failure scenarios
Validation failure
Authentication / authorization failure
Existence-hiding 404s
get()/update()/delete() never distinguish “not found” from “not authorized” — a caller that
can’t own a resource gets the same 404 NOT_FOUND as a caller asking for a nonexistent id. This is
intentional (don’t build error-driven existence probes). Note that bulk_delete() is the exception:
it doesn’t raise on per-id failures — see Behavior & edge cases.
Rate limiting
Only429 is retried automatically (up to max_retries, honoring Retry-After). If all retries
are exhausted, the final 429 response raises PersonaApiError with status_code == 429. See
Retries and rate limits.
Non-JSON error bodies
If a failure response has no JSON body, the SDK can’t decode an envelope and raises a genericPersonaApiError with code == "NON_JSON_ERROR_RESPONSE" and the raw status code. This usually
indicates a proxy/load-balancer error page rather than an API error — check your network path.
Errors on streaming calls
chat.stream() and chat.send_message() raise the same typed errors if the server responds with a
normal JSON error envelope instead of starting the event stream (e.g. a 400 EXTERNAL_USER_REQUIRED
when no external_user_id was set, or a 404 for a mismatched thread_id). They surface as
exceptions on the generator/send_message() call — not as events inside the stream. Malformed or
partial SSE frames are skipped silently (tolerant parsing), never raised.