Errors
Every error response has error.code. The set is closed — if you see a code not on this list, treat it as a bug.
Codes
error.code | HTTP | When |
|---|---|---|
BAD_REQUEST | 400 | Malformed body, invalid query params, type validation failure |
UNAUTHORIZED | 401 | No session, expired token, invalid bearer |
PAYMENT_REQUIRED | 402 | No active subscription required by the endpoint |
FORBIDDEN | 403 | Authenticated but lacking required role |
NOT_FOUND | 404 | Resource doesn't exist or is hidden from caller |
CONFLICT | 409 | Uniqueness violation (duplicate slug, duplicate review) |
UNPROCESSABLE_ENTITY | 422 | Logical / semantic failure (e.g. release on rolling metric, invalid returnUrl) |
QUOTA_EXCEEDED | 429 | Consume would exceed effective quota |
INTERNAL_ERROR | 500 | Unhandled exception |
SERVICE_UNAVAILABLE | 503 | Downstream dependency down (Stripe, DB transient failure) |
Endpoint-specific details
| Code | Endpoint | error.details shape |
|---|---|---|
UNAUTHORIZED | any | { signInUrl, signUpUrl } |
QUOTA_EXCEEDED | POST /me/agents/.../consume | { remaining, resetsAt, effectiveQuota } |
PAYMENT_REQUIRED | usage endpoints | { tierUpgradeUrl } |
INVALID_RETURN_URL (subset of 422) | checkout | { allowedOrigins[] } |
CONFLICT | review POST | { existingReviewId } |
Validation errors
class-validator is wired into the global ValidationPipe. When a body fails class-validator constraints, the response is 400 BAD_REQUEST with error.details: { errors: [{ field, constraints }] }.
Recovery hints
| Code | Caller should… |
|---|---|
| 401 | Redirect user to details.signInUrl |
| 402 | Redirect user to /pricing/<agent-slug> |
| 403 | Tell user they don't have permission. Don't auto-elevate. |
| 404 | Treat as "not present", don't retry |
| 409 | Show conflict to user, let them resolve |
| 422 | Show server message to user — it's specific |
429 (QUOTA_EXCEEDED) | Show upgrade CTA with details.resetsAt if rolling |
| 503 | Retry with backoff (max 3 attempts) |
How errors are produced
In NestJS controllers/services, throwing HttpException (or a subclass like ForbiddenException) is intercepted by ErrorHandlingInterceptor, which maps to one of the codes above. Custom error codes live in src/common/errors/.
throw new HttpException(
{ code: "QUOTA_EXCEEDED", message: "...", details: { remaining: 0, ... } },
HttpStatus.TOO_MANY_REQUESTS,
);