> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wristler.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> What each status means here, and where to look in the body.

Every error is a JSON:API error document:

```json theme={null}
{
  "jsonapi": { "version": "1.0" },
  "errors": [
    {
      "status": "422",
      "title": "Unprocessable Entity",
      "detail": "The price field must be an integer.",
      "source": { "pointer": "/data/attributes/price" }
    }
  ]
}
```

`source.pointer` names the field, so a validation error can be shown against the
right input rather than as a sentence at the top of a form.

| Status | What it means here |
| - | - |
| `400` | A query parameter we do not offer, or one with a value we will not take. |
| `401` | No token, or an expired one. Refresh, then retry once. |
| `403` | The token is valid but may not do this: it lacks the scope, the business in the path did not grant your application that scope, it is a guest token, or it is not the Wristler app asking for an endpoint only the app may use. Also: a field that is ours to set, and the wrong side of a thread answering an offer. The `code` says which. Do not retry. |
| `404` | No such resource — **or** one your token may not act for. |
| `409` | A listing a Connect feed maintains (`feed_managed`; the error names the fields), and the other conflicts in the table below. |
| `415` / `406` | The media type. See [JSON:API in practice](https://docs.wristler.eu/concepts/json-api.md). |
| `422` | Validation. `source.pointer` says where. |
| `426` | The Wristler app's release is older than we still serve (`app_update_required`). Only the app is ever told this. |
| `429` | [Rate limited](https://docs.wristler.eu/concepts/rate-limits.md). Honour `Retry-After`. |
| `503` with `maintenance` | We are down for maintenance. Any endpoint can answer it, `/oauth/token` included. Wait and retry; nothing about the request or the token is wrong. |
| `5xx` | Ours. Safe to retry, and safe to retry with the same idempotency key. |

## 404 and 403 are deliberate

Something that exists but is not yours answers `404`, not `403`. The two are the
same answer on purpose: ids cannot be used to find out what exists, or to count
what somebody else has.

So a `404` on a listing you believe you own is usually not a missing listing. It is
the wrong business in the path, or a grant that was withdrawn.

> [!NOTE]
>
> `/oauth/token` is the exception to all of this, apart from `maintenance`: it follows OAuth 2.0 and answers
> with `{"error": "invalid_grant"}` and friends. An `invalid_grant` on a refresh
> means the link is broken — re-authorize rather than retrying. The app's own sign-in
> adds `mfa_required`, `invalid_otp` and `account_blocked`, and asking for too many guest tokens from one address answers `too_many_requests`; see the [app guide](https://docs.wristler.eu/app/build.md).

## Every `code` this API answers with

`errors[].code` is the stable part of a refusal: the status says what kind of thing went
wrong, and the code says which one. **Branch on the code, never on `detail`** — that
sentence is translated by `Accept-Language`, so matching on it breaks in the next
language.

A code is only ever added, never repurposed. Treat an unfamiliar one as its status class.

| `code` | Status | When |
| - | - | - |
| `account_has_open_orders` | `409` | Deleting an account with an order that has not finished. `meta.orders` names them. Somebody is waiting to send a watch, or to be paid. |
| `account_runs_a_business` | `409` | Deleting an account that runs or works at a shop. Contact us: who takes the business over is a conversation. |
| `app_update_required` | `426` | The release named in the `X-Wristler-App` header is older than the minimum. `meta` has `platform`, `installed_version`, `minimum_version` and `store_url`. Only a request that sends the header is checked, so a partner never sees it. See [Build the app](https://docs.wristler.eu/app/build.md). |
| `already_paid` | `409` | The order has been paid, or is being paid. |
| `availability_on_request` | `422` | A checkout for a watch whose availability is on request (`availabilityOnRequest`), without the shop's offer. Ask with `requestAvailability` first; the proposal that comes back opens the checkout. |
| `availability_not_on_request` | `422` | `requestAvailability` on a watch that is not on request. Ask the seller a question instead. |
| `below_minimum` | `422` | A buyer's offer under the floor this business set. A seller's own proposal is never held to it. |
| `broad_search` | `422` | Saving a search that sends notifications (by mail or push) and matched at least `meta.threshold` watches last month. `meta` has `matchingWatches`, `reachedLimit` and `expectedEmails` per interval. Send it again with `acceptsManyEmails: true` to save it anyway. |
| `buyer_account_deleted` | `422` | A shop writing about an order (`POST /v1/sellers/{id}/orders/{id}/conversation`) whose buyer has deleted their account. There is nobody to send it to. |
| `cannot_refuse` | `422` | Answering a buyer's offer with `refused`. Offers are declined in the conversation, in words or with a proposal of the seller's own. |
| `cannot_be_disabled` | `409` | A notification channel that may not be switched off. Today: both channels of `security`. |
| `cannot_block_your_own_business` | `422` | Blocking a business the caller works at. It would close their own threads from the buyer's side and leave them unable to answer from the seller's. |
| `cannot_block_your_own_staff` | `422` | A shop blocking a buyer who works at that shop. It would lock a colleague out of the shop's correspondence. |
| `cannot_message_your_own_business` | `422` | Starting a conversation about a watch listed by a business the caller works at. The watch page offers no way to message yourself either. |
| `only_the_shop` | `422` | Setting `noReplyNeeded` on a conversation from the buyer's side. |
| `too_many_quick_replies` | `422` | A shop already keeps 50 quick replies. Remove one first. |
| `unknown_provider` | `422` | Linking an identity that is not `apple` or `google`. `meta.providers` lists them. |
| `identity_not_verified` | `422` | The ID token sent to link Apple or Google did not check out: signature, audience, expiry or nonce. |
| `feed_managed` | `409` | Changing a field a Connect feed maintains, or deleting a listing a feed maintains: the next import would undo it. `detail` names the fields. See [Listings a Connect feed maintains](https://docs.wristler.eu/concepts/feed-ownership.md). |
| `idempotency_key_invalid` | `400` | An `Idempotency-Key` longer than 191 characters. See [Idempotency](https://docs.wristler.eu/concepts/idempotency.md). |
| `idempotency_key_reuse` | `422` | An `Idempotency-Key` already used for a different request. Use a new key for a new request. |
| `idempotency_in_progress` | `409` | A request with this `Idempotency-Key` is still being processed. Wait, then retry with the same key. |
| `idempotency_conflict` | `409` | This `Idempotency-Key` can no longer be used. Retry with a new one. |
| `identity_already_linked` | `409` | A different Apple ID or Google account is already linked to this account. Unlink it first. |
| `identity_in_use` | `409` | That Apple ID or Google account is linked to another Wristler account. |
| `last_way_to_sign_in` | `409` | Unlinking the only identity of an account whose address is an Apple private relay one: nothing else could reach it. |
| `client_may_not` | `403` | A first-party endpoint, asked by somebody else's application. |
| `current_sign_in` | `422` | Ending, by its id, the sign-in that is making the call. Signing out here is `DELETE /v1/me/tokens/current`. |
| `country_not_shipped_to` | `422` | A checkout address, typed or saved, in a country we do not ship to. The pointer names the field, `meta.country` the code; `GET /v1/shipping-countries` lists where we do. |
| `house_number_missing` | `422` | A saved checkout address whose `addressLine1` has no house number in it. The pointer is `/data/attributes/billingAddress/addressLine1` or `.../shippingAddress/addressLine1`; ask the buyer to fix the address (`PATCH /v1/me/addresses/{id}`). |
| `invalid_filter` | `400` | `GET /v1/me/saved-searches/preview` was sent a `filter` that is not a list of filters: words go in `filter[q]`, facets in `filter[<facet>]`. |
| `conversation_blocked` | `403` | Writing in a thread a block has closed -- the buyer blocked the business, or the shop blocked the buyer -- or starting a new one between them; also any thread of a buyer whose account Wristler blocked. `meta.blockedByYou` says whether the caller's own side did the blocking -- `true` means offer them an unblock, `false` means there is nothing for them to do. |
| `deletion_code_invalid` | `422` | The code mailed for an account deletion is wrong, missing or expired. Five wrong guesses throw it away; ask for a new one. |
| `deletion_reason_invalid` | `422` | The `reason` sent with an account deletion is not one of the listed values (`meta.allowed`). Leave it out to give none. |
| `deletion_reason_note_invalid` | `422` | The `reasonNote` is longer than `meta.max` characters. |
| `implausible_email` | `422` | An address that looks mistyped, at sign-up or when changing it: a typo of a popular mail domain, or a domain that receives no mail. `meta.suggestion` is the address probably meant (or `null`), `meta.domain` the doubtful domain. Send it again with `emailConfirmed` set to the same address to keep it. A business's `email` and `invoiceEmail` are questioned the same way, and confirmed with `emailConfirmed` and `invoiceEmailConfirmed`. An address left as it was is never questioned. |
| `invalid_price` | `400` | A fee quote (`GET /v1/sellers/{sellerId}/application-fee`) without `price`, or with one that is not a whole number of cents. |
| `invalid_period` | `400` | The dashboard (`GET /v1/sellers/{sellerId}/dashboard`) with a `period` that is not `7d`, `30d` or `year`. |
| `maintenance` | `503` | We are down for maintenance. It comes from **every** endpoint, `/oauth/token` included, before anything else is checked -- so keep the session; the token is fine. `Retry-After` and `meta.retryAfter` say how long to wait when we know it; otherwise poll `GET /` now and then. Anything but this code means we are back. |
| `listing_reserved` | `409` | Deleting a listing a buyer is paying for right now. |
| `listing_sold` | `409` | A new status for a sold listing, or deleting one. A sold listing stays sold; start a new one from it with `duplicateOf`. |
| `message_not_in_conversation` | `422` | A report naming a message that belongs to another thread. |
| `missing_scope` | `403` | The token is valid and lacks the scope -- or, for a partner, the business in the path did not grant this application that scope, whatever other businesses allowed. `meta.scopes` names what it needs. Do not retry. |
| `no_payment_instructions` | `422` | Asked where to transfer for an order that is not waiting for one. |
| `no_such_listing` | `422` | A `duplicateOf` naming a listing that is not this business's, or is not there. |
| `not_an_offer` | `422` | Answering something that is not a buyer's offer. |
| `not_yours_to_answer` | `403` | The wrong side of a thread answering an offer. |
| `offer_already_open` | `409` | This buyer already has one waiting. `meta.offer` names it. |
| `offer_closed` | `409` | Already answered or expired. `meta.status` says which. |
| `offer_refused` | `422` | An offer that cannot be used at a checkout any more. |
| `offers_not_accepted` | `409` | A buyer's offer, and this business takes no offers, or this listing takes none — in a thread or opening one. A seller's own proposal is never refused with it. |
| `order_has_no_listing` | `422` | Writing about an order that kept no listing at all, not even a deleted one -- only very old orders. Contact us instead. |
| `order_incomplete` | `422` | An order with no billing address. Start the checkout again. |
| `order_not_shippable` | `409` | Tracking for an order that is not paid, or is no longer on its way: delivered, completed, cancelled, refunded or returned. `meta.status` says which. |
| `order_closed` | `422` | Cancelled, refunded or returned. Start a new one. |
| `payment_in_progress` | `409` | At a checkout: your order for this watch already has a payment open (`meta.order` names it) -- read that order instead. When paying: a payment for this order is being opened by another request right now -- retry. |
| `payment_path_locked` | `409` | The other card path is under way. `meta.path` says which. |
| `payment_refused` | `422` | The provider refused it. `detail` is written for the buyer. |
| `price_on_request` | `422` | A checkout for a listing with no price, without an accepted offer. Ask for a price first. |
| `payment_unavailable` | `503` | The provider could not be reached, or is busy. Retry. |
| `query_too_short` | `400` | A reference search with too little to go on: `filter[q]` shorter than the minimum the `detail` names. |
| `read_only_field` | `403` | A field that is ours to set. Refused rather than ignored. |
| `reference_not_found` | `404` | `GET /v1/references/{id}` for an id the reference database does not know. |
| `references_unavailable` | `503` | The reference service could not be reached, or is rate limiting us. Try again shortly. |
| `reservation_expired` | `422` | The 48 hours the watch was held for this order have run out. The order is cancelled shortly after; start a new one if the watch is still for sale. |
| `reset_failed` | `422` | A password reset that did not go through: the token is wrong, expired or used, or does not belong to that address. |
| `seller_side_filter` | `400` | A filter that only means something on the shop's side, such as `filter[unanswered]`, without `filter[side]=seller`. |
| `saved_search_exists` | `409` | Saving a search the person already has, or changing one into it: the same filters and words. `meta.savedSearch` is its id, `meta.name` and `meta.interval` its name and interval. |
| `search_unavailable` | `503` | The search service could not be reached. Retry. |
| `search_only_filter` | `400` | A filter only a search answers, on a list that is not one: a catalogue facet on a shop's own inventory (`/v1/sellers/{id}/watches`), `filter[q]` or `filter[country]` on `/v1/sellers` from a partner, who has no directory, or `filter[q]` where nothing is searched. On the catalogue (`/v1/watches`) a facet on its own is answered as a search, never refused. `detail` says which. |
| `search_only_sort` | `400` | A sort only the catalogue search offers (`year`), on a list that is not one. |
| `seller_cannot_take_payments` | `422` | The shop has no payment account set up, so the order cannot be paid yet. Nothing has been charged; Wristler sets the shop up. Its watches show `purchasable: false` meanwhile. |
| `sign_in_required` | `403` | A guest token asked for something only a signed-in person may do. Sign in -- do **not** refresh, the token is fine. |
| `seller_mismatch` | `422` | A listing written against a business the path does not name. |
| `server_error` | `500` | Ours. Safe to retry, and safe to retry with the same idempotency key. |
| `too_many_attempts` | `429` | Out of password attempts, too many account-deletion codes asked for, or (outside production) too many test notifications. `Retry-After` and `meta.retryAfter` say how long. |
| `too_many_reports` | `429` | More reports in an hour than anybody has to make. `Retry-After` and `meta.retryAfter` say how long. |
| `upload_failed` | `502` | The storage service would not take the file, or a `sourceUrl` could not be fetched. Nothing you did; retry. |
| `unknown_event` | `422` | A test notification for an event there is none of. `meta.events` lists them. Not in production. |
| `two_factor_already_enabled` | `409` | Turning one on while there is one. Turn the old one off first. |
| `two_factor_setup_expired` | `422` | The scanned secret has lapsed, or was never ours. Start the setup again. |
| `watch_not_for_sale` | `422` | Sold, offline or withdrawn, or its business cannot take payment or is not active. Also an offer made, sent or accepted on such a watch, and a conversation started about one (`POST /v1/conversations`, pointer `/data/relationships/watch`, or `/data/attributes/priceProposal` with an opening offer). |
| `watch_reserved` | `409` | Reserved for another buyer. |

> [!NOTE]
>
> This table is checked against the code by a test, so a refusal the API can answer with and
> this page does not name is a failing build rather than something you discover at runtime.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.