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



Failed HTTP requests return:

```ts
type SurveyErrorBody = {
  message: string;
  code?: SurveyErrorCode;
  questions?: string[];
};
```

Throw with `APIError.from` and a code from `SURVEY_ERROR_CODES`. A plain
`Error` is normalized to `INTERNAL_ERROR`. Treat `code` as the stable contract;
messages are for diagnostics.

```ts
import {
  APIError,
  SURVEY_ERROR_CODES,
  isAPIError,
} from "@dimah-survey/server";

throw APIError.from("NOT_FOUND", SURVEY_ERROR_CODES.SURVEY_NOT_FOUND);
throw APIError.from("FORBIDDEN", SURVEY_ERROR_CODES.FORBIDDEN);
throw APIError.from("CONFLICT", SURVEY_ERROR_CODES.STALE_UPDATE);

if (isAPIError(error)) {
  console.log(error.code, error.message);
}
```

`APIError` and `isAPIError` are also exported from `@dimah-survey/core`. There
is no `UNAUTHORIZED` code; guards reject callers with `FORBIDDEN`.

## Codes [#codes]

| Code                 | HTTP | Meaning                                                          |
| -------------------- | ---- | ---------------------------------------------------------------- |
| `NOT_FOUND`          | 404  | No route for the requested operation on this audience            |
| `SURVEY_NOT_FOUND`   | 404  | Survey id or slug is missing, or no active published read exists |
| `RESPONSE_NOT_FOUND` | 404  | Response id is missing                                           |
| `FORBIDDEN`          | 403  | Guard rejected the caller or ownership check failed              |
| `NOT_PUBLISHED`      | 409  | Start or resume has no eligible published document               |
| `SURVEY_CLOSED`      | 409  | Start, partial save, or submit is outside the collection window  |
| `RESPONSE_LIMIT`     | 409  | Submitted rows reached `maxResponses`                            |
| `SLUG_TAKEN`         | 409  | Another survey owns the slug                                     |
| `STALE_UPDATE`       | 409  | `expectedUpdatedAt` no longer matches                            |
| `RESPONSE_CLOSED`    | 409  | Response state forbids the write or reopen is disabled           |
| `OPEN_DRAFT`         | 409  | Another draft already exists for this survey and respondent      |
| `VALIDATION_ERROR`   | 400  | Request body or query failed schema validation                   |
| `VALIDATION_FAILED`  | 400  | The response definition rejected answer data                     |
| `INTERNAL_ERROR`     | 500  | An unhandled exception reached the handler                       |

`SURVEY_NOT_FOUND` on `getPublishedSurvey` also covers a draft or archived
survey. The editor `getSurvey` uses the same code when the id is missing, and
returns the draft when the row exists.

## SurveyJS validation failures [#surveyjs-validation-failures]

`VALIDATION_FAILED` adds `questions`: the SurveyJS question names that failed.
The message lists the same names and is not localized.

```json
{
  "message": "Survey result is invalid: name, score",
  "code": "VALIDATION_FAILED",
  "questions": ["name", "score"]
}
```

`VALIDATION_ERROR` is a bad request body or query, such as a settings object
whose `closesAt` is not after `opensAt`. It has no `questions` array.

A resubmit that fails validation on an already submitted row is
`RESPONSE_CLOSED`, not `VALIDATION_FAILED`. The row is already closed. See
[Responses](https://survey.dimah.dev/docs/responses.md).
