Skip to main content
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, raise PolarError 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 default HTTPValidationError and 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-coded PolarError”.