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

# Errors

> The error envelope, common error codes, and how to debug failed requests.

Every error response uses a consistent JSON envelope. Whether you got a 400, 404, 422, or 500, the shape is the same.

## Error envelope

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "card_id is required.",
    "param": "card_id",
    "request_id": "req_01jBhYqKsfXr8M2WVCdXyL9pZk",
    "doc_url": "https://vexioncards.mintlify.site/api-reference/errors#parameter_missing"
  }
}
```

## Fields

| Field | Description |
| - | - |
| `error.type` | Broad category - a closed set of 7 values (see below) |
| `error.code` | Specific machine-readable code (branch on this in your code) |
| `error.message` | Human-readable explanation |
| `error.param` | The offending field, when a single field is at fault. Omitted from the response when there is no single field |
| `error.request_id` | Include in support requests |
| `error.doc_url` | Link to the documentation for this code. The API sends `https://vexioncards.mintlify.site/api-reference/errors#<code>`, where `<code>` is the value of `error.code` |
| `error.field_errors[]` | Present when several fields fail validation at once - each entry has `param`, `code`, `message` |

## Error types

`error.type` is one of exactly seven values:

| Type | HTTP | When |
| - | - | - |
| `invalid_request_error` | 400 / 408 / 409 / 413 / 422 / 431 | Malformed request, missing/unknown field, oversized body (`request_too_large`), a request that is not valid HTTP at all (see [Parser-level rejections](#parser-level-rejections-400-/-431-/-408)), or a business-rule violation |
| `authentication_error` | 401 | Missing, invalid, or revoked API key (`paused` is reserved and does not occur today) |
| `permission_error` | 403 | Your key lacks the required scope, or the organization's plan or account state does not allow the call |
| `not_found_error` | 404 | Referenced resource does not exist in your organization |
| `idempotency_error` | 409 | Idempotency key reused with a different body |
| `rate_limit_error` | 429 | Too many requests - see [Rate Limits](https://vexioncards.mintlify.site/api-reference/rate-limits.md) |
| `api_error` | 500 | Server error on our side |

## Decision flow: which status code will I get?

Requests are checked in this order. The first gate that fails determines the status code - a request that would fail two ways (say, a malformed body **and** a missing scope) always fails at the earlier gate.

1. **IP throttle.** Too many requests, or too many failed sign-ins, from one IP address: `429` (`rate_limited`, `too_many_failed_attempts`).
2. **Request shape.** A body that is not valid JSON, is too large or is nested too deeply, or a query string that carries objects: `400` or `413`.
3. **API key.** A missing, invalid or revoked key: `401`.
4. **Account state.** An organization that is paused (hibernating), locked, cancelled or not yet active: `403` (`organization_*`).
5. **Plan.** A plan without API access: `403` (`plan_no_api_access`).
6. **Scope.** The key lacks a scope the endpoint needs: `403` (`insufficient_scope`).
7. **Rate limit.** The key is over its budget for the endpoint: `429` (`rate_limited`).
8. **Idempotency.** A missing or malformed `Idempotency-Key` on a `POST`: `400`. A key reused with a different request, or still in use: `409`.
9. **Validation and business rules.** The request itself: `400`, `404`, `409` or `422`.

A path that matches no endpoint answers `404` without any of the checks above.

> [!NOTE]
>
> `GET /v1/organization` is the one exception to the plan and account-state gates above - it always answers, regardless of plan or account state, so a blocked integration can still read `account_state` and find out why. It still requires a valid key with the `organization:read` scope.

## Common error codes

Branch on `error.code`. Codes are open-ended: new ones ship over time, so treat any code you don't recognize as its parent `type`. Every code below has its own heading, so the `doc_url` on an error response links straight to its entry.

### Authentication (401)

#### `api_key_paused`

`401`. Reserved. The `paused` key state exists in the schema, but no code path pauses a key and the dashboard has no pause or resume control, so you will not see this code today.

#### `api_key_revoked`

`401`. This API key has been revoked. Mint a new key from the dashboard at [https://app.vexioncards.one/settings/developers](https://app.vexioncards.one/settings/developers).

#### `invalid_api_key`

`401`. The API key does not exist, is malformed, or is not associated with an organization.

#### `missing_api_key`

`401`. Authorization header is required. Send 'Authorization: Bearer vxn\_sk\_...'.

### Permissions (403)

Every `organization_*` code here is an **account-level gate that blocks the entire request before it reaches a resource handler** - it is not specific to writes. The one exception is `GET /v1/organization`, which always answers regardless of plan or account state, specifically so a blocked integration can still read `account_state` and find out why. See the [Decision flow](#decision-flow-which-status-code-will-i-get) above and the diagram on the [Introduction](https://vexioncards.mintlify.site/api-reference/introduction.md) page.

#### `insufficient_scope`

`403`. The API key does not carry a scope this endpoint requires. The message names the missing scope and lists the scopes the key does have.

#### `organization_cancelled`

`403`. This organization is cancelled. The Public API is unavailable.

#### `organization_locked`

`403`. This organization is locked. The Public API is unavailable; contact support.

#### `organization_not_active`

`403`. This organization is not active yet. The Public API is unavailable until activation, or claiming of the account, is complete.

#### `organization_paused`

`403`. This organization is paused (hibernating). The Public API is unavailable, except `GET /v1/organization`, until the organization is resumed from the dashboard.

#### `plan_limit_reached`

`403`. The plan's limit for this resource is already in use. Upgrade the plan or remove records to create more.

#### `plan_no_api_access`

`403`. This organization's plan does not include Public API access. Upgrade the plan to use /v1.

### Validation (400 / 422)

#### `field_immutable`

`400`. A card's `type` cannot be changed after creation, and a stamp card cannot switch `mechanics.is_prepaid` after creation.

#### `invalid_cursor`

`400`. A `starting_after` or `ending_before` cursor is malformed, or is a cursor for a different resource.

#### `invalid_date`

`400`. A date or timestamp parameter is neither unix seconds nor an ISO-8601 timestamp.

#### `invalid_json`

`400`. The request body is not valid JSON.

#### `invalid_limit`

`400`. The `limit` parameter is outside the accepted range (1 to 100).

#### `invalid_query_parameter`

`400`. A query parameter carries a value outside its allowed set. The message names the parameter and lists the values it accepts. Examples: sending both `starting_after` and `ending_before`, or a `serial_number` filter on `GET /v1/customers` that is longer than 256 characters or shaped like a query operator.

#### `missing_required_field`

`422`. A field this operation requires is absent from the request body. The message names the field and, where the field is an enum, the values it accepts.

#### `parameter_invalid`

`422`. A field or filter value is invalid for this operation. The message names the offending value and why it is rejected.

#### `parameter_missing`

`400`. A required field or body is absent.

#### `parameter_unknown`

`422`. The request carries a parameter this operation does not accept, or one that exists but is not settable here. Remove it. A key inside `mechanics` that the card type does not have (also inside a reward, a level or `employee`) answers this code with the full name, for example `mechanics.rewards[0].colour`.

#### `request_body_too_deep`

`400`. The request body is nested more deeply than the API accepts. Body SIZE is bounded separately and reports `request_too_large` instead.

#### `request_too_large`

`413`. The request body exceeds the maximum allowed size.

#### `validation_failed`

`422`. One or more fields failed validation. Read `field_errors[]` for the per-field detail: each entry carries its own `param`, `code` and `message`. The per-field codes are listed under [Per-field codes](#per-field-codes-inside-field_errors).

### Per-field codes (inside `field_errors[]`)

These codes appear in the `code` of a `field_errors[]` entry, not as the top-level `error.code`. The top-level code is `validation_failed`.

#### `card_id_required`

`422`. The customer is enrolled in more than one card, so `POST /v1/transactions` cannot tell which card to use. Send `card_id`.

#### `card_mismatch`

`422`. The `card_id` sent on `POST /v1/transactions` does not match the card the customer is enrolled in.

#### `invalid_for_card_type`

`422`. A key in `initial_state` is not valid for this card type, for example `initial_state.stamps_collected` on a card that is not a stamp card.

#### `invalid_format`

`422`. The value does not match the expected format, for example a malformed email, URL or id.

#### `invalid_length`

`422`. A string is too short or too long, or an array or object has too few or too many entries.

#### `invalid_value`

`422`. The value is present but not acceptable for this field. On a card it also covers a level name used twice, a level whose spend is not above the level before, a reward with both `value` and `percentage`, and a reward `value` that is not a whole number of minor units.

#### `options_required`

`422`. A custom field of a type that offers choices (`select` or `multiselect`) was sent without `options`.

#### `out_of_range`

`422`. A number is below its minimum or above its maximum. A referral or starting bonus above the limit of the card also answers this code, in `field_errors[].code` of a `validation_failed` error, with `param` `referral.referrer_bonus`, `referral.referee_bonus`, `mechanics.starting_points` or `mechanics.starting_stamps`. On a cashback card the limit is money: about 50 EUR per bonus in your currency (for example CZK 1 250, HUF 20 000, PLN 200), so the allowed number of points is that amount divided by `cashback_point_value`. Card money limits answer this code too, with `param` `mechanics.earn_rate_per_unit`, `mechanics.cashback_point_value`, `mechanics.tiers` or `mechanics.rewards`: cashback above 30 percent (60 percent per level), more than 20 rewards, `points_required` below 1, a reward `percentage` outside 1 to 99, a reward `value` below 1, a `cashback_point_value` of 0 or less, or an employee discount that starts below 1 percent. `initial_state.points` above the starting bonus limit of the card answers it as well. Only a changed value is checked on `PATCH`.

#### `prepaid_starts_empty`

`422`. A prepaid stamp card must start at 0. Send `initial_state.stamps_collected: 0` (or omit it). Stamps are added by a transaction.

### Parser-level rejections (400 / 431 / 408)

These are different from every other code on this page: they are produced by the HTTP server **before the request reaches the API at all**. No route is matched, your API key is never read, and the request is not counted against any rate limit.

The response still includes a `Vexion-Request-Id`, but because the request never reached the API there is no corresponding entry in your API logs to correlate it with. Retrying an identical request will fail identically - the fix is in whatever produced the bytes (your HTTP client, or a proxy in front of it), not in the request's content.

#### `invalid_request`

`400`. The request was not valid HTTP. In practice this is almost always a header problem: a control character in a header value, an invalid character in a header name, or a bare carriage return in the header block. The usual cause is unsanitised user input interpolated into a header. Strip control characters from any value you place into a header before sending.

#### `request_header_fields_too_large`

`431`. The request's header block exceeded the maximum size the server accepts. Send fewer or shorter headers. The body size limit is separate and reports `request_too_large` instead.

#### `request_timeout`

`408`. Reserved for a request that was not completed within the time the server allows for it. Current server versions typically close an idle connection without sending a response body rather than returning this code, so you are unlikely to observe it in practice. It is documented so that a client which does receive it can branch on the code rather than on an unexpected status.

### Not found (404)

#### `card_not_found`

`404 / 422`. A referenced card does not exist in your organization. Returned as `404` when the card is the resource you addressed, and as `422` when it is a value inside your request body (for example an `enabled_card_ids` entry on a location, or `audience.card_ids` on a broadcast).

#### `location_not_found`

`404 / 422`. A referenced location does not exist in your organization. Returned as `404` when the location is the resource you addressed, and as `422` when it is a value inside your request body (for example `location_id` on a customer, or a team member's `location_ids`).

#### `notification_not_found`

`404`. No notification with that id exists in your organization.

#### `resource_missing`

`404`. The universal not-found code, used by EVERY /v1 resource. A resource that belongs to another organization returns this too, never a `403`, so the API never leaks whether an id exists outside your organization.

### Idempotency (400 / 409)

#### `idempotency_key_conflict`

`409`. The same `Idempotency-Key` was reused with a different request body.

#### `idempotency_key_in_use`

`409`. An earlier request with this `Idempotency-Key` is still processing.

#### `idempotency_key_invalid`

`400`. Idempotency-Key header must be between 8 and 255 characters.

#### `idempotency_key_required`

`400`. No `Idempotency-Key` was sent on a `POST`. Optional on `PATCH` and `DELETE`, and never raised there.

### Rate limit (429)

#### `concurrent_broadcasts_exceeded`

`429`. The organization already has the maximum number of broadcasts in flight. Wait for one to finish before sending another.

#### `deliver_cooldown_active`

`429`. This customer was already sent a wallet link on this channel recently. Try again shortly.

#### `rate_limited`

`429`. The rate limit was hit. Respect `Retry-After`. Also returned by the pre-authentication per-IP flood ceiling (600 requests per minute per IP), which runs before your API key is checked - see [Rate Limits](https://vexioncards.mintlify.site/api-reference/rate-limits.md#pre-authentication-ip-throttle).

#### `rate_limited_per_day`

`429`. The per-day bucket for this operation is exhausted.

#### `rate_limited_per_minute`

`429`. The per-minute bucket for this operation is exhausted.

#### `too_many_failed_attempts`

`429`. Pre-authentication per-IP ceiling: 20 failed authentications (`401`s) in 15 minutes from the same IP. Runs before your API key is checked; a `403` for a missing scope never counts toward it.

### Server (500)

#### `delivery_failed`

`500`. The wallet link could not be delivered on the requested channel. This is a failure in our delivery pipeline, not a problem with your request.

#### `image_ingest_failed`

`503`. The image passed validation but could not be ingested into our storage. This is a server-side failure, not a problem with your image; retry.

#### `internal_error`

`500`. Something failed on our side. Retry-safe with the same `Idempotency-Key`.

#### `service_unavailable`

`503`. The public API failed to start and is temporarily unavailable due to a server-side configuration error. This is not a problem with your request; retry shortly. Distinct from a `404` on purpose: `/v1` answering `503` means the facade is down, not that the route does not exist.

### Business-rule violations (mostly 409, some 422)

Semantic violations: the request is well formed, but it asks for something the resource's own rules forbid. They all carry `type: invalid_request_error`; the `message` explains the specific rule. Branch on the `code`.

#### `action_not_valid_for_card_type`

`422`. Coupon cards support only the "redeem" action.

#### `address_not_geocodable`

`422`. The address could not be resolved to coordinates. Check it, or send coordinates alongside the address.

#### `admin_has_all_locations`

`422`. admin members have implicit access to every location; omit location\_ids.

#### `already_member`

`409`. This email is already a member or has a pending invitation.

#### `already_reversed`

`409`. This transaction has already been reversed.

#### `amount_not_valid_for_action`

`422`. A `redeem` carries no bill. `amount_minor` on a redeem answers this code, also on a coupon redeem. Remove `amount_minor`. `param`: `amount_minor`.

#### `amount_required`

`422`. amount\_minor (bill total in minor units, greater than 0) is required for discount transactions.

#### `amount_too_large`

`422`. `amount_minor` is above the largest single bill of your currency, or above the maximum transaction amount you set in the dashboard. The largest bill is 10,000 EUR, 250,000 CZK, 4,000,000 HUF, 45,000 PLN, 50,000 RON, 9,000 GBP, 10,000 CHF or 11,000 USD. `param`: `amount_minor`. See [Money and currency](https://vexioncards.mintlify.site/api-reference/money-and-currency.md).

#### `audience_invalid`

`400`. An `audience.customer_ids` entry references a customer that is not in your organization.

#### `broadcast_not_cancellable`

`409`. This broadcast can no longer be cancelled - it is not a pending scheduled send.

#### `cannot_reverse_reversal`

`409`. A reversal row cannot itself be reversed.

#### `card_has_active_customers`

`409`. The card still has active enrolled customers. Archive it first (`DELETE` without `gdpr=true`), migrate customers as needed, then retry with `gdpr=true`.

#### `card_name_already_exists`

`409`. A card template with that name already exists in your organization.

#### `card_type_not_writable`

`422`. Cards of this type cannot be written through the API. A transaction can only be posted against `stamp`, `reward`, `discount` and `coupon` cards. On `membership`, `gift_card` and `custom` cards, `POST /v1/transactions` returns this code. You also cannot `PATCH` the `mechanics` of a `membership` or `gift_card` card. `POST /v1/cards` never returns this code, because `type` accepts only the five creatable values and anything else fails schema validation as `422 validation_failed` instead.

#### `contact_missing_for_channel`

`422`. The customer has no contact detail on file for the requested channel (no email address for an email delivery, no phone number for an SMS one).

#### `coupon_already_redeemed`

`409`. This coupon has already been redeemed.

#### `coupon_not_reissuable`

`422`. Only a coupon card can be reissued, and this card is a different type.

#### `currency_mismatch`

`422`. The `currency` you sent on `POST /v1/transactions` is not the currency of the business. A business has one currency. `field_errors[0].expected_currency` names the currency to send. You can also leave `currency` out. `param`: `currency`. See [Money and currency](https://vexioncards.mintlify.site/api-reference/money-and-currency.md).

#### `custom_field_in_use`

`409`. The `key` of a custom field cannot be renamed while a card references the field. Detach it from all cards first.

#### `custom_field_limit_reached`

`409`. The organization is already at its maximum number of defined custom fields.

#### `customer_erased`

`409`. This customer has been erased (GDPR right-to-be-forgotten) and can no longer be modified.

#### `expires_at_in_past`

`422`. expires\_at must be a unix timestamp in the future.

#### `expires_at_scanner_only`

`422`. expires\_at is available on the scanner role only.

#### `external_id_collision`

`422`. An `audience.external_ids` value maps to more than one customer. Resolve the duplicate before sending.

#### `external_id_conflict`

`409`. On `POST /v1/customers` and `PATCH /v1/customers/{id}`: a customer with this external\_id already exists for this card. On `POST /v1/transactions`: you reused an `external_id` for a different transaction. A retry of the same transaction is fine and returns the original. A different `action`, `value`, `amount_minor` or `currency` answers this code. `amount_minor` and `currency` are compared only when you send them. `param`: `external_id`.

#### `form_builder_missing_email_field`

`422`. `form_builder.fields` must include a required system `email` field. The `param` is `form_builder.fields`.

#### `form_builder_missing_first_name_field`

`422`. `form_builder.fields` must include a required system `first_name` field. The `param` is `form_builder.fields`.

#### `geocoding_unavailable`

`422`. Geocoding is unavailable right now. Send coordinates alongside the address.

#### `image_aspect_ratio_invalid`

`422`. The supplied image does not match the aspect ratio this field requires (a 5% tolerance is allowed).

#### `image_format_invalid`

`422`. The supplied image is not a valid PNG. Either the magic-byte signature did not match, or the PNG header or chunk structure is malformed or truncated. Only PNG is accepted; the check reads the bytes, never the `Content-Type` header. `param` names the offending field, for example `design.icon_url`.

#### `image_not_square`

`422`. The supplied image must be square, within a 2% tolerance.

#### `image_resolution_too_large`

`422`. The supplied image exceeds the maximum pixel dimensions for this field.

#### `image_resolution_too_small`

`422`. The supplied image is below the minimum pixel dimensions for this field.

#### `image_too_large`

`422`. The supplied image exceeds the 10 MB maximum file size. The limit is measured on the real streamed bytes, not on a declared length. `param` names the offending field, for example `design.logo_url`.

#### `insufficient_balance`

`409`. The customer does not hold enough of the unit being redeemed to cover the requested amount.

#### `invalid_image_url`

`422`. A URL could not be used. An image URL must point to an image that can be fetched over https. `terms_of_use.full_url` must use https and must not contain credentials. Links with other schemes (`javascript:`, `data:`, `ftp:`, `file:`, plain `http:`) are rejected.

#### `invalid_status_transition`

`409`. The requested `status` is not a legal transition from the card's current status.

#### `key_already_exists`

`409`. An active custom field already uses this `label` or this explicit `key`. The `param` says which one.

#### `languages_supported_empty`

`422`. languages\_supported must contain at least one language.

#### `languages_supported_missing_translations`

`422`. You cannot widen `languages_supported` to include a language that the card's form fields have no translations for. Add the translations first.

#### `languages_supported_requires_form_builder`

`422`. This card has no `form_builder` yet. Include `form_builder` in the same request as `languages_supported`, so field translations can be built for it.

#### `levels_invalid_level`

`422`. A level on a reward card needs a multiplier of at least 1 and a spend of 0 or more. `param`: `mechanics.tiers`.

#### `levels_need_a_level`

`422`. A reward card has levels switched on (`earn_mode: tiered`) but no level. Add a level or switch levels off. `param`: `mechanics.tiers`.

#### `linked_card_not_found`

`422`. mechanics.linked\_card\_template does not reference a valid card.

#### `linked_coupon_redeem_unsupported`

`422`. This coupon upgrades into another card on redemption. Redeem it at the point of sale.

#### `location_ids_too_many`

`422`. audience.location\_ids accepts at most 25 ids.

#### `mechanics_type_mismatch`

`422`. The `mechanics` block is required and must match the card's own type.

#### `member_not_editable`

`409`. The team member is in a terminal state and cannot be edited. Add the person again with `POST /v1/team_members`.

#### `multiple_locales_unavailable`

`422`. A notification `title` or `body` was sent in more than one language. Notifications are delivered in one language to every recipient, so send each in a single language. See [Send a notification](https://vexioncards.mintlify.site/api-reference/notifications/send-a-notification.md).

#### `not_invited`

`409`. The team member has no pending invitation, so there is nothing to resend.

#### `occurred_at_out_of_range`

`422`. occurred\_at must be a Unix timestamp within the last 90 days and not in the future.

#### `organization_not_linked`

`409`. This organization's team setup is not connected, so team members cannot be created. Contact support.

#### `owner_immutable`

`409`. The organization owner cannot be modified through the API. Use the dashboard.

#### `product_not_found`

`422`. A product\_id on a line item does not match a product in this location catalog.

#### `reusable_coupon_cannot_link`

`422`. A reusable coupon cannot link an upgrade-on-redeem card.

#### `reversal_window_expired`

`409`. Transactions can only be reversed within 90 days of creation.

#### `scanner_requires_single_location`

`422`. The scanner role requires location\_ids with exactly one entry.

#### `schedule_at_out_of_range`

`422`. schedule\_at must be in the future and at most 90 days ahead.

#### `sku_already_exists`

`409`. A product with this SKU already exists at this location.

#### `source_not_reversible`

`409`. Only transactions created through the Public API (source "api") can be reversed. Corrections for other sources happen in their originating channel.

#### `stamp_count_images_language_mismatch`

`422`. A `mechanics.stamp_count_images` entry declares a different language set than its siblings.

#### `stamp_count_images_length`

`422`. `mechanics.stamp_count_images` must contain exactly one image per stamp in the card's goal.

#### `template_invalid`

`422`. Template variables are not supported in the title (it is shared across recipients) - use them in the body.

#### `template_variable_not_supported`

`422`. Reserved. Notifications are plain text today, so any `{{` in a body is refused earlier with [`template_variables_unavailable`](#template_variables_unavailable). You will not see this code today.

#### `template_variable_not_supported_by_card`

`422`. Reserved. Notifications are plain text today, so any `{{` in a body is refused earlier with [`template_variables_unavailable`](#template_variables_unavailable). You will not see this code today.

#### `template_variable_requires_card_scope`

`422`. Reserved. Notifications are plain text today, so any `{{` in a body is refused earlier with [`template_variables_unavailable`](#template_variables_unavailable). You will not see this code today.

#### `template_variables_unavailable`

`422`. A notification `body` contains template variables such as `{{first_name}}`. Notification titles and bodies are plain text, and template variables are not available. See [Send a notification](https://vexioncards.mintlify.site/api-reference/notifications/send-a-notification.md).

#### `tier_achieve_field_missing`

`422`. A tier in `mechanics.tiers` is missing the achievement field that tier type requires.

#### `tier_achieve_field_not_allowed`

`422`. A tier in `mechanics.tiers` carries an achievement field that is not valid for this card.

#### `transaction_not_reversible`

`422`. This transaction record cannot be reversed.

#### `unknown_custom_field`

`422`. The request references a custom field that is not defined on the card. Define it on the card's `form_builder` first (see `GET /v1/cards/id`).

#### `unknown_form_field_reference`

`422`. `pass_fields` references a form field that is not declared on the card.

#### `unsupported_card_type`

`422`. Enrolling a customer on this card type is not supported through the API. Supported types are `stamp`, `reward`, `discount`, `custom` and `coupon`; `membership` and `gift_card` are not.

#### `unsupported_target_type`

`422`. An upgrade-on-redeem card must target a Stamp, Reward, Discount or Membership card.

#### `value_required`

`422`. value (whole stamps, greater than 0) is required for stamp transactions; stamps are always an explicit count, spend-driven stamp derivation is not a supported mechanic. On a reward card, `value` (whole points, greater than 0) is required for a redeem, and an award needs `value` or `amount_minor`.

#### `value_too_large`

`422`. An explicit `value` on `POST /v1/transactions` is above the limit for the card: 100,000 points on a points card, points worth more than the largest bill on a cashback card, or more than one full card of stamps on a stamp `award`. `param`: `value`. See [Money and currency](https://vexioncards.mintlify.site/api-reference/money-and-currency.md).

#### `webhook_endpoint_limit_reached`

`409`. The organization is already at its maximum number of webhook endpoints. Delete an unused endpoint before registering another.

#### `webhook_event_unknown`

`422`. The request subscribes to an event name that is not in the event catalog.

#### `webhook_url_invalid`

`422`. The webhook `url` is rejected on its own merits: it is not a valid absolute URL, does not use https, has no host, or resolves to a private or non-publicly-routable address.

#### `webhook_url_unreachable`

`422`. The webhook `url` is well formed but our probe could not reach it: the host did not resolve, resolved to no address, or refused the connection.

#### `would_drive_balance_negative`

`409`. Reversing this earn would drive the customer's balance below zero.

## MCP server errors

Most problems with the [MCP server](https://vexioncards.mintlify.site/ai/mcp-server.md) are one of: the sign-in expired, the access level is too low, or the business is not eligible. Find the message your assistant shows.

### Sign-in and access

> [accordion] 401: "Sign in to VEXiON to use this connector."
>
> Your sign-in is missing or expired. Reconnect the VEXiON connector in your AI app and sign in again. In Claude Code, run `/mcp` and choose **vexion**.

> [accordion] 401: "This API key is not valid. Check it and try again."
>
> The key is mistyped, revoked, or sent in the wrong form. The header must read `Authorization: Bearer vxn_sk_...` with no extra characters. Check that the environment variable behind it is set, and create a new key in **Settings > Developer** if needed.

> [accordion] 403: "Only an admin of a business on VEXiON PRO can use this connector."
>
> The account you signed in with is not an admin of a business that qualifies. Ask an admin of the business to connect, or upgrade the business to VEXiON PRO. If you are an admin of several businesses, sign in with the account that administers the business on VEXiON PRO.

> [accordion] 403: "The plan of this business does not include AI assistants. Upgrade to VEXiON PRO."
>
> The business is not on VEXiON PRO. The MCP server is part of VEXiON PRO.

> [accordion] 403: "This AI connection was revoked. Reconnect it from the AI app to restore access."
>
> Someone revoked the app under **Settings > Developer > AI assistants**. Remove the connector in your AI app and add it again, then choose the access level once more. Connecting again lifts the revocation.

> [accordion] The business is paused, locked, cancelled or not active
>
> The assistant tells you the business is unavailable, for example "This business is paused. VEXiON is unavailable until it is resumed in the dashboard." Resume or activate the business in the dashboard. For a locked business, contact VEXiON support.

> [accordion] Access stopped working after it worked yesterday
>
> Access follows your role and plan live. It ends within about a minute if you stop being an admin, if the business leaves VEXiON PRO, or if the business is paused, locked or cancelled. Check your role, the plan and **Settings > Developer > AI assistants**.

### Tools and results

> [accordion] A tool I expect is missing, or the assistant says it needs more access
>
> The tool needs access you did not grant, so the assistant cannot see it. With OAuth, reconnect and choose **Read and write**, or allow the area under **Customize** (see [Permissions](https://vexioncards.mintlify.site/ai/mcp-permissions.md)). With an API key, create a key with the scopes the tool needs (see [Tools](https://vexioncards.mintlify.site/ai/mcp-tools.md)).
>
> In Claude, also check that **VEXiON** is switched on for the conversation: click **+** at the lower left of the chat, then **Connectors**.

> [accordion] The assistant asks which business to use
>
> You choose the businesses on the VEXiON consent page when you connect the app. When the app can use several businesses, the assistant asks which one you mean unless you named it. Tell it the business name. It then passes that business's `organization_id` on every call (see [Several businesses](https://vexioncards.mintlify.site/ai/mcp-tools.md)). An older connection with no stored choice gets the `select_organization` fallback instead. To add or remove a business, reconnect the app and choose again on the consent page.

> [accordion] My report is marked partial or truncated
>
> The report hit the 30-second or 3,000-row limit. The assistant should continue from the returned `next_cursor` and add the counts together, or you can ask for a shorter date range, for example month by month. See [Safety and limits](https://vexioncards.mintlify.site/ai/mcp-tools.md#reports-and-large-lists).

> [accordion] A notification was refused because of languages
>
> A notification has one language. Ask the assistant to send one notification per language, each to the customers who speak it. See [Sending in more than one language](https://vexioncards.mintlify.site/ai/mcp-tools.md#sending-in-more-than-one-language).

> [accordion] A confirmation was refused or expired
>
> A confirmation works once, for 10 minutes, and only for the exact details previewed. Ask the assistant to preview again, check the preview and confirm.

> [accordion] An action may or may not have happened
>
> If a confirmed action fails in a way that leaves the result unknown, the assistant is told it may already have been done and must not repeat it. Ask it to check first, for example by listing recent notifications or opening the transaction, before trying again.

### Limits

> [accordion] 429: "Too many requests. Slow down."
>
> You hit a rate limit. Wait for the time the response gives, then continue. Large reports use many reads at once, so leave a minute between them. The numbers are on [Rate limits](https://vexioncards.mintlify.site/api-reference/rate-limits.md#mcp-server).

### Connection problems

> [accordion] The client cannot connect at all
>
> Check that the URL is exactly `https://api.vexioncards.one/mcp` and that your client supports **Streamable HTTP**. Clients that only support the older SSE transport cannot connect. If your computer or network is managed by your organization, ask your IT team to allow `api.vexioncards.one`.

> [accordion] I changed the configuration but nothing happens
>
> Restart the AI app or reload the MCP servers, then reconnect. In Claude Code, run `claude mcp list` to see the state of the server.

Still stuck? Email VEXiON support from the address on your account and include the time of the problem and the message you saw. Do not send API keys.

## Debugging failed requests

**1. Copy the request\_id**

It's in both the JSON body and the `Vexion-Request-Id` response header.

**2. Check field\_errors\[]**

It pinpoints the exact fields that failed.

**3. Contact support**

Email [support@vexioncards.one](mailto:support@vexioncards.one) with the `request_id` - we can trace exactly what happened.

## Example handling

```javascript Node.js theme={null}
const res = await fetch('https://api.vexioncards.one/v1/customers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.VXN_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ card_id: 'crd_...', email: 'anna@example.com' })
});

if (!res.ok) {
  const err = (await res.json()).error;
  console.error(`${err.type}/${err.code}: ${err.message} (req ${err.request_id})`);

  switch (err.code) {
    case 'validation_failed':
      // Inspect err.field_errors[] and fix the input
      break;
    case 'insufficient_scope':
      // The key is missing a scope - re-issue it in the dashboard
      break;
    case 'rate_limited':
      // Retry after the Retry-After header
      break;
    case 'idempotency_key_conflict':
      // Same key, different body - retry with a fresh key
      break;
    default:
      // Log + retry-safe if idempotent
  }
}
```

```python Python theme={null}
import os, requests

r = requests.post(
    "https://api.vexioncards.one/v1/customers",
    headers={"Authorization": f"Bearer {os.environ['VXN_API_KEY']}"},
    json={"card_id": "crd_...", "email": "anna@example.com"},
)

if not r.ok:
    err = r.json()["error"]
    print(f"{err['type']}/{err['code']}: {err['message']} (req {err['request_id']})")
```


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