Skip to main content
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.

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

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.
  • 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.
  • 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.
  • 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.
  • 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 ids 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.
  • 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.

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