# Errors (https://v1.s3.dimah.dev/docs/server/errors)

All failed requests return JSON `{ message, code?, params? }` and standard HTTP status codes.

Use the `errors` helper to throw standard library errors. A plain `Error` has
no stable `code` — in guards it becomes a generic 403:

```ts
import { APIError, errors, isAPIError, isS3ErrorCode } from "@dimah-s3/server";

throw new Error("Not allowed"); // [!code --]
throw errors.unauthorized(); // [!code ++]
throw errors.forbidden();
throw errors.objectNotFound();
throw errors.payloadTooLarge();
throw errors.quotaExceeded();
throw errors.featureDisabled("download");
throw errors.unknownRoute("avatar");
throw errors.fileTypeNotAllowed("archive.zip", "application/zip");

throw new APIError("BAD_REQUEST", {
  message: "Invalid file payload.",
});
```

Catch and inspect errors:

```ts
if (isAPIError(err)) {
  console.log(err.status, err.statusCode, err.code, err.message);
}

if (isS3ErrorCode(err, "OBJECT_NOT_FOUND")) {
  // Handle missing object specifically
}
```

---

## Error catalog [#error-catalog]

| `code`                   | HTTP | Trigger                                                                        |
| ------------------------ | ---- | ------------------------------------------------------------------------------ |
| `NOT_FOUND`              | 404  | Unknown API path                                                               |
| `UNKNOWN_ROUTE`          | 404  | Target `route` is not registered on the server (`params.route`)                |
| `FEATURE_DISABLED`       | 404  | Target operation (`upload`, `download`, `delete`) is not enabled on this route |
| `OBJECT_NOT_FOUND`       | 404  | Object key or multipart upload does not exist                                  |
| `UNAUTHORIZED`           | 401  | Thrown by your auth guards (`errors.unauthorized()`)                           |
| `FORBIDDEN`              | 403  | Guard rejection or generic unhandled `Error` inside guards                     |
| `CONFLICT`               | 409  | Resource conflict in lifecycle hooks                                           |
| `PAYLOAD_TOO_LARGE`      | 413  | File size exceeds route `maxFileSize` (at presign, part upload, or HeadObject) |
| `QUOTA_EXCEEDED`         | 413  | `db()` or `createQuotaGuard` rejects a scope's byte or file-count projection   |
| `FILE_TYPE_NOT_ALLOWED`  | 400  | File type not allowed by route `fileTypes`                                     |
| `VALIDATION_ERROR`       | 400  | Invalid payload shape or malformed checksum                                    |
| `INVALID_KEY`            | 400  | Unsafe key or key outside route `keyPrefix`                                    |
| `MULTIPART_PART_MISSING` | 400  | Complete payload references missing part numbers                               |
| `S3_NETWORK_ERROR`       | 502  | S3 storage service unreachable                                                 |
| `INTERNAL_ERROR`         | 500  | Unhandled server exception or failed `on*` hook                                |

## Frequently asked questions [#frequently-asked-questions]

<Accordions>

<Accordion title="How do I display friendly error messages on the client?">

Use `useFormatDimahError` from `@dimah-s3/react` to map error codes to localized user-facing strings:

```tsx
import { isAPIError, useFormatDimahError } from "@dimah-s3/react";

const formatError = useFormatDimahError();

try {
  await download(key);
} catch (err) {
  if (isAPIError(err)) {
    toast.error(formatError(err));
  }
}
```

</Accordion>

</Accordions>
