111 lines
5.2 KiB
Markdown
111 lines
5.2 KiB
Markdown
---
|
|
title: Error Codes
|
|
tags: [api, errors, reference]
|
|
---
|
|
|
|
# Error Codes
|
|
|
|
This page documents the error shape returned by the AMN backend and the HTTP status mapping used across services.
|
|
|
|
## Standard error shape
|
|
|
|
The canonical helper is `ResponseHandler.error` in [`backend/src/shared/utils/response-handler.ts`](../../backend/src/shared/utils/response-handler.ts). Modern routes return:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Validation failed",
|
|
"error": "Validation Error",
|
|
"statusCode": 400,
|
|
"timestamp": "2026-05-23T10:00:00.000Z",
|
|
"path": "/api/auth/register",
|
|
"method": "POST",
|
|
"data": [ /* field-level details for validation errors */ ]
|
|
}
|
|
```
|
|
|
|
Uncaught errors are formatted by [`shared/middleware/errorHandler.ts`](../../backend/src/shared/middleware/errorHandler.ts):
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Internal Server Error",
|
|
"statusCode": 500,
|
|
"stack": "..." // only in NODE_ENV=development
|
|
}
|
|
```
|
|
|
|
Legacy routes (chiefly `/api/users` legacy paths, `/api/marketplace` legacy paths, `/api/payment/decentralized/*`, parts of `/api/payment/shkeeper/*`) return ad-hoc shapes such as `{ "error": "..." }` or `{ "success": false, "message": "..." }`. Treat any non-`2xx` response as an error and read whichever of `error` / `message` is present.
|
|
|
|
## HTTP status mapping
|
|
|
|
| Status | When | Examples |
|
|
| --- | --- | --- |
|
|
| `200 OK` | Successful read or mutation | Most `GET`s, idempotent `PUT`s/`PATCH`s |
|
|
| `201 Created` | Resource created | `POST /api/marketplace/purchase-requests`, `POST /api/auth/register` (when user created), `POST /api/marketplace/reviews` |
|
|
| `202 Accepted` | Async accepted (provider webhooks) | SHKeeper webhook acknowledgement |
|
|
| `204 No Content` | Mutations with no body to return | Rare — most endpoints return the updated object |
|
|
| `400 Bad Request` | Validation failure, malformed input | `express-validator` errors, bad MongoIds, missing fields |
|
|
| `401 Unauthorized` | Missing or invalid JWT | `Access token required`, `Invalid or expired token` |
|
|
| `403 Forbidden` | Authenticated but not allowed | Role check failed, email not verified, ownership check failed |
|
|
| `404 Not Found` | Resource (or route) missing | `notFoundHandler`, `Resource not found` from `ResponseHandler.notFound` |
|
|
| `409 Conflict` | Duplicate / state collision | `USER_EXISTS`, duplicate review, dispute already open |
|
|
| `423 Locked` | Account temporarily locked | After repeated failed logins (Redis-tracked) |
|
|
| `429 Too Many Requests` | Rate limit hit | Currently issued only by per-feature Redis limits (auth / AI); global limiter is disabled |
|
|
| `500 Internal Server Error` | Unhandled exception | Caught by `errorHandler`; included stack trace in dev |
|
|
| `502 Bad Gateway` | Upstream provider failure | OpenAI / SHKeeper unreachable |
|
|
|
|
## Application error codes
|
|
|
|
The `error` field of the response envelope contains a human-readable category. Currently used codes:
|
|
|
|
| Code | Meaning | Returned by |
|
|
| --- | --- | --- |
|
|
| `Validation Error` | `express-validator` rejected the body | All `*Validation` middlewares |
|
|
| `Not Found` | Generic resource lookup miss | `ResponseHandler.notFound` |
|
|
| `Unauthorized` | No token / bad token | `authenticateToken`, `ResponseHandler.unauthorized` |
|
|
| `Forbidden` | Role/ownership check failed | `authorizeRoles`, `ResponseHandler.forbidden` |
|
|
| `Internal Server Error` | Catch-all 500 | `ResponseHandler.internalError`, `errorHandler` |
|
|
| `USER_EXISTS` | Email already registered | `POST /api/auth/register` |
|
|
|
|
For auth-specific 4xx responses the body's `message` carries the user-facing text (often Persian/Farsi for legacy reasons):
|
|
|
|
- `"کاربری با این ایمیل قبلاً ثبتنام کرده است"` - email already in use (409)
|
|
- `"کد تحویل نادرست است"` - wrong delivery code (400)
|
|
- `"شما مجاز به ایجاد کد تحویل برای این درخواست نیستید"` - not the buyer (403)
|
|
|
|
## Mongoose-specific mappings
|
|
|
|
Handled in `errorHandler`:
|
|
|
|
| Mongoose error | Mapped status | Body `message` |
|
|
| --- | --- | --- |
|
|
| `ValidationError` | 400 | `Validation Error` |
|
|
| `MongoError` code `11000` | 409 | `Duplicate resource` |
|
|
| `JsonWebTokenError` | 401 | `Invalid token` |
|
|
| `TokenExpiredError` | 401 | `Token expired` |
|
|
|
|
## Webhook-specific
|
|
|
|
| Provider | Endpoint | Status on success | Status on signature mismatch |
|
|
| --- | --- | --- | --- |
|
|
| SHKeeper pay-in | `POST /api/payment/shkeeper/webhook` | 200 `{ success: true }` | 401 `{ success: false }` (then ignored) |
|
|
| SHKeeper payout | `POST /api/payment/shkeeper/payout/webhook` | 200 / 400 with `{ success, message, data }` | 400 |
|
|
| Generic payment callback | `POST /api/payment/callback` | 200 `{ success: true, message }` | 400 |
|
|
|
|
If a webhook is acknowledged with non-2xx, the provider re-delivers (SHKeeper retries every 60 seconds).
|
|
|
|
## Client guidance
|
|
|
|
1. Always parse `response.json()` even on non-2xx — both `message` and `error` are useful for UX surface text.
|
|
2. For optimistic UI, treat `409` as "your action raced — refresh".
|
|
3. For `401`, attempt one silent refresh-token call before redirecting to sign-in.
|
|
4. For `403`, do not retry — the user lacks permission.
|
|
5. For `423`, surface the lockout window from the `message`/`data` and direct the user to password reset.
|
|
|
|
## Related
|
|
|
|
- [[API Overview]]
|
|
- [[Authentication API]]
|
|
- [[Rate Limiting]]
|