# SPECIFICATIONS/06_API/06_08_ERROR_CODES.md

**PURPOSE:** Canonical catalog of all error scenarios, their HTTP status codes, and translation keys. This is the single source of truth for error responses.

> **Response format for errors:** `../../CONSTITUTIONS/07_BOUNDARY_LAYERS.md` → Response Architecture
> **Translation file structure:** `../../CONSTITUTIONS/07_BOUNDARY_LAYERS.md` → Localization Confinement

---

## Response Format

```json
{
    "success": false,
    "message": "errors/general.unauthorized",
    "errors": {
        "field_name": ["errors/client.phone_unique", "errors/client.name_required"]
    }
}
```

- `message` always uses a translation key — resolved server-side before sending
- `errors` present only for validation failures (422)
- Both fields use translation keys, never hardcoded language strings
- All error keys use the `errors/{domain}.{key}` format matching the translation file structure under `lang/ar/errors/`

---

## HTTP Status Code Usage

| Code | Meaning | When Used |
|:---|---|:---|
| 400 | Bad Request | Missing or invalid data (general) |
| 401 | Unauthorized | No token, invalid token, or expired token |
| 403 | Forbidden | Authenticated but lacks permission, OR business rule violation |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Sequencing violation (LIFO deletion, no pending installments) |
| 422 | Unprocessable | Validation failure, amount mismatch, time limit |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected internal error |

---

## Error Catalog

### Authentication

| Scenario | Key | HTTP |
|:---|:---|:---|
| Missing or invalid token | `errors/general.unauthorized` | 401 |
| Invalid credentials | `errors/auth.invalid_token` | 401 |
| Login rate exceeded | `errors/auth.too_many_attempts` | 429 |

### Authorization

| Scenario | Key | HTTP |
|:---|:---|:---|
| Missing permission | `errors/general.forbidden` | 403 |

### Customers

| Scenario | Key | HTTP |
|:---|:---|:---|
| Duplicate phone | `errors/client.phone_unique` | 422 |
| Customer not found | `errors/client.not_found` | 404 |

### Agents

| Scenario | Key | HTTP |
|:---|:---|:---|
| Duplicate phone | `errors/agent.phone_unique` | 422 |
| Agent not found | `errors/agent.not_found` | 404 |

### Investors

| Scenario | Key | HTTP |
|:---|:---|:---|
| Duplicate phone | `errors/investor.phone_unique` | 422 |
| Investor not found | `errors/investor.not_found` | 404 |
| Already an investor | `errors/investor.already_investor` | 422 |
| No fields provided for update | `errors/investor.update_no_fields` | 422 |

### Shares

| Scenario | Key | HTTP |
|:---|:---|:---|
| Client not an investor | `errors/shares.not_investor` | 404 |
| Not the latest record or not active | `errors/shares.not_latest` | 403 |
| 30-day lock period expired | `errors/shares.lock_period_expired` | 403 |
| Withdrawal exceeds balance | `errors/shares.insufficient_balance` | 422 |
| Acquisition date missing | `errors/shares.acquisition_date_required` | 422 |

### Contracts

| Scenario | Key | HTTP |
|:---|:---|:---|
| Math validation failed | `errors/contract.math_validation_failed` | 422 |
| Fractional payment | `errors/contract.fractional_payment` | 422 |
| Total mismatch | `errors/contract.total_mismatch` | 422 |
| Cannot sign non-draft | `errors/contract.cannot_sign` | 403 |
| Cannot delete non-draft | `errors/contract.cannot_delete` | 403 |
| Contract immutable | `errors/contract.immutable` | 403 |
| Contract not found | `errors/contract.not_found` | 404 |

### Payments

| Scenario | Key | HTTP |
|:---|:---|:---|
| Contract not active | `errors/payment.contract_not_active` | 403 |
| No pending installments | `errors/payment.no_pending_installments` | 409 |
| Amount mismatch | `errors/payment.amount_mismatch` | 422 |
| LIFO violation (modify) | `errors/payment.lifo_violation` | 403 |
| Cannot modify completed contract payment | `errors/payment.cannot_modify_completed` | 403 |
| Amount modification attempted | `errors/payment.cannot_modify_amount` | 422 |

### Analytics

| Scenario | Key | HTTP |
|:---|:---|:---|
| Invalid date range (from > to) | `errors/analytics.invalid_date_range` | 422 |
| From date required | `errors/analytics.from_date_required` | 422 |
| To date required | `errors/analytics.to_date_required` | 422 |
| Comparison from date required | `errors/analytics.comp_from_date_required` | 422 |
| Comparison to date required | `errors/analytics.comp_to_date_required` | 422 |

### General

| Scenario | Key | HTTP |
|:---|:---|:---|
| Data integrity violation | `errors/general.data_integrity` | 500 |
| Rate limit exceeded (authenticated) | `errors/general.too_many_requests` | 429 |
| General server error | `errors/general.server` | 500 |

---

## Context-Dependent HTTP Codes

Some error keys map to different HTTP codes depending on the operation context:

| Key | Context | HTTP | Why |
|:---|:---|:---|:---|
| `errors/payment.lifo_violation` | PATCH (modify payment) | 403 | Modifying a non-latest payment is a permission-like violation |
| `errors/payment.lifo_violation` | DELETE (reverse payment) | 409 | Deleting out of order is a conflict with ledger sequence |