# Errors (https://form.dimah.dev/docs/errors)



Failed requests return JSON with a stable `code`, an English `message`, and optional `issues`.

***

## Standard Error Response Schema [#standard-error-response-schema]

```ts
type ErrorResponseBody = {
  /** Human-readable explanation of the error */
  message: string;

  /** Stable machine-readable error code */
  code?: string;

  /** Optional parameters for message interpolation */
  params?: Record<string, string | number>;

  /** Field-level validation issues (present on VALIDATION_ERROR) */
  issues?: Array<{
    field: string;
    message: string;
    code?: string;
    params?: Record<string, string | number>;
  }>;
};
```

***

## Core Error Codes (`FORM_ERROR_CODES`) [#core-error-codes-form_error_codes]

| Error Code                       | HTTP Status | Trigger Condition                                                                  |
| :------------------------------- | :---------- | :--------------------------------------------------------------------------------- |
| **`NOT_FOUND`**                  | 404         | Requested URL route does not match any registered endpoint.                        |
| **`UNKNOWN_FORM`**               | 404         | Form ID or slug could not be found in code catalogs or database.                   |
| **`UNKNOWN_RESPONSE`**           | 404         | Response ID does not exist in the database.                                        |
| **`UNAUTHORIZED`**               | 401         | Thrown by `guard` when the user is unauthenticated.                                |
| **`FORBIDDEN`**                  | 403         | Thrown by `guard` when the user lacks permissions or ownership.                    |
| **`STALE_UPDATE`**               | 409         | `updatedAt` CAS token mismatch (concurrent write collision).                       |
| **`FORM_INACTIVE`**              | 409         | `startResponse` called on a form with status `"draft"` or `"archived"`.            |
| **`RESPONSE_NOT_DRAFT`**         | 409         | `saveDraft` or `submitResponse` attempted on a locked response.                    |
| **`RESPONSE_NOT_LOCKED`**        | 409         | `reopenResponse` attempted on an active draft response.                            |
| **`CODE_AUTHORED_FORM`**         | 409         | Overwrite (`saveForm`) or delete (`deleteForm`) attempted on a code-authored form. |
| **`FORM_HAS_RESPONSES`**         | 409         | `deleteForm` attempted on a form that already has recorded responses.              |
| **`SLUG_TAKEN`**                 | 409         | Form slug collides with an existing form.                                          |
| **`VALIDATION_ERROR`**           | 400         | Payload or answer validation failure (inspect `issues[]`).                         |
| **`UNKNOWN_FIELD_TYPE`**         | 400         | Form references a field type not registered in `fieldTypes`.                       |
| **`RESUME_REQUIRES_RESPONDENT`** | 400         | `startResponse({ resume: true })` called without a `respondentId`.                 |
| **`INTERNAL_ERROR`**             | 500         | Unhandled server exception.                                                        |

***

## Field Issue Codes (`FIELD_ISSUE_CODES`) [#field-issue-codes-field_issue_codes]

When a `400 VALIDATION_ERROR` occurs during submission, the `issues[]` array contains field-specific issue codes:

| Issue Code                  | Description                                          | Parameters |
| :-------------------------- | :--------------------------------------------------- | :--------- |
| **`REQUIRED`**              | Visible required field was left empty on submit.     | —          |
| **`UNKNOWN_FIELD`**         | Answer key does not exist in the form snapshot.      | —          |
| **`UNKNOWN_FIELD_TYPE`**    | Field type has no registered validator.              | `{ type }` |
| **`INVALID`**               | Custom validator rejected the answer.                | —          |
| **`EXPECTED_STRING`**       | Answer must be a string.                             | —          |
| **`EXPECTED_NUMBER`**       | Answer must be a finite number.                      | —          |
| **`EXPECTED_INTEGER`**      | Answer must be an integer.                           | —          |
| **`EXPECTED_BOOLEAN`**      | Answer must be a boolean (`true` or `false`).        | —          |
| **`EXPECTED_STRING_ARRAY`** | Answer must be an array of strings.                  | —          |
| **`EXPECTED_EMAIL`**        | Answer must be a valid email address.                | —          |
| **`EXPECTED_DATE`**         | Answer must be an ISO calendar date (`YYYY-MM-DD`).  | —          |
| **`TOO_SHORT`**             | Text length is below `minLength`.                    | `{ min }`  |
| **`TOO_LONG`**              | Text length exceeds `maxLength`.                     | `{ max }`  |
| **`TOO_SMALL`**             | Numeric value is below `min`.                        | `{ min }`  |
| **`TOO_LARGE`**             | Numeric value exceeds `max`.                         | `{ max }`  |
| **`INVALID_FORMAT`**        | Text does not match the configured `pattern` regex.  | —          |
| **`INVALID_OPTION`**        | Selected value is not in the allowed `options` list. | —          |
| **`DUPLICATE_OPTION`**      | `multiSelect` array contains duplicate values.       | —          |

***

## Client Error Helpers [#client-error-helpers]

`@dimah-form/react` provides helpers to map error codes to user-friendly or localized messages:

```ts
import {
  fieldIssueMap,
  formErrorMessage,
  issuesByField,
} from "@dimah-form/react";

// Convert APIError issues array into a field-id-to-message map
const issues = fieldIssueMap(apiError);
// { fullName: "Full Name is required", email: "Invalid email" }

// Extract formatted top-level error message
const message = formErrorMessage(error);
```

***

## Declaring Custom Error Codes with `defineErrorCodes` [#declaring-custom-error-codes-with-defineerrorcodes]

When authoring plugins or custom field validators, use `defineErrorCodes()` to declare type-safe custom error catalogs:

```ts
import { defineErrorCodes } from "@dimah-form/server";

export const CUSTOM_ERROR_CODES = defineErrorCodes({
  PAYMENT_REQUIRED: "Payment verification failed",
  SUBSCRIPTION_EXPIRED: "Account subscription is inactive",
});
```

***

## Next Steps [#next-steps]

<Cards>
  <Card title="Auth" href="/docs/auth" description="Learn how to throw UNAUTHORIZED and FORBIDDEN errors in guards." />

  <Card title="Protocol" href="/docs/protocol" description="Review all route endpoints and HTTP status codes." />

  <Card title="React Client" href="/docs/react" description="Inspect how useFormResponse handles field error states." />
</Cards>
