Status: AcceptedArea: BackendDate: 2026-07-02
Context
PolarError’s default status code is 500, and Sentry reports every 500 as a hard crash. So a
normal business conflict (say, “you already have an active subscription”) raised without an
explicit status pages on-call as if the service fell over, burying real incidents. Separately,
422 means one specific thing to clients: the request payload was malformed.
Decision
For logical and conflict errors, raisePolarError subclasses with an explicit status_code
(404, 409, 410, 403, and so on) and declare them on the endpoint’s responses= so the
generated client gets the error schema. Reserve 422 for request-payload validation: never
re-raise a business error as a validation error.
Consequences
- Clients get correct HTTP status codes, and expected conflicts stay out of the crash reporter.
- The generated TypeScript client gets typed error shapes via
PolarError.schema(). - Don’t add a content-less
422: {"description": ...}override: it clobbers FastAPI’s defaultHTTPValidationErrorand breaks the generated client.
Alternatives considered
- Let business errors fall through as bare 500s: right response body, but pages on-call and pollutes Sentry.
- Raise 422 for logical failures: misleads clients into thinking they sent bad JSON.
References
server/polar/exceptions.py(base + subclasses),server/polar/exception_handlers.py(handlers, 422 separation).- Real usage:
server/polar/checkout/service.py,server/polar/checkout/endpoints.py. server/AGENTS.md, “Errors to status-codedPolarError”.

