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

# Errors

> Every error code the Admin API and the token endpoint answer, and what to do about the common ones.

Every refusal says what failed and how to fix it. Act on the stable `code`,
show or log the `fix`, and quote the `requestId` when you ask Tassyir for
help.

## The shape

The Admin API answers errors as `application/problem+json`:

```json theme={null}
{
  "type": "https://developers.tassyir.io/errors/permission-denied",
  "title": "Permission denied",
  "status": 403,
  "detail": "You do not have permission: orders:read",
  "code": "PERMISSION_DENIED",
  "permission": "orders:read",
  "fix": "Request the read_orders scope in the app's manifest; it applies once the merchant consents to the new version.",
  "requestId": "a1b2c3"
}
```

| Member | Meaning |
| - | - |
| `code` | Stable and machine-readable. Act on it, never on `title` or `detail`. |
| `fix` | One sentence saying what to do, when there is one. |
| `status` | The HTTP status, also the answer's. |
| `requestId` | Names the request (also the `X-Request-Id` header). |
| `type` | `https://developers.tassyir.io/errors/<code in kebab case>`: an identifier. |
| others | What was refused: `permission`, `storeId`, `limit`, `retryAfter`, `errors`, `allowedFields`, … |

A 5xx never carries an internal message. The token endpoint is the one
exception to this shape: it answers OAuth errors ([below](#token-endpoint-errors)).

In `@tassyir/app`, an Admin API error is a `TassyirApiError` (`code`, `fix`,
`status`, `requestId`, `retryAfterSeconds`, `problem`), a token endpoint
error a `TassyirOAuthError` (its `code` is `OAUTH_` and the OAuth error in
capitals), and every other refusal a `TassyirError` with a `code` and a
`fix`. App Bridge's errors are listed in
[Embedded pages](/guides/embedded-pages#errors), the manifest's in
[The manifest](/guides/manifest#validate-before-you-upload).

## Common errors and what to do

### `VALIDATION_FAILED`

HTTP 400. A parameter or a field failed validation, or is not one the route
takes. `errors` lists each problem. Fix the request: unknown parameters are
refused on purpose, so a misspelt filter never silently returns everything.

### `INVALID_CURSOR`

HTTP 400. The `cursor` is not this list's, was edited, or was made with other
filters. Send `nextCursor` back unchanged, with the same filters; to change a
filter, start again without a cursor.

### `INVALID_FIELDS`

HTTP 400. `fields` names a field the resource does not have. `allowedFields`
lists the valid names; fields are top-level only.

### `APP_TOKEN_REQUIRED`

HTTP 401. No `Authorization: Bearer` header. Send the installation's access token.

### `APP_TOKEN_INVALID`

HTTP 401. The token is unknown, expired or revoked, or its installation or app is
no longer active. Refresh it once (the SDK does). If the refresh is refused,
the installation was uninstalled, suspended or had its scopes changed: an
embedded app gets a new token on the next page view, a headless app needs a
new install code.

### `PERMISSION_DENIED`

HTTP 403. Your installation lacks the permission the route checks (`permission`).
The `fix` names the scope: add it to the manifest, upload a version, and the
merchant must accept the update. With an online token, the person may lack
it even when your app has it.

### `STORE_SCOPE_FORBIDDEN`

HTTP 460. Your request named a store (`storeId`) outside the stores the
merchant gave your installation. Use one of its stores; list them with
`GET /stores` (`read_stores`).

### `NOT_FOUND`

HTTP 404. No such route, or no resource your token can read under that id: a
resource of another store or business, or a malformed id, reads the same.

### `RATE_LIMITED`

HTTP 429. Your installation's bucket is empty. Wait `Retry-After` seconds, then
retry; spread your calls using the `RateLimit-*` headers.

### `BUSINESS_RATE_LIMITED`

HTTP 429. All the apps of this business together are past its ceiling. Wait
`Retry-After` seconds; other apps are using the room, so slow down.

### `API_BUSY`

HTTP 503. The API was too busy to take your call and did **nothing**. Wait
`Retry-After` (1 second) and send the same call again, a write included.

### `IDEMPOTENCY_KEY_REUSED`

HTTP 422. You reused an `Idempotency-Key` with another method, path, query or
body. Use a new key for a different write.

### `IDEMPOTENCY_KEY_IN_USE`

HTTP 409. A request with the same key is still running. Wait, then retry with the
same key: you will get its answer.

### `IDEMPOTENCY_UNAVAILABLE`

HTTP 503. Tassyir cannot check idempotency keys right now, so the write was not
attempted. Retry after `Retry-After` with the same key.

### `PLAN_FEATURE_REQUIRED`

HTTP 403. The merchant's plan lacks the feature (`feature`), such as stock at a
chosen location. Do without it, or tell the merchant.

### `SUBSCRIPTION_REQUIRED`

HTTP 422. The route needs a plan, and the business has no usable subscription.
Tell the merchant.

### `ORDER_LIMIT_REACHED`

HTTP 403. The order is waiting: it is past the plan's order allowance, and
cannot be confirmed until it unlocks (`orders/updated` with `waiting`).

### `ORDER_NOT_EDITABLE`

HTTP 409. The order is dispatched, delivered or returned (`orderStatus`): it can
no longer be edited, confirmed or cancelled.

### `ORDER_STATUS_CONFLICT`

HTTP 409. The order's status does not allow this move (`orderStatus`,
`requestedStatus`): read the order again and decide.

### `MEDIA_URL_NOT_ALLOWED`

HTTP 400. An image is not on `https://cdn.tassyir.io/` and not already on the
resource (`field`, `url`). Upload it with `POST /images` first
([Products and collections](/guides/products-and-collections#bring-an-image-onto-the-cdn)).

### `APP_DATA_KEY_NOT_DECLARED`

HTTP 400. Your installation's version does not declare that key for that owner
type. Declare it under `[[data.definitions]]`, upload, and have the merchant
accept the update.

### `INTERNAL_ERROR`

HTTP 500. Tassyir's fault. Retry reads with backoff; retry writes only with the
same `Idempotency-Key`. Quote the `requestId` if it persists.

### `SERVICE_UNAVAILABLE`

HTTP 503. Something Tassyir depends on is down. Retry with backoff, writes with the
same `Idempotency-Key`.

## Every Admin API code

Generated from the OpenAPI document of API version `2026-10` that `@tassyir/app` is typed from (41 operations under `/apps/2026-10/`). An operation lists every code it can answer, per status, in that document.

| Code | HTTP | Answered by |
| - | - | - |
| [`API_BUSY`](/errors/api-busy) | 503 | every operation |
| [`APP_DATA_KEY_NOT_DECLARED`](/errors/app-data-key-not-declared) | 400 | `PUT /data/{ownerType}/{ownerId}/{key}` |
| [`APP_DATA_LIMIT_REACHED`](/errors/app-data-limit-reached) | 409 | `PUT /data/{ownerType}/{ownerId}/{key}` |
| [`APP_DATA_VALUE_INVALID`](/errors/app-data-value-invalid) | 400 | `PUT /data/{ownerType}/{ownerId}/{key}` |
| [`APP_DATA_VALUE_TOO_LARGE`](/errors/app-data-value-too-large) | 400 | `PUT /data/{ownerType}/{ownerId}/{key}` |
| [`APP_TOKEN_INVALID`](/errors/app-token-invalid) | 401 | every operation |
| [`APP_TOKEN_REQUIRED`](/errors/app-token-required) | 401 | every operation |
| [`BAD_REQUEST`](/errors/bad-request) | 400 | 16 operations |
| [`BRAND_NOT_FOUND`](/errors/brand-not-found) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`BULK_EXPORTS_NOT_CONFIGURED`](/errors/bulk-exports-not-configured) | 503 | `POST /bulk/exports` |
| [`BULK_EXPORT_IN_PROGRESS`](/errors/bulk-export-in-progress) | 409 | `POST /bulk/exports` |
| [`BULK_EXPORT_RATE_LIMITED`](/errors/bulk-export-rate-limited) | 429 | `POST /bulk/exports` |
| [`BUSINESS_RATE_LIMITED`](/errors/business-rate-limited) | 429 | every operation |
| [`CANCEL_NOTE_REQUIRED`](/errors/cancel-note-required) | 400 | `POST /orders/{id}/cancel` |
| [`CANCEL_REASON_INVALID`](/errors/cancel-reason-invalid) | 400 | `POST /orders/{id}/cancel` |
| [`COLLECTION_CONDITIONAL_LIMIT`](/errors/collection-conditional-limit) | 409 | `PATCH /collections/{id}`, `POST /collections` |
| [`COLLECTION_DEPTH_LIMIT`](/errors/collection-depth-limit) | 409 | `PATCH /collections/{id}`, `POST /collections` |
| [`COLLECTION_NOT_FOUND`](/errors/collection-not-found) | 400 | `POST /products` |
| [`COLLECTION_PARENT_CYCLE`](/errors/collection-parent-cycle) | 409 | `PATCH /collections/{id}` |
| [`COLLECTION_PARENT_NOT_FOUND`](/errors/collection-parent-not-found) | 404 | `PATCH /collections/{id}`, `POST /collections` |
| [`COLLECTION_PRODUCT_WRONG_STORE`](/errors/collection-product-wrong-store) | 400 | `PATCH /collections/{id}/products` |
| [`COLLECTION_RULE_INVALID`](/errors/collection-rule-invalid) | 400 | `PATCH /collections/{id}`, `POST /collections` |
| [`DELIVERY_SPEED_UNAVAILABLE`](/errors/delivery-speed-unavailable) | 400 | `PATCH /orders/{id}`, `POST /orders` |
| [`DELIVERY_SPEED_UNSUPPORTED`](/errors/delivery-speed-unsupported) | 400 | `PATCH /orders/{id}` |
| [`DEV_STORE_ORDER_LIMIT_REACHED`](/errors/dev-store-order-limit-reached) | 429 | `POST /orders` |
| [`FORBIDDEN`](/errors/forbidden) | 403 | `GET /inventory-levels`, `GET /inventory-levels/{id}`, `POST /inventory-levels/{id}/adjust`, `POST /orders` |
| [`HTML_NOT_ALLOWED`](/errors/html-not-allowed) | 400 | `PATCH /collections/{id}`, `PATCH /products/{id}`, `POST /collections`, `POST /products` |
| [`IDEMPOTENCY_KEY_INVALID`](/errors/idempotency-key-invalid) | 400 | 18 operations |
| [`IDEMPOTENCY_KEY_IN_USE`](/errors/idempotency-key-in-use) | 409 | 18 operations |
| [`IDEMPOTENCY_KEY_REUSED`](/errors/idempotency-key-reused) | 422 | 18 operations |
| [`IDEMPOTENCY_UNAVAILABLE`](/errors/idempotency-unavailable) | 503 | 18 operations |
| [`IMAGE_FETCH_FAILED`](/errors/image-fetch-failed) | 400 | `POST /images` |
| [`IMAGE_QUOTA_REACHED`](/errors/image-quota-reached) | 429 | `POST /images` |
| [`IMAGE_TOO_LARGE`](/errors/image-too-large) | 400 | `POST /images` |
| [`IMAGE_TYPE_NOT_ALLOWED`](/errors/image-type-not-allowed) | 400 | `POST /images` |
| [`IMAGE_URL_NOT_ALLOWED`](/errors/image-url-not-allowed) | 400 | `POST /images` |
| [`INSUFFICIENT_AT_LOCATION`](/errors/insufficient-at-location) | 409 | `POST /inventory-levels/{id}/adjust` |
| [`INTERNAL_ERROR`](/errors/internal-error) | 500 | every operation |
| [`INVALID_CURSOR`](/errors/invalid-cursor) | 400 | 10 operations |
| [`INVALID_FIELDS`](/errors/invalid-fields) | 400 | 37 operations |
| [`LOCATION_NOT_FOUND`](/errors/location-not-found) | 400, 404 | `POST /inventory-levels/{id}/adjust` |
| [`MEDIA_URL_NOT_ALLOWED`](/errors/media-url-not-allowed) | 400 | `PATCH /collections/{id}`, `PATCH /products/{id}`, `PATCH /products/{id}/variants/{variantId}`, `POST /collections`, `POST /products` |
| [`NOT_FOUND`](/errors/not-found) | 404 | 28 operations |
| [`ORDER_DUPLICATE`](/errors/order-duplicate) | 409 | `POST /orders` |
| [`ORDER_LIMIT_REACHED`](/errors/order-limit-reached) | 403 | `POST /orders/{id}/confirm` |
| [`ORDER_NOT_EDITABLE`](/errors/order-not-editable) | 409 | `PATCH /orders/{id}`, `POST /orders/{id}/cancel`, `POST /orders/{id}/confirm`, `POST /orders/{id}/schedule` |
| [`ORDER_PRICING_LOCKED`](/errors/order-pricing-locked) | 400 | `PATCH /orders/{id}` |
| [`ORDER_STATUS_CONFLICT`](/errors/order-status-conflict) | 409 | `POST /orders/{id}/cancel`, `POST /orders/{id}/confirm`, `POST /orders/{id}/schedule` |
| [`PAYLOAD_TOO_LARGE`](/errors/payload-too-large) | 413 | 15 operations |
| [`PERMISSION_DENIED`](/errors/permission-denied) | 403 | 40 operations |
| [`PLAN_FEATURE_REQUIRED`](/errors/plan-feature-required) | 403 | `GET /inventory-levels`, `GET /inventory-levels/{id}`, `POST /inventory-levels/{id}/adjust` |
| [`PRODUCT_DEFAULT_VARIANT`](/errors/product-default-variant) | 409 | `PATCH /products/{id}/variants/{variantId}` |
| [`PRODUCT_OPTIONS_REQUIRE_VARIANTS`](/errors/product-options-require-variants) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_OPTION_DUPLICATE_NAME`](/errors/product-option-duplicate-name) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_OPTION_DUPLICATE_VALUE`](/errors/product-option-duplicate-value) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_OPTION_NOT_FOUND`](/errors/product-option-not-found) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_OPTION_NO_VALUES`](/errors/product-option-no-values) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_OPTION_VALUE_EMPTY`](/errors/product-option-value-empty) | 400 | `PATCH /products/{id}` |
| [`PRODUCT_OPTION_VALUE_NOT_FOUND`](/errors/product-option-value-not-found) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_SLUG_INVALID`](/errors/product-slug-invalid) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_SLUG_TAKEN`](/errors/product-slug-taken) | 409 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_COMBINATION_MISMATCH`](/errors/product-variant-combination-mismatch) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_COUNT_MISMATCH`](/errors/product-variant-count-mismatch) | 400 | `PATCH /products/{id}`, `PATCH /products/{id}/variants/{variantId}`, `POST /products` |
| [`PRODUCT_VARIANT_DUPLICATE_COMBINATION`](/errors/product-variant-duplicate-combination) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_DUPLICATE_OPTION`](/errors/product-variant-duplicate-option) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_INVALID_VALUE`](/errors/product-variant-invalid-value) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_MISSING_COMBINATIONS`](/errors/product-variant-missing-combinations) | 400 | `PATCH /products/{id}`, `PATCH /products/{id}/variants/{variantId}`, `POST /products` |
| [`PRODUCT_VARIANT_MISSING_OPTION`](/errors/product-variant-missing-option) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_SELECTED_COUNT`](/errors/product-variant-selected-count) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_VARIANT_UNKNOWN_OPTION`](/errors/product-variant-unknown-option) | 400 | `PATCH /products/{id}`, `POST /products` |
| [`PRODUCT_WRITE_TIMEOUT`](/errors/product-write-timeout) | 400 | `PATCH /products/{id}`, `PATCH /products/{id}/variants/{variantId}`, `POST /products` |
| [`RATE_LIMITED`](/errors/rate-limited) | 429 | every operation |
| [`SERVICE_UNAVAILABLE`](/errors/service-unavailable) | 503 | every operation |
| [`STORE_REQUIRED`](/errors/store-required) | 400 | `POST /orders` |
| [`STORE_SCOPE_FORBIDDEN`](/errors/store-scope-forbidden) | 460 | 15 operations |
| [`SUBSCRIPTION_REQUIRED`](/errors/subscription-required) | 422 | `GET /inventory-levels`, `GET /inventory-levels/{id}`, `POST /inventory-levels/{id}/adjust` |
| [`UNSUPPORTED_MEDIA_TYPE`](/errors/unsupported-media-type) | 415 | 15 operations |
| [`VALIDATION_FAILED`](/errors/validation-failed) | 400 | 40 operations |

## Token endpoint errors

`POST /apps/oauth/token` answers OAuth errors, `{ "error": "…", "error_description": "…" }`, never problem+json.

| `error` | HTTP |
| - | - |
| `invalid_client` | 401 |
| `invalid_grant` | 400 |
| `invalid_request` | 400, 413 |
| `slow_down` | 429 |
| `temporarily_unavailable` | 503 |
| `unauthorized_client` | 400 |
| `unsupported_grant_type` | 400 |


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