# Mapping Errors to Status Codes

Every error has a matching HTTP status code, which the `httpapi` package derives from the kind of the error.

To get the status code that matches an error, call the `StatusFor` function:

```go
status := httpapi.StatusFor(err)
```

It checks the categories in this order:

| Error | Status code |
|---|---|
| `nil` | `200 OK` |
| `httpapi.ErrUnauthorized` | `401 Unauthorized` |
| `httpapi.ErrForbidden` | `403 Forbidden` |
| `httpapi.ErrTooLarge` | `413 Request Entity Too Large` |
| `httpapi.ErrUnsupportedMediaType` | `415 Unsupported Media Type` |
| `httpapi.ErrMalformed` | `400 Bad Request` |
| `httpapi.ErrNotFound`, `query.ErrNoItems` | `404 Not Found` |
| `architecturekit.ErrDomain` | `422 Unprocessable Entity` |
| `architecturekit.ErrOutcomeUnknown` | `500 Internal Server Error` |
| `architecturekit.ErrNotCaughtUp` | `500 Internal Server Error` |
| `architecturekit.ErrConflict` | `409 Conflict` |
| `architecturekit.ErrTransient` | `503 Service Unavailable` |
| `context.Canceled` | `499 Client Closed Request` |
| `context.DeadlineExceeded` | `503 Service Unavailable` |
| `architecturekit.ErrPermanent` | `500 Internal Server Error` |
| `architecturekit.ErrNotARevision`, `architecturekit.ErrEmptyRange` | `400 Bad Request` |
| any other error | `500 Internal Server Error` |

*Note that `context.Canceled` means that the caller went away before it got an answer. HTTP has no status code for that, so `499` is the one that nginx introduced, and which logs and metrics commonly know. Since nothing failed, it is not logged. Its message is `request canceled`, because the error may name internals, such as the subject that was read.*

*Note that `ErrOutcomeUnknown` means that a write may or may not have stored its events (see **[Handling Errors](/docs/architecturekit/v2.0/handling-errors)**). So it comes before `ErrConflict`, `ErrTransient`, and the end of the context, and an error that wraps one of them as well is answered with `500 Internal Server Error` all the same: trying again, which `409` and `503` invite, may store the events twice, and `499` is not logged. Its message is `outcome unknown: the request may have succeeded`, so that the caller does not simply try again, while the error, which may name internals, such as the subject, is logged at level `Error`. Only if it comes from the function that returns a command, from the one that returns a query, or from the one that determines the user, the command or the query has not run, so its message is `internal server error`, as for any other internal failure. `Handle`, `Ask`, and `UserOf` return such an error marked for that, with the same text, which `errors.Is` and `errors.As` see through.*

*Note that `ErrNotCaughtUp` means that a write has succeeded, but that a view did not catch up with it (see **[Waiting for Written Events](/docs/architecturekit/v2.0/reading-your-own-writes#waiting-for-written-events)**). Trying again would store the events twice, so it comes right after `ErrOutcomeUnknown`, and is answered with `500 Internal Server Error` the same way, also if it wraps `ErrConflict`, `ErrTransient`, `ErrPermanent`, or the end of the context as well, as it does when one of them kept the view from catching up. Its message is `the request succeeded, but its result is not visible yet`, while the error is logged at level `Error`. A handler that waits after a command usually answers it with success, though, since the write has succeeded. Only if it comes from the function that returns a command, from the one that returns a query, or from the one that determines the user, the command or the query has not run, so its message is `internal server error`, and `Handle`, `Ask`, and `UserOf` return it marked for that, as they do for `ErrOutcomeUnknown`.*

*Note that `ErrNotARevision` means that a value that was handed over is not a revision, such as a bound of `Read`, the event ID of `OnEventID`, a value for `CompareRevisions` or `ParseRevision`, or the revision a view is to wait for, which usually comes from the request. So it is answered with `400 Bad Request` and the error as the message, like any other mistake in the request, also if it is the function that answers a query that finds it. An ID that the server stored or made itself and that is broken is a failure of the server, though, so an error of the category `ErrPermanent` is answered with `500 Internal Server Error`, even if it wraps `ErrNotARevision` as well. Both come last, so that an error that belongs to another category as well keeps its status code.*

*Note that `ErrEmptyRange` means that the bounds of `Read` leave no room for any event, which usually come from the request as well. So it is answered the same way, with `400 Bad Request` and the error as the message, such as `empty range: no event can lie before "0"`, unless the error belongs to the category `ErrPermanent` as well (see **[Reading Events](/docs/architecturekit/v2.0/reading-events)**).*

*Note that an error of the function that returns a command, of the one that returns a query, or of the one that determines the user keeps its status code only if it has one of its own, belongs to the category `ErrPermanent`, or wraps `ErrOutcomeUnknown` or `ErrNotCaughtUp`. Any other error is answered with `400 Bad Request` for the first two, and with `401 Unauthorized` for the last (see **[Authorizing Commands](/docs/architecturekit/v2.0/handling-commands-over-http#authorizing-commands)** and **[Setting Up an HTTP API](/docs/architecturekit/v2.0/setting-up-an-http-api)**).*

*Note that a panic while a route handles a request is answered with `500 Internal Server Error` as well (see **[Handling Commands over HTTP](/docs/architecturekit/v2.0/handling-commands-over-http)**).*

*Note that the status code says nothing about what to tell the caller. If you answer in a format of your own, leave out the error for `401`, `409`, `499`, and `500` and above, as `Respond`, `RespondResult`, and `RespondError` do, since it may name internals, say `not found` for `query.ErrNoItems`, tell the caller for `ErrOutcomeUnknown` that the request may have succeeded, and for `ErrNotCaughtUp` that it has succeeded, but that its result is not visible yet (see **[Handling Commands over HTTP](/docs/architecturekit/v2.0/handling-commands-over-http)**). `Respond`, `RespondResult`, and `RespondError` answer such an error from before the command or the query with `internal server error` instead, by the mark that `Handle`, `Ask`, and `UserOf` give it. Only they can tell it apart, so in a format of your own, answer both kinds the same way, or answer failures with `RespondError`.*
