> ## 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.

# Changelog

> Every change to the VEXiON cards Public API, dated. Entries are never deleted or rewritten.

This page records every change to the public API surface, newest first. Each entry has a stable anchor of the form `#YYYY-MM-DD-short-slug`, so a link to an entry keeps working forever. Deprecations are announced here first. See [Versioning](https://vexioncards.mintlify.site/api-reference/versioning.md).

## 02. 11. 2026: stricter input checks

No endpoint and no field is removed. Requests that were already wrong now get a clear `422` instead of a `500` or a silent repair.

* **Long text on `POST /v1/cards` answers `422`, not `500`.** A text field above its stored length answers `422 validation_failed` with the field name, for example `issuer.company_name`. The stored length counts an emoji as 2 characters.
* **`field_errors[]` lists at most 100 entries.** A request with more errors gets the first 100.
* **Webhook endpoint `url` is checked more strictly.** A URL with a space, a control character, a backslash, a user name or password, or more than 2,048 characters answers `422 webhook_url_invalid`. Endpoints you saved before keep working.
* **`enabled_events` stores each event name once.** A name sent twice is saved once.
* **Schema fix:** `referral.referrer_bonus` and `referral.referee_bonus` in a card response no longer show a `maximum` of 1000. The real limit depends on the card and is described in the field text. The API could already return values above 1000, for example on a points card.

## 01. 11. 2026: money limits, one business currency and reward percentages

This release adds limits on cards and transactions, makes the business currency explicit, and adds three read fields. No endpoint is removed. Some requests that were accepted before now answer `422`, as listed below. See [Money and currency](https://vexioncards.mintlify.site/api-reference/money-and-currency.md) for the rules in one place.

**New and changed fields**

* **New field `percentage` on a reward.** A percent-off reward carries `mechanics.rewards[].percentage`, a whole number from 1 to 99, on read and on write. Send `percentage` or `value`, not both. A reward with neither is a free product.
* **`value` on a reward reads back for every currency.** Before, a fixed-amount reward made in the dashboard in a currency other than the euro read back without `value`. Now it reads back with `value`. `value` is a whole number of minor units, at least 1.
* **A reward sent back unchanged keeps its value.** A reward with its `id` and with neither `value` nor `percentage` keeps the stored reward in a `PATCH`. Before, it was saved as a free product, so the old advice not to send such a reward back is gone.
* **New field `currency` on a card and on a customer card slot.** Both carry the business currency. Customer webhooks carry it too.
* **New field `expected_currency` in `field_errors[]`.** It is sent only on `currency_mismatch`.
* **`cashback_balance_minor` and `balance_after.cashback_minor` use the rate of the till.** On a card connected to a till that sets its own point value, the money figure uses that rate, as on the wallet pass.

**Changed limits on cards.** A breach answers `422 validation_failed` with `field_errors[].code` `out_of_range` (or `invalid_value` where noted). On `PATCH` only a changed value is checked, so a card that already pays more keeps saving other fields.

* **Cashback is limited to 30 percent** on a card without levels and 60 percent per level. The percent is `earn_rate_per_unit` x `cashback_point_value` x 100, times the level multiplier. This replaces the 20 percent that the dashboard editor offered on a card without levels. On a card connected to Oberon, or to Papaya with the card basis, the point value of the till counts. `param`: `mechanics.earn_rate_per_unit`, `mechanics.cashback_point_value` or `mechanics.tiers`.
* **At most 20 rewards** on a card. `param`: `mechanics.rewards`.
* **`points_required` of a reward is at least 1.** Before, 0 was accepted.
* **`percentage` is 1 to 99, `value` is a whole number of at least 1.** A `value` with decimals or below 1 answers `422` on `mechanics.rewards[n].value`.
* **`cashback_point_value` must be above 0.** Before, 0 was accepted.
* **A discount card needs at least 1 level.** Level names are unique, and each level needs a spend above the level before it. The same holds for the levels of a reward card. A repeated name or a level that is not higher answers `invalid_value` on `mechanics.tiers`. The first step of an employee discount is at least 1 percent.
* **An unknown key inside `mechanics` answers `422 parameter_unknown`** with the full name, for example `mechanics.rewards[0].colour`. This also covers keys inside a reward, a level and `employee`. Before, such a key was ignored.
* **`PATCH` on `referral` merges key by key.** A key you leave out keeps its stored value. Before, the block replaced the stored settings and an omitted bonus became 0.
* **`initial_state.points` on `POST /v1/customers`** above the starting bonus limit of the card answers `422 validation_failed` with `field_errors[].code` `out_of_range`.

**Schema:** the limits above are checked by the server and described in the field texts. The schema types and minimums of existing fields do not change, so a refused request gets one exact error with the field name. The new fields are `rewards[].percentage`, `currency` on the card and on the customer card slot, and `field_errors[].expected_currency`.

**One business currency and new limits on transactions**

* **`currency` on `POST /v1/transactions` is optional.** When you send it, it must be the business currency, in any letter case. Another code answers `422 currency_mismatch` with `field_errors[0].expected_currency`. This also holds for a coupon redeem and for the MCP tool `create_transaction`.
* **Largest bill per currency.** `amount_minor` above 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 answers `422 amount_too_large`. The maximum transaction amount that the business sets in the dashboard now applies on the API and the MCP server too.
* **Largest `value`.** An explicit `value` above 100,000 points (points card), above the worth of the largest bill (cashback card) or above one full card (stamp `award`) answers `422 value_too_large`.
* **`amount_minor` on a redeem answers `422 amount_not_valid_for_action`**, also on a coupon redeem.
* **`external_id` compares the whole transaction.** A retry that sends `amount_minor` or `currency` must match what was booked. A different `action`, `value`, `amount_minor` or `currency` answers `409 external_id_conflict`.
* **MCP `create_transaction`: `external_id` stays required.** A retry with the same `external_id` books once; a new purchase needs a new one.
* **Employee discount over the API.** The saving is capped at the monthly limit of the card. On a card that counts net spend, the bill minus the discount counts.

**Webhooks**

* `transaction.created` carries `team_member_id` (when a team member booked it) and `products`.
* A coupon redeem made through `POST /v1/transactions` sends `transaction.created`. Before, no event was sent.

**Currency lock.** A business currency is locked after the first transaction, customer balance or gift card purchase. Contact support to change it. Your VEXiON subscription is always billed in EUR.

**New `422` and `409` codes in this release:** `currency_mismatch`, `amount_too_large`, `value_too_large`, `amount_not_valid_for_action`, and `external_id_conflict` on `POST /v1/transactions`. See [Errors](https://vexioncards.mintlify.site/api-reference/errors.md).

## 01. 11. 2026: bonus limits, cashback levels and earn rate 0

This release adds limits on bonuses, rules for cards with levels, and a clearer earn rate of 0. Nothing is removed and no field or endpoint is added. Some requests that were accepted before now answer `422`, as listed below.

* **Limits on the referral bonus and the starting bonus.** `POST /v1/cards` and `PATCH /v1/cards/{id}` check `referral.referrer_bonus`, `referral.referee_bonus`, `mechanics.starting_points` and `mechanics.starting_stamps` against the limit of the card, the same limits as in the dashboard. The referral limits have been live since 26. 10. 2026 and are part of this entry. The starting bonus limit on the API is new: before, the API accepted a starting bonus up to 100,000,000,000. The limits are:

  * A stamp card: up to 1,000 referral stamps. Starting stamps at most the card's `stamps_required`, and never above 1,000.
  * A points card: up to 100,000 points.
  * A cashback card: about 50 EUR of credit in your currency (CZK 1,250, HUF 20,000, PLN 200, RON 250, GBP 50, CHF 50, USD 50). The most points are that amount divided by `cashback_point_value`.
  * A discount card: about 1,000 EUR of spend (CZK 25,000, HUF 400,000, PLN 4,000, RON 5,000), in whole major currency units.

  A value above the limit answers `422 validation_failed` with `field_errors[].code` `out_of_range`. On `PATCH` only a changed value is checked. Every stored value is checked again when the change makes a point worth more (a higher `cashback_point_value`, or a switch into cashback), or lowers `stamps_required`. See [Errors](https://vexioncards.mintlify.site/api-reference/errors.md#out_of_range).
* **Changed `param` on the referral error.** This changes an existing error. The `422` for a referral bonus above the limit used to name the internal path `referralSettings.referrerBonus` or `referralSettings.refereeBonus`. It now names the field you sent: `referral.referrer_bonus` or `referral.referee_bonus`. The error `code` is unchanged. If you branch on `param`, update the check.
* **Cards with levels need a level.** A reward card with `earn_mode: "tiered"` needs at least one level. Without one, `POST` and `PATCH` answer `422 levels_need_a_level`. A level with a multiplier below 1 or a spend below 0 answers `422 levels_invalid_level`. Both name `mechanics.tiers`. Before, such a card was saved. Switching levels on places every customer in a level from past purchases. See [Update a card template](https://vexioncards.mintlify.site/api-reference/cards/update-a-card-template.md).
* **`field_immutable` on `mechanics.is_prepaid`.** A stamp card cannot switch between prepaid and normal after it is created. `PATCH` with the other value answers `400 field_immutable` with `param` `mechanics.is_prepaid`.
* **`reward_tier` on cashback cards.** A customer on a cashback card with levels carries `reward_tier`, as on a points card. It is absent when the card's levels are off.
* **Awards on a card with levels.** `POST /v1/transactions` with `value` books `value` times the level multiplier, rounded to the nearest whole point. The bill in `amount_minor` counts toward level spend, so the customer can level up. `tier_at_earning` is the level name, for example `Silver`.
* **`earn_rate_per_unit: 0` earns nothing.** Before, a rate of 0 earned 1 point per currency unit. Now it earns 0 points from spend. A card with no rate set still earns 1 point per currency unit. An award with `amount_minor` on a card with rate 0 answers `201` with `value: 0`, and the balance does not change. An explicit `value` still books `value` points.
* **Event descriptions.** The text served by `GET /v1/webhook_endpoints/event_types` for `customer.updated` and `transaction.created` now says what is sent. Which events fire did not change.

## 20. 10. 2026 (live since 21. 10. 2026): level retention on discount cards, employee discount without a limit, and fixes

This release adds two optional fields and lets discount cards use level retention. It also fixes four behaviours. Nothing is removed.

* **Level retention on discount cards.** `mechanics.tier_demotion` now works on discount cards with 2 or more tiers. Before, it answered `422 mechanic_not_available`. A customer who makes no qualifying transaction for `inactivity_days` drops one tier. On an employee discount card (`is_employee_discount: true`) the field is ignored on write and reads back as `null`. See [Update a card template](https://vexioncards.mintlify.site/api-reference/cards/update-a-card-template.md).
* **New field `level_valid_until` on customers.** Customer responses carry the date until which the customer keeps the current tier. It is `null` when level retention does not apply. See [List customers](https://vexioncards.mintlify.site/api-reference/customers/list-customers.md).
* **New field `mechanics.employee.no_limit` on employee discount cards.** With `no_limit: true` the discount does not stop at a monthly limit: the highest step applies until the reset day. Send it on create or update, and read it back on every card. On a card with no limit, `monthly_budget_minor` is left out of the response. An `employee` block with neither `monthly_budget_minor` nor `no_limit: true` answers `422 parameter_missing`, and one with both answers `422 validation_failed`. To switch a card from no limit to a limit, send `monthly_budget_minor` in the same request. A new employee discount card (or a card you switch to one) needs the `employee` block. With `no_limit: true`, the highest step must be below 100 %, else `422 validation_failed` on `mechanics.tiers`.
* **Employee settings on update.** A `PATCH` now changes only the employee fields it sends. Before, a `PATCH` with `mechanics.employee` but without `is_employee_discount` turned the card into a normal discount card. A `PATCH` with only `is_employee_discount` reset the monthly limit and the reset day.
* **Discount tier order.** Discount tiers are stored and returned from the lowest to the highest `spend_to_achieve_minor`, also when you send them in another order. Tier `id`s do not change.
* **Notification delivery counts.** `GET /v1/notifications` and `GET /v1/notifications/{id}` count deliveries per device. Some older sends where a device never confirmed now count that device as failed, so their `status` changes from `completed` to `partial`. The `status` filter uses the same counts.
* **Idempotent retries.** A retry sent the moment the first response arrives now gets the replayed response. Before, it could get `409 idempotency_key_in_use`.

## 11. 10. 2026 (live since 17. 10. 2026): small /v1 changes that ship with the MCP server

These changes reach every API client, not only the MCP server. They add no field, no endpoint and no required parameter, and they remove nothing. The first and third change the answer to a request in a narrow case, as described below.

* **`DELETE /v1/notifications/{id}`** now answers `409 broadcast_not_cancellable` for a scheduled broadcast whose send time is less than a minute away. A job that is about to run can no longer be stopped. Repeating the cancel of a broadcast that is already cancelled still returns 200. See [Cancel a scheduled notification](https://vexioncards.mintlify.site/api-reference/notifications/cancel-a-scheduled-notification.md).
* **Error texts.** `image_ingest_failed` and `delivery_failed` now carry a message from a fixed template with no internal detail. Failures in the authentication, scope, account-state, rate-limit and idempotency layers answer with the same fixed `internal_error` text, `An unexpected error occurred.` Branch on `error.code`, never on the message.
* **List filters with a cursor.** `GET /v1/transactions` and `GET /v1/notifications` now return an empty list when a filter can match nothing, for example a `card_id` that is not of the requested `card_type`, also when you pass `starting_after` or `ending_before`. Before, the cursor could bring rows back.

## 07. 10. 2026: look up a customer by serial number

`GET /v1/customers` accepts a new `serial_number` filter. Pass the value scanned from the wallet pass QR code or barcode, or the number printed on a physical card, and you get that customer back without listing your customers. The change is additive: nothing existing was changed. See [List customers](https://vexioncards.mintlify.site/api-reference/customers/list-customers.md).

## 27. 09. 2026: v1 (1.0.0) is generally available

The first public release of the VEXiON cards Public API.

* **54 REST endpoints** across customers, cards, transactions, products, notifications, locations, team members, custom fields, webhook endpoints, events, the organization and its invoices, API keys and icons. Base URL `https://api.vexioncards.one/v1`.
* **22 webhook events**, signed with HMAC-SHA-256 under the Standard Webhooks scheme.
* **The additive-only guarantee is in force** for everything under `/v1/` from this date. Removing or renaming a field, or changing a status or error code, is a breaking change and is announced here first. See [Versioning](https://vexioncards.mintlify.site/api-reference/versioning.md).
* **Notifications** accept `title` and `body` as plain text in one language. Template variables and more than one locale are answered with `422 template_variables_unavailable` and `422 multiple_locales_unavailable`, and nothing is sent.
* **API access** is included in every plan except VEXiON ONE. Creating an API key requires accepting the API terms in the dashboard.


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