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

# Errors

> Every BrandWallet API error returns a JSON envelope with a stable machine-readable token in the message field. Reference all error codes here.

**The BrandWallet API uses standard HTTP status codes, and every error response carries the same JSON envelope with a stable, machine-readable token in `message`. Match on the token in your integration code — tokens do not change between API releases.**

Error envelope:

```json theme={null}
{
  "statuscode": 400,
  "errorcode": 400,
  "message": "invalid_campaign",
  "description": "",
  "timestamp": 1754040000000,
  "path": "/v1/customers/register",
  "method": "POST"
}
```

| Field | Type | Description |
| - | - | - |
| `statuscode` | number | HTTP status code returned by the API. |
| `errorcode` | number | Internal error code; matches `statuscode` in most cases. |
| `message` | string | Stable, machine-readable error token. Use this field in your error handling — it does not change between API versions. |
| `description` | string | Optional human-readable detail. May be empty. |
| `timestamp` | number | Unix epoch timestamp in milliseconds. |
| `path` / `method` | string | The request that triggered the error. |

> Token naming: auth and infrastructure tokens are underscore-prefixed (`_invalid_credentials`, `_jwt_expired`); domain tokens on the `/v1` endpoints are plain (`customer_not_found`, `invalid_campaign`). Always match the exact string.

### Auth tokens

| Token | Status | Meaning |
| - | - | - |
| `_invalid_payload` | 400 | Missing `x-api-key` or `x-api-secret` header on `/auth/token`. |
| `_invalid_credentials` | 401 | The API key / secret pair is not valid. |
| `_invalid_refresh_token` | 400 | The refresh token is unknown, expired, or already rotated. |
| `_jwt_expired` | 401 | The access token has expired. Refresh or request a new one. |
| `_jwt_malformed` | 401 | The access token is not a valid JWT. |

### List / filter tokens

| Token | Status | Meaning |
| - | - | - |
| `invalid_filter_field` | 400 | The `f[...]` field is not filterable on this endpoint. The description lists the allowed fields. |
| `invalid_filter_value` | 400 | The filter value is malformed (wrong type, empty, or below the minimum term length). |
| `invalid_limit` | 400 | `limit` is not an integer between 1 and 250. |
| `invalid_cursor` | 400 | `cursor` is not a cursor returned in `meta`. |
| `invalid_filter_op` | 400 | Unknown filter operator name (allowed: `gt`, `gte`, `lt`, `lte`, on endpoints that support operators). |

### Customer tokens

| Token | Status | Meaning |
| - | - | - |
| `customer_not_found` | 400 | No customer matches the given search term or id. |
| `existing_customer_with_email` | 400 | Another customer of the organization already has this email. |
| `existing_customer_with_phone` | 400 | Another customer of the organization already has this phone number. |
| `opaque_id_already_exists` | 400 | Another customer of the organization already has this `opaqueId`. |
| `invalid_phone` | 400 | The phone number could not be parsed into E.164 form. |
| `invalid_campaign` | 400 | No published campaign matches the given `campaignId`. |
| `invalid_customer_payload` | 400 | The `customer` field on register is neither a string (customer id) nor an object (create model). |
| `customer_data_required` | 400 | The `customer` object on register is missing a valid `data` object. |
| `customer_id_must_be_string` | 400 | The `customer` object on register contains an `id` field — send the existing customer's id as the `customer` string instead. |
| `nothing_to_update` | 400 | The update body contains none of the updatable fields. |
| `_q_required` | 400 | The `q` query parameter is missing on `/v1/customers/find`. |

### Transaction & coupon tokens

| Token | Status | Meaning |
| - | - | - |
| `invalid_customer` | 400 | The customer is unknown, deleted, or voided — the transaction is rejected. |
| `card_not_found` | 400 | The given `card` does not belong to the customer. |
| `point_campaign_not_found` | 400 | `redeem.points` was sent but the card's campaign has no point spend (e.g. a stamp card). |
| `insufficient_collectable_balance` | 400 | `redeem.points` exceeds the card's current point balance. |
| `invalid_coupon` | 400 | The coupon instance does not exist or does not belong to the customer. |
| `coupon_is_disabled` | 400 | The coupon has been disabled. |
| `coupon_is_not_valid` | 400 | The coupon is not redeemable (expired or already redeemed). |
| `invalid_user` / `invalid_branch` | 400 | The `user` / `branch` could not be resolved: unknown opaque id (string form), or an unknown object without a `name` to create it from (lazy sync). |
| `invalid_transaction` | 400 | The transaction does nothing: no collection, no coupon redemption, and no point spend. |
| `invalid_amount_value` | 400 | The monetary value of `redeem.points` exceeds `amount`. |
| `duplicate_transaction` | 400 | A completed transaction with the same `orderId` already exists — no side effects were produced. |
| `no_applicable_campaign` | 400 | The selected card has no campaign that can run for this transaction. |

### Rollback tokens

| Token | Status | Meaning |
| - | - | - |
| `transaction_already_rolledback` | 400 | The transaction was already rolled back — treat as "already undone". |
| `transaction_is_not_latest` | 400 | Only the most recent non-rolled-back transaction of the customer + campaign can be rolled back; undo newer ones first. |
| `reward_coupon_already_used` | 400 | A reward coupon granted by the transaction has already been redeemed — the rollback is blocked and nothing was changed. |
| `reward_coupon_not_found` / `redeem_coupon_not_found` | 400 | A coupon referenced by the transaction no longer exists. |
| `coupon_is_not_redeemed` | 400 | The coupon of a `redeem-coupon` transaction is no longer in redeemed state. |
| `rollback_not_supported_for_external_transaction` | 400 | The transaction came from an external integration and cannot be rolled back here. |
| `rollback_not_supported_for_system_transaction` | 400 | The transaction is a system correction and cannot be rolled back. |
| `rollback_not_supported_for_legacy_transaction` | 400 | An old record without a pre-transaction snapshot — it cannot be restored safely. |
| `rollback_exceeds_current_threshold` | 400 | The campaign's configuration changed and the restored value would exceed the current threshold. |

### Product & category tokens

| Token | Status | Meaning |
| - | - | - |
| `entity_not_found` | 400 | No product/category matches the given id, or the record was deleted. |
| `opaque_id_already_exists` | 400 | Another product of the organization already has this `opaqueId`. |
| `auth_error` | 400/403 | The token lacks the required permission, or the resource belongs to another organization. |

### Server errors

| Token | Status | Meaning |
| - | - | - |
| `_internal_server_error` | 500 | Unexpected server error — wait briefly and retry. |

Validation errors on request bodies return **400** with the individual field messages joined in the `message` field (e.g. `campaignId must be a string / campaignId should not be empty`).

`2xx` codes indicate success; `4xx` codes indicate a request error — inspect the `message` token and fix the request before retrying; `5xx` codes indicate a server-side error — retry briefly and contact BrandWallet at [dev@brand-wallet.com](mailto:dev@brand-wallet.com) if the condition persists.


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