> For the complete documentation index, see [llms.txt](https://core-foundation-doc.rupeshstha.com.np/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://core-foundation-doc.rupeshstha.com.np/http-and-application-layer/exceptions.md).

# Exception Handling

Core Foundation provides an enterprise-grade exception system that ensures your API always returns consistent JSON, maintains security by hiding internal details in production, and simplifies debugging through correlation IDs.

## The 3-Layer System

### Layer 1: Global `ExceptionRenderer`

Registered automatically via the ServiceProvider, this layer handles all framework-level exceptions for every JSON request. You **never** need to try/catch these in your controllers.

| Exception                       | Status | `message`                                       |     `errors`    |
| ------------------------------- | :----: | ----------------------------------------------- | :-------------: |
| `ValidationException`           |   422  | Original validation message                     | Field-level bag |
| `ModelNotFoundException`        |   404  | From `core-foundation::http.not-found-record`   |       `{}`      |
| `NotFoundHttpException`         |   404  | From `core-foundation::http.not-found`          |       `{}`      |
| `MethodNotAllowedHttpException` |   405  | From `core-foundation::http.method-not-allowed` |       `{}`      |
| `AuthenticationException`       |   401  | From `core-foundation::http.unauthenticated`    |       `{}`      |
| `AuthorizationException`        |   403  | From `core-foundation::http.unauthorized`       |       `{}`      |
| `QueryException`                |   400  | Sanitized DB message                            |       `{}`      |
| `HttpException`                 | varies | Exception message or HTTP status text           |       `{}`      |
| Unhandled `Throwable`           |   500  | From `core-foundation::http.server-error`       |       `{}`      |

All error responses include the `errors` key — empty object `{}` when there are no field-level errors. This keeps the envelope shape predictable for all callers.

### Layer 2: Domain Exceptions (`BaseApiException`)

Your business logic should throw subclasses of `BaseApiException`. These exceptions are "self-rendering" — they know their own status code and message.

```php
class InsufficientFundsException extends BaseApiException
{
    protected int $status = 402;
    protected string $message = "You don't have enough balance.";
}
```

#### Structured Field Errors

You can attach field-level errors to domain exceptions, mirroring validation errors:

```php
throw new OrderException(
    message: "Validation failed.",
    errors:  ['items' => ['Minimum 1 item required.']]
);
```

### Layer 3: Controller Fallback (`handleException`)

The last resort for unexpected errors or third-party SDK exceptions.

```php
try {
    $this->service->place($data);
} catch (Throwable $e) {
    return $this->handleException($e);
}
```

## Exception ID (Correlation UUID)

When an unhandled `Throwable` reaches the fatal fallback, CoreFoundation:

1. Generates a UUID and returns it in the response as `exception_id`.
2. Logs the full structured context under that UUID.

**Support workflow:** a user reports an error and gives you the `exception_id` — grep your logs for that UUID and you have the exact request, user, and trace without asking the user to reproduce it.

## What Gets Logged

### Fatal exceptions (`Throwable` fallback)

```
ERROR  Unhandled exception.
  exception_id: 018f2a3b-...
  exception:
    class:   RuntimeException
    message: Something failed
    file:    app/Services/OrderService.php       ← relative path, IDE-clickable
    line:    42
    trace:
      - {at: app/Services/OrderService.php:42,       call: OrderService->place()}
      - {at: app/Http/Controllers/OrderController.php:28, call: OrderController->store()}
      ...                                            ← up to 20 frames
    caused_by:                                      ← present when exception wraps another
      class:   PDOException
      message: SQLSTATE[HY000] General error ...
      file:    vendor/laravel/framework/...
      line:    98
  request:
    method:  POST
    url:     https://api.example.com/api/orders
    route:   orders.store
    params:  {}
    ip:      192.168.1.1
    user_id: 42
    body:    {product_id: 123, password: "[REDACTED]"}
```

### QueryException

```
ERROR  QueryException.
  sql:      INSERT INTO `orders` (`product_id`) VALUES (?)   ← raw query, no interpolation
  bindings: [123]                                            ← paste both into DB client to replay
  exception:
    class: Illuminate\Database\QueryException
    file:  app/Repositories/OrderRepository.php
    line:  87
    trace: [...]
  request:  { method, url, route, user_id, body }
```

### 4xx exceptions

Expected flow — not logged. `ValidationException`, `AuthenticationException`, `AuthorizationException`, `ModelNotFoundException`, and `MethodNotAllowedHttpException` produce JSON responses without log entries to avoid noise.

## Sensitive Field Redaction

The following request body keys are automatically replaced with `[REDACTED]` before logging:

`password` · `password_confirmation` · `current_password` · `token` · `api_token` · `api_key` · `secret` · `secret_key` · `credit_card` · `card_number` · `cvv` · `cvc`

Only top-level keys are scanned.

## Production Security

`QueryException` always returns a sanitised message to the client — the actual SQL and error code never leave the server. The raw query and bindings go to your internal log only.

## Reusing Exception Context Builders

`ExceptionRenderer::buildExceptionContext(Throwable $e)` is `public static`. Use it anywhere you need the same structured exception data format (class, relative file:line, trace, cause chain) — for example, inside `BaseJob::logContext()`:

```php
protected function logContext(Throwable $exception): array
{
    return [
        'order_id' => $this->orderId,
    ];
}
```

The base `logFailure()` method in `BaseJob` already calls `ExceptionRenderer::buildExceptionContext()` automatically — `logContext()` only needs the domain-specific additions.
