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

# Create a card template

> Creates a new card template.

## The `type` field is immutable

Once created, you cannot switch a card from `stamp` to `reward`. Design your card family carefully - create a new template if you need different mechanics.

## Required fields

- `type` - one of `stamp`, `reward`, `discount`, `coupon`, `custom`. `gift_card` and `membership` cards also exist, but they are dashboard-managed: create them there or through a connected POS, not through this endpoint.
- `name` - display name (string or `{sk, cs, en, de}` per-language map)
- `description` - customer-facing description
- `default_language` + `languages_supported[]` - the localization set
- `design` - bg/text colors, logo, icon
- `mechanics` - type-specific (see request examples for each variant)
- `issuer` - company name + contact info shown on wallet pass

## Design mode

- `design.design_mode: "standard"` - pick from our 10,000+ icon library
- `design.design_mode: "individual"` - upload custom images per stamp/tier

## Image generation (standard design)

Cards created with `design.design_mode: "standard"` get their wallet pass images rendered automatically - the per-stamp-state strip images for stamp cards, or the single pattern strip for other types. The card is returned immediately; images render in the background, typically within seconds. Track progress via `image_generation.status` on the card (`processing` -> `completed`). The pattern image text comes from the card `description`: pass a per-language object and one image is generated per supported language, each customer receiving the one matching their signup language (fallback sk -> en). A plain-string description produces a single image. Individual-design cards skip generation: you supply the imagery.

## Wallet pass

The response includes the auto-generated pass URLs (`add_to_wallet_url`, `google_wallet_url`) so you can immediately test the design.

## Idempotency

Send `Idempotency-Key` to make retries safe. See [Idempotency](/api-reference/idempotency).

See the **11 request examples** below for every card-type variant.



## OpenAPI

````yaml /openapi.json post /v1/cards
openapi: 3.1.0
info:
  title: VEXiON cards Public API
  description: >-
    ## Rate limiting


    Two independent layers, checked in this order:


    1. **Per-IP throttle (pre-authentication).** Applied to every `/v1` request
    BEFORE the API key is checked, keyed on the caller's IP address:
       - **600 requests/minute** per IP - a flood ceiling, `429 rate_limit_error` / `rate_limited`.
       - **20 failed authentications per 15 minutes** per IP - a key-guessing ceiling. Only responses that are `401` (missing/invalid/revoked key) count against this ceiling; a `403` from a valid key that simply lacks a scope does not. `429 rate_limit_error` / `too_many_failed_attempts`.

       Both ceilings send `Retry-After` (seconds).
    2. **Per-key, per-bucket limiter (post-authentication).** Documented per
    endpoint (see each operation's `429` response and the `rate_limit` field on
    the API Key resource) - e.g. 60/min default, 120/min for transactions, plus
    the notification-broadcast and lookup-filter buckets described on their own
    endpoints.


    ### Rate-limit response headers


    Every rate-limited response carries the legacy `X-RateLimit-Limit` /
    `X-RateLimit-Remaining` / `X-RateLimit-Reset` family (the last is an **epoch
    timestamp**) AND the IETF standards-track headers, side by side:


    - `RateLimit-Policy: "<bucket>";q=<quota>;w=<window seconds>` - the static
    policy for the bucket that was hit.

    - `RateLimit: "<bucket>";r=<remaining>;t=<seconds until reset>` - live
    state. **`t` is a DURATION (seconds remaining), not the epoch timestamp
    `X-RateLimit-Reset` carries - the two are never the same value.**
  version: 1.0.0
servers:
  - url: https://api.vexioncards.one
    description: Production
  - url: https://migration.api.vexioncards.one
    description: Sandbox
security:
  - apiKey: []
tags:
  - name: Customers
  - name: Cards
  - name: Transactions
  - name: Notifications
  - name: Locations
  - name: Products
  - name: API keys
  - name: Organization
  - name: Webhook endpoints
  - name: Events
  - name: Custom fields
  - name: Team members
  - name: Icons
paths:
  /v1/cards:
    post:
      tags:
        - Cards
      summary: Create a card template
      description: >-
        Creates a new card template.


        ## The `type` field is immutable


        Once created, you cannot switch a card from `stamp` to `reward`. Design
        your card family carefully - create a new template if you need different
        mechanics.


        ## Required fields


        - `type` - one of `stamp`, `reward`, `discount`, `coupon`, `custom`.
        `gift_card` and `membership` cards also exist, but they are
        dashboard-managed: create them there or through a connected POS, not
        through this endpoint.

        - `name` - display name (string or `{sk, cs, en, de}` per-language map)

        - `description` - customer-facing description

        - `default_language` + `languages_supported[]` - the localization set

        - `design` - bg/text colors, logo, icon

        - `mechanics` - type-specific (see request examples for each variant)

        - `issuer` - company name + contact info shown on wallet pass


        ## Design mode


        - `design.design_mode: "standard"` - pick from our 10,000+ icon library

        - `design.design_mode: "individual"` - upload custom images per
        stamp/tier


        ## Image generation (standard design)


        Cards created with `design.design_mode: "standard"` get their wallet
        pass images rendered automatically - the per-stamp-state strip images
        for stamp cards, or the single pattern strip for other types. The card
        is returned immediately; images render in the background, typically
        within seconds. Track progress via `image_generation.status` on the card
        (`processing` -> `completed`). The pattern image text comes from the
        card `description`: pass a per-language object and one image is
        generated per supported language, each customer receiving the one
        matching their signup language (fallback sk -> en). A plain-string
        description produces a single image. Individual-design cards skip
        generation: you supply the imagery.


        ## Wallet pass


        The response includes the auto-generated pass URLs (`add_to_wallet_url`,
        `google_wallet_url`) so you can immediately test the design.


        ## Idempotency


        Send `Idempotency-Key` to make retries safe. See
        [Idempotency](/api-reference/idempotency).


        See the **11 request examples** below for every card-type variant.
      operationId: createCard
      parameters:
        - name: Idempotency-Key
          in: header
          description: >-
            Stable opaque string up to 255 chars. Same key + same body within
            24h replays cached response.
          required: true
          example: 9b2f4a7e-2c1c-4d3e-9f8b-1234567890ab
          schema:
            type: string
            maxLength: 255
            minLength: 8
            pattern: ^[A-Za-z0-9_-]{8,255}$
        - name: Accept-Language
          in: header
          description: >-
            Preferred language for error messages and localized fields. One of
            sk, cs, en, de. Defaults to card's default_language.
          required: false
          example: sk
          schema:
            type: string
            enum:
              - sk
              - cs
              - en
              - de
            default: sk
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardCreateRequest'
            examples:
              Stamp basic:
                summary: Stamp basic
                value:
                  type: stamp
                  name:
                    sk: Coffee Card
                    en: Coffee card
                  description:
                    sk: Collect 10 stamps, get the 11th coffee free.
                    en: Collect 10 stamps, get the 11th coffee free.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  external_id: loyalty-coffee-stamp-2026
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: null
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls: []
                    strip_color: '#36C6FA'
                  pass_fields:
                    header: null
                    front_left:
                      label:
                        sk: Card
                        en: Card
                      value_template: '{{serial_number}}'
                      change_message: null
                    front_right: null
                    back:
                      - key: terms
                        label:
                          sk: Terms
                          en: Terms
                        value_template: Full terms at www.example.com
                        change_message: null
                  mechanics:
                    type: stamp
                    stamps_required: 10
                    is_prepaid: false
                    starting_stamps: 1
                    reward_at_completion: 1x Free coffee (any size)
                    stamp_count_images: []
                    stamp_icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: true
                    referrer_bonus: 1
                    referee_bonus: 1
                  issuer:
                    company_name: Demo Bakery s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  issue_limit: null
                  metadata: {}
              Stamp prepaid:
                summary: Stamp prepaid
                value:
                  type: stamp
                  name:
                    sk: Káva 10-pack predplatne
                    en: Coffee 10-pack subscription
                  description:
                    sk: Kup si 10 kav vopred za zvyhodnenu cenu.
                    en: Buy 10 coffees upfront at a discounted price.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: null
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls: []
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Zostáva
                        en: Remaining
                      value_template: '{{card.stamps_remaining}}'
                      change_message: null
                    front_left:
                      label:
                        sk: Card
                      value_template: '{{serial_number}}'
                      change_message: null
                    front_right: null
                    back:
                      - key: info
                        label:
                          sk: Info
                        value_template: Card je platna 12 mesiacov od kupy
                        change_message: null
                  mechanics:
                    type: stamp
                    stamps_required: 10
                    is_prepaid: true
                    starting_stamps: 0
                    reward_at_completion: Kazda pecatka = 1x Káva
                    stamp_count_images: []
                    stamp_icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Bakery s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Reward flat cashback:
                summary: Reward flat cashback
                value:
                  type: reward
                  name:
                    sk: Cashback card
                    en: Cashback card
                  description:
                    sk: >-
                      Za kazde 1€ ziskas 1 bod. 100 bodov = 1€ k pouzitiu pri
                      dalsom nakupe.
                    en: >-
                      Earn 1 point per €1 spent. 100 points = €1 to use at next
                      purchase.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/star.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Body
                        en: Points
                      value_template: '{{card.points}}'
                      change_message: null
                    front_left:
                      label:
                        sk: Cashback k dispozicii
                        en: Cashback available
                      value_template: '{{card.cashback_available | money}}'
                      change_message: null
                    front_right: null
                    back: []
                  mechanics:
                    type: reward
                    earn_mode: flat
                    earn_rate_per_unit: 1
                    starting_points: 50
                    redemption_mode: cashback
                    cashback_point_value: 0.01
                    rewards: []
                    tiers: []
                    tier_demotion:
                      enabled: false
                      inactivity_days: 365
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: true
                    referrer_bonus: 100
                    referee_bonus: 50
                  issuer:
                    company_name: Demo Coffee s.r.o.
                    email: info@democoffee.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Reward tiered product:
                summary: Reward tiered product
                value:
                  type: reward
                  name:
                    sk: Vernostny program
                    en: Loyalty program
                  description:
                    sk: >-
                      Postup tier-mi a ziskaj nasoboky bodov. Vymen body za
                      produkty z menu.
                    en: >-
                      Progress through tiers to earn point multipliers. Redeem
                      points for menu items.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/star.png
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/diamond.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Úrovne
                        en: Tier
                      value_template: '{{card.reward_tier.tier_name}}'
                      change_message: null
                    front_left:
                      label:
                        sk: Body
                        en: Points
                      value_template: '{{card.points}}'
                      change_message: null
                    front_right:
                      label:
                        sk: Násobok
                        en: Multiplier
                      value_template: '{{card.reward_tier.earn_multiplier}}x'
                      change_message: null
                    back: []
                  mechanics:
                    type: reward
                    earn_mode: tiered
                    earn_rate_per_unit: 1
                    starting_points: 0
                    redemption_mode: product
                    cashback_point_value: null
                    rewards:
                      - id: rwd_cafe
                        name: Káva
                        points_required: 100
                      - id: rwd_cake
                        name: Tortový kúsok
                        points_required: 250
                      - id: rwd_combo
                        name: Combo menu
                        points_required: 500
                    tiers:
                      - id: tier_bronze
                        name: Bronze
                        earn_multiplier: 1
                        spend_to_achieve_minor: 0
                      - id: tier_silver
                        name: Silver
                        earn_multiplier: 1.5
                        spend_to_achieve_minor: 50000
                      - id: tier_gold
                        name: Gold
                        earn_multiplier: 2
                        spend_to_achieve_minor: 200000
                    tier_demotion:
                      enabled: true
                      inactivity_days: 365
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: true
                    referrer_bonus: 200
                    referee_bonus: 100
                  issuer:
                    company_name: Demo Coffee s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Discount single:
                summary: Discount single
                value:
                  type: discount
                  name:
                    sk: VIP zlava 10%
                    en: VIP 10% discount
                  description:
                    sk: 10% zlava na vsetky sluzby v salone.
                    en: 10% off all services at the salon.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/scissors.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Zlava
                        en: Discount
                      value_template: 10%
                      change_message: null
                    front_left: null
                    front_right: null
                    back: []
                  mechanics:
                    type: discount
                    tiers:
                      - name: VIP
                        spend_to_achieve_minor: 0
                        percentage: 10
                    is_employee_discount: false
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Salon s.r.o.
                    email: info@demosalon.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Discount tiered:
                summary: Discount tiered
                value:
                  type: discount
                  name:
                    sk: Vernostné úrovne
                    en: Loyalty tiers
                  description:
                    sk: >-
                      Pohyb medzi úrovňami podľa minutia za posledných 12
                      mesiacov.
                    en: Move between tiers based on spend over the past 12 months.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/star.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header: null
                    front_left:
                      label:
                        sk: Zlava
                        en: Discount
                      value_template: '{{card.discount.discount_level}}%'
                      change_message: null
                    front_right: null
                    back: []
                  mechanics:
                    type: discount
                    tiers:
                      - id: tier_bronze
                        name: Bronze
                        spend_to_achieve_minor: 0
                        percentage: 5
                      - id: tier_silver
                        name: Silver
                        spend_to_achieve_minor: 10000
                        percentage: 10
                      - id: tier_gold
                        name: Gold
                        spend_to_achieve_minor: 30000
                        percentage: 15
                      - id: tier_platinum
                        name: Platinum
                        spend_to_achieve_minor: 75000
                        percentage: 20
                    is_employee_discount: false
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Salon s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Discount employee:
                summary: Discount employee
                value:
                  type: discount
                  name:
                    sk: Zamestnanecka karta
                    en: Employee card
                  description:
                    sk: Zamestnanecka zlava s mesacnym rozpoctom.
                    en: Employee discount with monthly budget.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: Code128
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/briefcase.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Zostavajuci rozpocet
                        en: Remaining budget
                      value_template: '{{card.employee.budget_remaining_minor | money}}'
                      change_message: null
                    front_left:
                      label:
                        sk: Zlava
                      value_template: 20%
                      change_message: null
                    front_right: null
                    back: []
                  mechanics:
                    type: discount
                    tiers:
                      - name: Employee
                        spend_to_achieve_minor: 0
                        percentage: 20
                    is_employee_discount: true
                    employee:
                      monthly_budget_minor: 50000
                      reset_day: 1
                      budget_counts_as: grossSpend
                      hide_budget_on_pass: false
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Bakery s.r.o.
                    email: hr@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Coupon one time:
                summary: Coupon one time
                value:
                  type: coupon
                  name:
                    sk: Uvitací kupón -15%
                    en: Welcome -15% coupon
                  description:
                    sk: Jednorazovy kupon na prvy nakup.
                    en: One-time coupon for first purchase.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/gift.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header:
                      label:
                        sk: Stav
                        en: Status
                      value_template: '{{card.coupon_status}}'
                      change_message: null
                    front_left:
                      label:
                        sk: Kod
                        en: Code
                      value_template: '{{card.coupon_code}}'
                      change_message: null
                    front_right: null
                    back: []
                  mechanics:
                    type: coupon
                    is_reusable: false
                    reset_count: 0
                    last_reset_at: null
                    reward_value:
                      sk: '-15% zľava'
                      en: '-15% off'
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Bakery s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Coupon reusable:
                summary: Coupon reusable
                value:
                  type: coupon
                  name:
                    sk: Mesacny kupon -5€
                    en: Monthly -€5 coupon
                  description:
                    sk: Pouzite kupon mesacne. Po pouzti sa automaticky obnovi.
                    en: Use the coupon monthly. Auto-resets after use.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: QR
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/gift.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header: null
                    front_left:
                      label:
                        sk: Card
                        en: Card
                      value_template: '{{serial_number}}'
                      change_message: null
                    front_right: null
                    back:
                      - key: terms
                        label:
                          sk: Terms
                          en: Terms
                        value_template: Full terms at www.example.com
                        change_message: null
                  mechanics:
                    type: coupon
                    is_reusable: true
                    reset_count: 0
                    last_reset_at: null
                    reward_value:
                      sk: '-5€ zľava'
                      en: '-€5 off'
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Bakery s.r.o.
                    email: info@demobakery.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Custom pet clinic:
                summary: Custom pet clinic
                value:
                  type: custom
                  name:
                    sk: Pet karta
                    en: Pet card
                  description:
                    sk: Card pacienta veterinarnej kliniky.
                    en: Veterinary clinic patient card.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: Code128
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/dog.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header: null
                    front_left:
                      label:
                        sk: Majitel
                        en: Owner
                      value_template: '{{first_name}} {{last_name}}'
                      change_message: null
                    front_right: null
                    back: []
                  mechanics: null
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Demo Clinic Clinic s.r.o.
                    email: info@democlinic.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata: {}
              Custom event ticket:
                summary: Custom event ticket
                value:
                  type: custom
                  name:
                    sk: Lustok na koncert
                    en: Concert ticket
                  description:
                    sk: Vstupenka na podujatie. Ukazte pri vstupe.
                    en: Event ticket. Show at entrance.
                  languages_supported:
                    - sk
                    - en
                  default_language: sk
                  barcode_format: PDF417
                  design:
                    text_color: '#FFFFFF'
                    bg_color: '#1F4E79'
                    logo_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                    icon_url: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                    custom_background_image_url: null
                    pattern_type: brick
                    show_logo_at_issuing_form: true
                    show_background_color_at_issuing_form: true
                    design_mode: standard
                    pattern_icon_urls:
                      - >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/ticket.png
                    strip_color: '#36C6FA'
                  pass_fields:
                    header: null
                    front_left: null
                    front_right: null
                    back: []
                  mechanics: null
                  form_builder:
                    enabled: true
                    default_language: sk
                    fields:
                      - id: email
                        type: system
                        system_key: email
                        required: true
                        order: 0
                      - id: first_name
                        type: system
                        system_key: first_name
                        required: true
                        order: 1
                      - id: phone
                        type: system
                        system_key: phone
                        required: false
                        order: 2
                  referral:
                    enabled: false
                    referrer_bonus: 0
                    referee_bonus: 0
                  issuer:
                    company_name: Downtown Events s.r.o.
                    email: info@demoevents.sk
                    phone: '+421901234567'
                  terms_of_use:
                    enabled: true
                    text: >-
                      Card je neprenosna a viazana na vasu identifikaciu. Sluzi
                      na zber peciatok pri nakupe v prevadzkach Demo Bakery.
                      Card je platna pocas trvania programu.
                    full_url: https://www.demobakery.sk/terms
                  metadata:
                    event_id: EVT-2026-001
        required: true
      responses:
        '201':
          description: Card created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
              examples:
                Created stamp basic:
                  summary: Created stamp basic
                  value:
                    type: stamp
                    name:
                      sk: Coffee Card
                      en: Coffee card
                    description:
                      sk: Collect 10 stamps, get the 11th coffee free.
                      en: Collect 10 stamps, get the 11th coffee free.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    external_id: loyalty-coffee-stamp-2026
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header: null
                      front_left:
                        label:
                          sk: Card
                          en: Card
                        value_template: '{{serial_number}}'
                        change_message: null
                      front_right: null
                      back:
                        - key: terms
                          label:
                            sk: Terms
                            en: Terms
                          value_template: Full terms at www.example.com
                          change_message: null
                    mechanics:
                      type: stamp
                      stamps_required: 10
                      is_prepaid: false
                      starting_stamps: 1
                      reward_at_completion: 1x Free coffee (any size)
                      stamp_count_images: null
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: true
                      referrer_bonus: 1
                      referee_bonus: 1
                    issuer:
                      company_name: Demo Bakery s.r.o.
                      email: info@demobakery.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    issue_limit: null
                    metadata: {}
                    id: crd_ufeq7ssao43qj67fh26jmlobqm
                    object: card
                    issued_count: 42
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_ufeq7ssao43qj67fh26jmlobqm
                    status: active
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created stamp prepaid:
                  summary: Created stamp prepaid
                  value:
                    type: stamp
                    name:
                      sk: Káva 10-pack predplatne
                      en: Coffee 10-pack subscription
                    description:
                      sk: Kup si 10 kav vopred za zvyhodnenu cenu.
                      en: Buy 10 coffees upfront at a discounted price.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header:
                        label:
                          sk: Zostáva
                          en: Remaining
                        value_template: '{{card.stamps_remaining}}'
                        change_message: null
                      front_left:
                        label:
                          sk: Card
                        value_template: '{{serial_number}}'
                        change_message: null
                      front_right: null
                      back:
                        - key: info
                          label:
                            sk: Info
                          value_template: Card je platna 12 mesiacov od kupy
                          change_message: null
                    mechanics:
                      type: stamp
                      stamps_required: 10
                      is_prepaid: true
                      starting_stamps: 0
                      reward_at_completion: Kazda pecatka = 1x Káva
                      stamp_count_images: null
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: false
                      referrer_bonus: 0
                      referee_bonus: 0
                    issuer:
                      company_name: Demo Bakery s.r.o.
                      email: info@demobakery.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_jhjvddv57pfdnkvz74fptmjgdy
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_jhjvddv57pfdnkvz74fptmjgdy
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created reward flat cashback:
                  summary: Created reward flat cashback
                  value:
                    type: reward
                    name:
                      sk: Cashback card
                      en: Cashback card
                    description:
                      sk: >-
                        Za kazde 1€ ziskas 1 bod. 100 bodov = 1€ k pouzitiu pri
                        dalsom nakupe.
                      en: >-
                        Earn 1 point per €1 spent. 100 points = €1 to use at
                        next purchase.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header:
                        label:
                          sk: Body
                          en: Points
                        value_template: '{{card.points}}'
                        change_message: null
                      front_left:
                        label:
                          sk: Cashback k dispozicii
                          en: Cashback available
                        value_template: '{{card.cashback_available | money}}'
                        change_message: null
                      front_right: null
                      back: []
                    mechanics:
                      type: reward
                      earn_mode: flat
                      earn_rate_per_unit: 1
                      starting_points: 50
                      redemption_mode: cashback
                      cashback_point_value: 0.01
                      rewards: []
                      tiers: []
                      tier_demotion:
                        enabled: false
                        inactivity_days: 365
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: true
                      referrer_bonus: 100
                      referee_bonus: 50
                    issuer:
                      company_name: Demo Coffee s.r.o.
                      email: info@democoffee.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_rj3wkv574lxy4i24q763tuyjie
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_rj3wkv574lxy4i24q763tuyjie
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created reward tiered product:
                  summary: Created reward tiered product
                  value:
                    type: reward
                    name:
                      sk: Vernostny program
                      en: Loyalty program
                    description:
                      sk: >-
                        Postup tier-mi a ziskaj nasoboky bodov. Vymen body za
                        produkty z menu.
                      en: >-
                        Progress through tiers to earn point multipliers. Redeem
                        points for menu items.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header:
                        label:
                          sk: Úrovne
                          en: Tier
                        value_template: '{{card.reward_tier.tier_name}}'
                        change_message: null
                      front_left:
                        label:
                          sk: Body
                          en: Points
                        value_template: '{{card.points}}'
                        change_message: null
                      front_right:
                        label:
                          sk: Násobok
                          en: Multiplier
                        value_template: '{{card.reward_tier.earn_multiplier}}x'
                        change_message: null
                      back: []
                    mechanics:
                      type: reward
                      earn_mode: tiered
                      earn_rate_per_unit: 1
                      starting_points: 0
                      redemption_mode: product
                      cashback_point_value: null
                      rewards:
                        - id: rwd_uas4xm344kos2gac5ky77axquy
                          name: Káva
                          points_required: 100
                        - id: rwd_z6dnezeod6ee4ingbnd5mxai4m
                          name: Tortový kúsok
                          points_required: 250
                        - id: rwd_n3f6kq2yv5xw4ztb7hcj2lm4pa
                          name: Combo menu
                          points_required: 500
                      tiers:
                        - id: tir_swbxxw6ogmmj2muabpnadygkee
                          name: Bronze
                          earn_multiplier: 1
                          spend_to_achieve_minor: 0
                        - id: tir_r4se33bb5tvfzqmm6czx3zltq4
                          name: Silver
                          earn_multiplier: 1.5
                          spend_to_achieve_minor: 50000
                        - id: tir_bu23sde3h6xbyb2vjplaehu2va
                          name: Gold
                          earn_multiplier: 2
                          spend_to_achieve_minor: 200000
                      tier_demotion:
                        enabled: true
                        inactivity_days: 365
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: true
                      referrer_bonus: 200
                      referee_bonus: 100
                    issuer:
                      company_name: Demo Coffee s.r.o.
                      email: info@demobakery.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_fjpwqok2r5ggagbzyq26qstwp4
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_fjpwqok2r5ggagbzyq26qstwp4
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created discount single:
                  summary: Created discount single
                  value:
                    type: discount
                    name:
                      sk: VIP zlava 10%
                      en: VIP 10% discount
                    description:
                      sk: 10% zlava na vsetky sluzby v salone.
                      en: 10% off all services at the salon.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header:
                        label:
                          sk: Zlava
                          en: Discount
                        value_template: 10%
                        change_message: null
                      front_left: null
                      front_right: null
                      back: []
                    mechanics:
                      type: discount
                      tiers:
                        - id: tir_swbxxw6ogmmj2muabpnadygkee
                          name: VIP
                          spend_to_achieve_minor: 0
                          percentage: 10
                      is_employee_discount: false
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: false
                      referrer_bonus: 0
                      referee_bonus: 0
                    issuer:
                      company_name: Demo Salon s.r.o.
                      email: info@demosalon.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_cocb7wea65i5txhpyrsbqxkitq
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_cocb7wea65i5txhpyrsbqxkitq
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created discount tiered:
                  summary: Created discount tiered
                  value:
                    type: discount
                    name:
                      sk: Vernostné úrovne
                      en: Loyalty tiers
                    description:
                      sk: >-
                        Pohyb medzi úrovňami podľa minutia za posledných 12
                        mesiacov.
                      en: >-
                        Move between tiers based on spend over the past 12
                        months.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: QR
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header: null
                      front_left:
                        label:
                          sk: Zlava
                          en: Discount
                        value_template: '{{card.discount.discount_level}}%'
                        change_message: null
                      front_right: null
                      back: []
                    mechanics:
                      type: discount
                      tiers:
                        - id: tir_swbxxw6ogmmj2muabpnadygkee
                          name: Bronze
                          spend_to_achieve_minor: 0
                          percentage: 5
                        - id: tir_r4se33bb5tvfzqmm6czx3zltq4
                          name: Silver
                          spend_to_achieve_minor: 10000
                          percentage: 10
                        - id: tir_bu23sde3h6xbyb2vjplaehu2va
                          name: Gold
                          spend_to_achieve_minor: 30000
                          percentage: 15
                        - id: tir_4mhq7xk2dnv3zwc6pj5ebtafsi
                          name: Platinum
                          spend_to_achieve_minor: 75000
                          percentage: 20
                      is_employee_discount: false
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: false
                      referrer_bonus: 0
                      referee_bonus: 0
                    issuer:
                      company_name: Demo Salon s.r.o.
                      email: info@demobakery.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_ppypgergwgiixsy7vicinoht6q
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_ppypgergwgiixsy7vicinoht6q
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
                Created discount employee:
                  summary: Created discount employee
                  value:
                    type: discount
                    name:
                      sk: Zamestnanecka karta
                      en: Employee card
                    description:
                      sk: Zamestnanecka zlava s mesacnym rozpoctom.
                      en: Employee discount with monthly budget.
                    languages_supported:
                      - sk
                      - en
                    default_language: sk
                    barcode_format: Code128
                    design:
                      text_color: '#FFFFFF'
                      bg_color: '#1F4E79'
                      logo_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
                      icon_url: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
                      custom_background_image_url: null
                      pattern_type: null
                      show_logo_at_issuing_form: true
                      show_background_color_at_issuing_form: true
                      design_mode: standard
                      pattern_icon_urls: []
                      strip_color: '#36C6FA'
                    pass_fields:
                      header:
                        label:
                          sk: Zostavajuci rozpocet
                          en: Remaining budget
                        value_template: '{{card.employee.budget_remaining_minor | money}}'
                        change_message: null
                      front_left:
                        label:
                          sk: Zlava
                        value_template: 20%
                        change_message: null
                      front_right: null
                      back: []
                    mechanics:
                      type: discount
                      tiers:
                        - id: tir_swbxxw6ogmmj2muabpnadygkee
                          name: Employee
                          spend_to_achieve_minor: 0
                          percentage: 20
                      is_employee_discount: true
                      employee:
                        monthly_budget_minor: 50000
                        reset_day: 1
                        budget_counts_as: grossSpend
                        hide_budget_on_pass: false
                    form_builder:
                      enabled: true
                      default_language: sk
                      fields:
                        - id: email
                          type: system
                          system_key: email
                          required: true
                          order: 0
                        - id: first_name
                          type: system
                          system_key: first_name
                          required: true
                          order: 1
                        - id: phone
                          type: system
                          system_key: phone
                          required: false
                          order: 2
                    referral:
                      enabled: false
                      referrer_bonus: 0
                      referee_bonus: 0
                    issuer:
                      company_name: Demo Bakery s.r.o.
                      email: hr@demobakery.sk
                      phone: '+421901234567'
                    terms_of_use:
                      enabled: true
                      text: >-
                        Card je neprenosna a viazana na vasu identifikaciu.
                        Sluzi na zber peciatok pri nakupe v prevadzkach Demo
                        Bakery. Card je platna pocas trvania programu.
                      full_url: https://www.demobakery.sk/terms
                    metadata: {}
                    id: crd_at5ffpcc3udjfnr3biwqdzblli
                    object: card
                    issued_count: 42
                    issue_limit: null
                    registration_url: >-
                      https://vexioncards.one/signup/?id=crd_at5ffpcc3udjfnr3biwqdzblli
                    status: active
                    external_id: null
                    created_at: 1782604800
                    updated_at: 1782604800
                    archived_at: null
          headers: {}
        '400':
          description: Malformed body or missing required field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Missing required type:
                  summary: Missing required type
                  value:
                    error:
                      type: invalid_request_error
                      code: missing_required_field
                      message: card.type is required.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#missing_required_field
                      request_id: req_5f8b2c4a9e1d3
                      param: type
                Invalid languages supported:
                  summary: Invalid languages supported
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: languages_supported must contain at least one language.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      param: languages_supported
          headers: {}
        '401':
          description: Missing, invalid, revoked, or paused API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: authentication_error
                  code: missing_api_key
                  message: >-
                    Authorization header is required. Send 'Authorization:
                    Bearer vxn_sk_...'.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#missing_api_key
                  request_id: req_5f8b2c4a9e1d3
              examples:
                Missing API key:
                  summary: Missing API key
                  value:
                    error:
                      type: authentication_error
                      code: missing_api_key
                      message: >-
                        Authorization header is required. Send 'Authorization:
                        Bearer vxn_sk_...'.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#missing_api_key
                      request_id: req_5f8b2c4a9e1d3
                API key revoked:
                  summary: API key revoked
                  value:
                    error:
                      type: authentication_error
                      code: api_key_revoked
                      message: >-
                        This API key has been revoked. Mint a new key from the
                        dashboard at
                        https://app.vexioncards.one/settings/developers.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#api_key_revoked
                      request_id: req_0s4rpseD8N6fvsdT3kb3bFse9m
                API key paused:
                  summary: API key paused
                  value:
                    error:
                      type: authentication_error
                      code: api_key_paused
                      message: This API key is paused. Resume it from the dashboard.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#api_key_paused
                      request_id: req_R3JwWw43T29wjQ1ReK1GDkwgVJ
          headers: {}
        '403':
          description: API key lacks the required scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: permission_error
                  code: insufficient_scope
                  message: This API key does not have the cards:write scope.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                  request_id: req_5f8b2c4a9e1d3
              examples:
                Insufficient scope:
                  summary: Insufficient scope
                  value:
                    error:
                      type: permission_error
                      code: insufficient_scope
                      message: This API key does not have the cards:write scope.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                      request_id: req_5f8b2c4a9e1d3
          headers: {}
        '409':
          description: >-
            Idempotency-key reuse, balance constraint violated, or
            already-processed conflict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Idempotency key in use:
                  summary: Idempotency key in use
                  value:
                    error:
                      type: invalid_request_error
                      code: idempotency_key_in_use
                      message: >-
                        An earlier request with the same Idempotency-Key is
                        still processing. Retry in a few seconds.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#idempotency_key_in_use
                      request_id: req_5f8b2c4a9e1d3
                Card name already exists:
                  summary: Card name already exists
                  value:
                    error:
                      type: invalid_request_error
                      code: card_name_already_exists
                      message: >-
                        A card template with name 'Coffee Card' already exists
                        in your organization.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#card_name_already_exists
                      request_id: req_5f8b2c4a9e1d3
                      param: name
          headers: {}
        '413':
          description: >-
            Request body exceeds the 1 MB cap. The body was never parsed, so the
            request had no effect.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: invalid_request_error
                  code: request_too_large
                  message: The request body exceeds the maximum allowed size.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#request_too_large
                  request_id: req_5f8b2c4a9e1d3
        '422':
          description: Body parsed but semantically invalid. See field_errors[].
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Mechanics type mismatch:
                  summary: Mechanics type mismatch
                  value:
                    error:
                      type: invalid_request_error
                      code: mechanics_type_mismatch
                      message: >-
                        mechanics.type 'stamp' does not match card type
                        'reward'.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#mechanics_type_mismatch
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.type
                Missing default language in supported:
                  summary: Missing default language in supported
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: >-
                        default_language 'de' must be present in
                        languages_supported.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      param: default_language
                Missing pattern for a standard non-stamp card:
                  summary: Missing pattern for a standard non-stamp card
                  value:
                    error:
                      type: invalid_request_error
                      code: missing_required_field
                      message: >-
                        design.pattern_icon_urls must contain at least 1 icon
                        URL for a standard-design, non-stamp card; the pass
                        background has no other content source to render from.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#missing_required_field
                      request_id: req_5f8b2c4a9e1d3
                      param: design.pattern_icon_urls
                Custom icon is not square:
                  summary: Custom icon is not square
                  value:
                    error:
                      type: invalid_request_error
                      code: image_not_square
                      message: >-
                        design.pattern_icon_urls[0] must be a square image
                        (width equal to height, within 2% tolerance) or a
                        platform library icon from GET /v1/icons; received
                        192x96.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_not_square
                      request_id: req_5f8b2c4a9e1d3
                      param: design.pattern_icon_urls[0]
                Background image has the wrong aspect ratio:
                  summary: Background image has the wrong aspect ratio
                  value:
                    error:
                      type: invalid_request_error
                      code: image_aspect_ratio_invalid
                      message: >-
                        design.individual_background_image_url must be a 527:202
                        (~2.61:1) image, at least 527px wide, within 5% ratio
                        tolerance; received 192x192.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_aspect_ratio_invalid
                      request_id: req_5f8b2c4a9e1d3
                      param: design.individual_background_image_url
                Per-language image map has an unsupported language key:
                  summary: Per-language image map has an unsupported language key
                  value:
                    error:
                      type: invalid_request_error
                      code: parameter_invalid
                      message: >-
                        design.individual_background_image_url has an
                        unsupported language key 'de'. Allowed languages: sk,
                        en.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#parameter_invalid
                      request_id: req_5f8b2c4a9e1d3
                      param: design.individual_background_image_url
                stamp_count_images positions do not share the same language set:
                  summary: >-
                    stamp_count_images positions do not share the same language
                    set
                  value:
                    error:
                      type: invalid_request_error
                      code: stamp_count_images_language_mismatch
                      message: >-
                        mechanics.stamp_count_images[2].image_url has a
                        different language set than position 0 - every position
                        must supply the SAME languages (a per-language image set
                        is complete or absent across all positions; mixing a
                        plain URL with a language map is also a mismatch).
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#stamp_count_images_language_mismatch
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.stamp_count_images[2].image_url
                Image is not a valid PNG:
                  summary: Image is not a valid PNG
                  value:
                    error:
                      type: invalid_request_error
                      code: image_format_invalid
                      message: >-
                        design.icon_url is not a valid PNG file (magic-byte
                        signature mismatch). Only PNG is accepted.
                      param: design.icon_url
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_format_invalid
                      request_id: req_5f8b2c4a9e1d3
                Image exceeds the 3 MB cap:
                  summary: Image exceeds the 3 MB cap
                  value:
                    error:
                      type: invalid_request_error
                      code: image_too_large
                      message: design.logo_url exceeds the 3 MB maximum image size.
                      param: design.logo_url
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_too_large
                      request_id: req_5f8b2c4a9e1d3
                Square image below the minimum side:
                  summary: Square image below the minimum side
                  value:
                    error:
                      type: invalid_request_error
                      code: image_resolution_too_small
                      message: design.icon_url must be at least 88x88px.
                      param: design.icon_url
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_resolution_too_small
                      request_id: req_5f8b2c4a9e1d3
                Square image above the maximum side:
                  summary: Square image above the maximum side
                  value:
                    error:
                      type: invalid_request_error
                      code: image_resolution_too_large
                      message: design.icon_url must be at most 2000x2000px.
                      param: design.icon_url
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#image_resolution_too_large
                      request_id: req_5f8b2c4a9e1d3
                Image URL could not be trusted (opaque by design):
                  summary: Image URL could not be trusted (opaque by design)
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_image_url
                      message: >-
                        design.logo_url did not yield a usable image.
                        Unreachable hosts, non-200 responses, blocked targets
                        and rejected schemes all report this same code
                        deliberately.
                      param: design.logo_url
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_image_url
                      request_id: req_5f8b2c4a9e1d3
                Unknown form field reference:
                  summary: pass_fields token references an undeclared custom form field
                  value:
                    error:
                      type: invalid_request_error
                      code: unknown_form_field_reference
                      message: >-
                        pass_fields references unknown form field
                        '{{custom.blood_group}}' - declare it on
                        form_builder.fields (type:'custom') first.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#unknown_form_field_reference
                      request_id: req_5f8b2c4a9e1d3
                      param: pass_fields
                Card type not creatable via the API:
                  summary: type is membership or gift_card - not a creatable value
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: One or more fields failed validation.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      field_errors:
                        - param: type
                          code: invalid_enum_value
                          message: >-
                            type must be one of: stamp, reward, discount,
                            coupon, custom. gift_card and membership cards exist
                            too, but they are dashboard-managed and read-only
                            through this API - create them in the dashboard or
                            through a connected POS.
                Levels need a level:
                  summary: A reward card with levels on has no level
                  value:
                    error:
                      type: invalid_request_error
                      code: levels_need_a_level
                      message: >-
                        Levels are switched on but the card has no level. Add a
                        level or switch levels off.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#levels_need_a_level
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.tiers
                Level needs a valid multiplier or spend:
                  summary: A level has a multiplier below 1 or a spend below 0
                  value:
                    error:
                      type: invalid_request_error
                      code: levels_invalid_level
                      message: >-
                        Every level needs a cashback multiplier of at least 1×
                        and a spend of 0 or more.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#levels_invalid_level
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.tiers
                Starting bonus above the limit:
                  summary: >-
                    starting_points above the limit of the card (cashback card,
                    EUR 50 at 0.01)
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: One or more fields failed validation.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      field_errors:
                        - param: mechanics.starting_points
                          code: out_of_range
                          message: >-
                            Starting bonus is too high. The most is 50 EUR (5000
                            points at 0.01 EUR per point).
                Referral bonus above the limit:
                  summary: >-
                    referrer_bonus above the limit of the card (cashback card,
                    EUR 50 at 0.01)
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: One or more fields failed validation.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      field_errors:
                        - param: referral.referrer_bonus
                          code: out_of_range
                          message: >-
                            Referrer bonus is too high. The most is 50 EUR (5000
                            points at 0.01 EUR per point).
                Reward level without spend_to_achieve_minor:
                  summary: A reward level without spend_to_achieve_minor
                  value:
                    error:
                      type: invalid_request_error
                      code: tier_achieve_field_missing
                      message: mechanics.tiers[1].spend_to_achieve_minor is required.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#tier_achieve_field_missing
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.tiers[1].spend_to_achieve_minor
                Level with visits_to_achieve:
                  summary: A level with visits_to_achieve on a card that counts spend
                  value:
                    error:
                      type: invalid_request_error
                      code: tier_achieve_field_not_allowed
                      message: >-
                        mechanics.tiers[0].visits_to_achieve is not valid for
                        this card - use spend_to_achieve_minor (minor currency
                        units) instead.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#tier_achieve_field_not_allowed
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.tiers[0].visits_to_achieve
                netSpend with a 100 percent step:
                  summary: netSpend with a discount step at 100 percent
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: >-
                        With mechanics.employee.budget_counts_as 'netSpend',
                        every discount step must be below 100 %: the amount paid
                        is 0, so the limit never fills.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.employee.budget_counts_as
                Coupon without an offer:
                  summary: A coupon with neither reward_value nor linked_card_template
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: >-
                        A coupon must define what the customer gets: set
                        mechanics.reward_value (the coupon offer) or link an
                        upgrade card via mechanics.linked_card_template.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_5f8b2c4a9e1d3
                      param: mechanics.reward_value
                Cashback above the limit:
                  summary: >-
                    earn rate and point value pay 40 percent on a card without
                    levels
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: One or more fields failed validation.
                      field_errors:
                        - param: mechanics.cashback_point_value
                          code: out_of_range
                          message: >-
                            The card pays 40 % cashback. The most is 30 % for a
                            card without levels. Lower the points per currency
                            unit, the point value or the level multiplier.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
                Reward value is not a whole number:
                  summary: value in minor units must be a whole number of at least 1
                  value:
                    error:
                      type: invalid_request_error
                      code: validation_failed
                      message: One or more fields failed validation.
                      field_errors:
                        - param: mechanics.rewards[0].value
                          code: invalid_value
                          message: >-
                            value must be a whole number of minor currency
                            units, more than 0.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#validation_failed
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
                Unknown key in mechanics:
                  summary: a key the card type does not have
                  value:
                    error:
                      type: invalid_request_error
                      code: parameter_unknown
                      message: >-
                        Unknown or non-settable parameter:
                        mechanics.rewards[0].colour. Remove it - it is not
                        accepted on this operation.
                      param: mechanics.rewards[0].colour
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#parameter_unknown
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
          headers: {}
        '429':
          description: Per-minute or per-day rate limit exceeded. Respect Retry-After.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: rate_limit_error
                  code: rate_limited
                  message: Too many requests. Try again in 5 seconds.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#rate_limited
                  request_id: req_5f8b2c4a9e1d3
                  retry_after: 5
              examples:
                Rate limited:
                  summary: Rate limited
                  value:
                    error:
                      type: rate_limit_error
                      code: rate_limited
                      message: Too many requests. Try again in 5 seconds.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#rate_limited
                      request_id: req_5f8b2c4a9e1d3
                      retry_after: 5
          headers: {}
        '500':
          description: Server error. Retry-safe on idempotent calls.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: api_error
                  code: internal_error
                  message: >-
                    An internal error occurred. The request may be retried with
                    the same Idempotency-Key.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#internal_error
                  request_id: req_5f8b2c4a9e1d3
              examples:
                Http 500 default:
                  summary: Http 500 default
                  value:
                    error:
                      type: api_error
                      code: internal_error
                      message: >-
                        An internal error occurred. The request may be retried
                        with the same Idempotency-Key.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#internal_error
                      request_id: req_5f8b2c4a9e1d3
          headers: {}
        '503':
          description: >-
            Image storage failed. The supplied image URL passed every validation
            gate but VEXiON could not store a copy of it. The write was NOT
            applied. This is transient - retry the request with the same
            Idempotency-Key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: api_error
                  code: image_ingest_failed
                  message: >-
                    VEXiON could not store the image for design.stamp_icon_url.
                    This is a transient failure - retry the request.
                  param: design.stamp_icon_url
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#image_ingest_failed
                  request_id: req_5f8b2c4a9e1d3
                  retry_after: 5
      deprecated: false
components:
  schemas:
    CardCreateRequest:
      type: object
      description: >-
        Body for POST /v1/cards. Creates a card template. `type`, `name`,
        `description`, `default_language`, `design`, `form_builder` and `issuer`
        are required - everything else has sensible defaults or can be added via
        PATCH. The mechanics block must match the chosen type. Once created,
        type is immutable; all other fields can be patched.
      allOf:
        - if:
            properties:
              type:
                not:
                  const: stamp
              design:
                type: object
                properties:
                  design_mode:
                    const: individual
                required:
                  - design_mode
            required:
              - type
              - design
          then:
            properties:
              design:
                required:
                  - individual_background_image_url
        - if:
            properties:
              type:
                const: stamp
              design:
                type: object
                properties:
                  design_mode:
                    const: individual
                required:
                  - design_mode
            required:
              - type
              - design
          then:
            properties:
              mechanics:
                required:
                  - stamp_count_images
        - if:
            properties:
              type:
                const: custom
            required:
              - type
          then:
            required:
              - pass_fields
        - if:
            properties:
              type:
                not:
                  const: custom
            required:
              - type
          then:
            required:
              - mechanics
      required:
        - type
        - name
        - description
        - default_language
        - design
        - form_builder
        - issuer
      properties:
        type:
          type: string
          enum:
            - stamp
            - reward
            - discount
            - coupon
            - custom
          description: >-
            REQUIRED. Card mechanics type. Immutable after creation. Determines
            which mechanics block applies, which initial_state overrides are
            valid on customer enrollment, and the wallet pass layout.
            Notifications reach every card type, including `custom`.


            `membership` and `gift_card` cards exist too, but they are
            dashboard-managed: create them there or through a connected POS, not
            through this endpoint. Once created, they are fully readable through
            this API.
        name:
          oneOf:
            - type: string
              minLength: 1
              maxLength: 100
            - type: object
              additionalProperties:
                type: string
                maxLength: 100
              properties: {}
          description: >-
            REQUIRED. Display name (string OR per-language object). At minimum
            the default_language must have a value.
        default_language:
          type: string
          enum:
            - sk
            - cs
            - en
            - de
          description: >-
            REQUIRED. Default language for the card. Determines which language
            is used when the customer's preference is not supported.
        external_id:
          maxLength: 255
          description: >-
            Optional partner reference for syncing with external systems.
            Persisted on the card template and returned on reads (null when
            unset). Not unique: two templates may share a reference, so `GET
            /v1/cards?external_id=` filters and can return more than one row.
            Send null to clear it.
          type:
            - string
            - 'null'
        description:
          description: >-
            REQUIRED. Brief customer-facing description of the card/loyalty
            program. Required by Apple PKPass (description field, used for
            accessibility/voice-over) and Google LoyaltyClass (programName
            description for review approval). Can be a string OR per-language
            object for multi-language passes. Min 1, max 500 chars per language.
          oneOf:
            - type: string
              minLength: 1
              maxLength: 500
            - type: object
              additionalProperties:
                type: string
                minLength: 1
                maxLength: 500
              description: >-
                Per-language object, e.g., { 'sk': 'Zbieraj 10 pečiatok...',
                'en': 'Collect 10 stamps...' }
              example:
                sk: Zbieraj 10 peciatok, ziskaj 11. kavu zdarma.
                en: Collect 10 stamps, get the 11th coffee free.
              properties: {}
          example:
            sk: Zbieraj 10 peciatok, ziskaj 11. kavu zdarma.
            en: Collect 10 stamps, get the 11th coffee free.
        languages_supported:
          type: array
          items:
            type: string
            enum:
              - sk
              - cs
              - en
              - de
          description: >-
            Languages this card supports. Defaults to [default_language] when
            omitted.
        barcode_format:
          type: string
          enum:
            - QR
            - Aztec
            - PDF417
            - Code128
            - Code39
            - EAN8
            - EAN13
            - ITF14
            - UPCA
            - DataMatrix
            - Codabar
          default: QR
          description: >-
            Barcode format for the wallet pass. Default 'QR' (works on both
            Apple and Google Wallet).
        purchase_amount_required:
          type: boolean
          default: false
          description: >-
            Scanner UX flag. When true, staff MUST enter a purchase amount
            before they can record a transaction against this card. When false,
            the amount field is optional (staff can record visit-only /
            quick-redeem transactions). Affects scanner behavior across all card
            types.
          example: false
        design:
          $ref: '#/components/schemas/CardDesign'
        pass_fields:
          $ref: '#/components/schemas/PassFields'
        form_builder:
          type: object
          description: >-
            REQUIRED. Signup form rendered at the public claim URL
            (/signup/{card_id}). ENFORCEMENT: email (system_key='email') and
            first_name (system_key='first_name') are ALWAYS required and CANNOT
            be deleted from fields[]. Server returns 422
            'form_builder_missing_email_field' or
            'form_builder_missing_first_name_field' if either is missing. Both
            fields' system_key cannot be unset, but their translations can be
            overridden (e.g., display 'Tel. kontakt' instead of 'Email' in
            Slovak). Partners can add additional system fields (last_name,
            phone, date_of_birth, address, consent) or custom_field references
            with custom labels.
          required:
            - fields
          properties:
            enabled:
              type: boolean
              default: true
              description: >-
                When false, the public signup URL is fully disabled - customers
                can only be enrolled via API or dashboard.
            default_language:
              type: string
              enum:
                - sk
                - cs
                - en
                - de
              description: >-
                Default language the form renders in. Customer can switch via
                language selector.
            fields:
              type: array
              minItems: 2
              description: >-
                Ordered list of fields shown on the signup form. MUST contain at
                minimum: (1) system_key='email' with required:true, (2)
                system_key='first_name' with required:true. These two CANNOT be
                deleted (matches the dashboard wizard which locks them).
                Drag-to-reorder is preserved via the 'order' field. Any field
                (system OR custom) can have translations_override to localize
                labels per language.
              items:
                type: object
                required:
                  - id
                  - type
                  - required
                  - order
                properties:
                  id:
                    type: string
                    description: >-
                      Field identifier on this card. For system fields,
                      conventionally matches system_key. For custom fields,
                      conventionally matches the referenced custom_field.key.
                    example: email
                  type:
                    type: string
                    enum:
                      - system
                      - custom
                    description: >-
                      'system' uses one of the built-in field types; 'custom'
                      references a custom_field by ID.
                  system_key:
                    type: string
                    enum:
                      - email
                      - first_name
                      - last_name
                      - phone
                      - date_of_birth
                      - consent
                      - preferred_language
                    description: Built-in field key (only when type='system').
                  custom_field_id:
                    type: string
                    pattern: ^cf_[A-Za-z0-9]+$
                    description: >-
                      Reference to a custom_field by ID (only when
                      type='custom').
                  required:
                    type: boolean
                    description: >-
                      Whether this field must be filled to submit the form. On
                      POST /v1/customers this is enforced only for the system
                      fields `email`, `first_name`, `last_name`, `phone` and
                      `date_of_birth`; `consent` and `preferred_language` are
                      public-signup-page only and are never required through the
                      API.
                  order:
                    type: integer
                    minimum: 0
                    description: Display order (0-based, lower shows first).
                  translations_override:
                    description: >-
                      Per-language label overrides for this field, keyed by
                      language code: `{ "sk": { "label": "Meno" }, "en": {
                      "label": "Name" } }`. Falls back to the built-in preset
                      for a system field.
                    additionalProperties:
                      type: object
                      properties:
                        label:
                          type: string
                    example:
                      sk:
                        label: Tel. kontakt
                        placeholder: +421 9xx xxx xxx
                      en:
                        label: Phone number
                        placeholder: +421 9xx xxx xxx
                    type:
                      - object
                      - 'null'
              example:
                - id: email
                  type: system
                  system_key: email
                  required: true
                  order: 0
                - id: first_name
                  type: system
                  system_key: first_name
                  required: true
                  order: 1
                - id: phone
                  type: system
                  system_key: phone
                  required: false
                  order: 2
        mechanics:
          description: >-
            Type-specific mechanics block. REQUIRED but must match Card.type.
            Pick the matching tab. Custom = pick 'Custom (null)'. An unknown key
            inside `mechanics` (also inside a reward, a level or `employee`)
            answers `422 parameter_unknown` with the full name, for example
            `mechanics.rewards[0].colour`. Before, an unknown key was ignored.
          anyOf:
            - $ref: '#/components/schemas/CardMechanics_Stamp'
            - $ref: '#/components/schemas/CardMechanics_Reward'
            - $ref: '#/components/schemas/CardMechanics_Discount'
            - $ref: '#/components/schemas/CardMechanics_Coupon'
            - $ref: '#/components/schemas/CardMechanics_Membership'
            - $ref: '#/components/schemas/CardMechanics_GiftCard'
            - title: Custom (null - no built-in mechanics)
              type: 'null'
              description: >-
                For Card.type='custom'. Notifications reach custom cards like
                any other type.
        issue_limit:
          minimum: 0
          description: >-
            Maximum customer enrollments. Null = unlimited. Capped at
            100,000,000 to prevent overflow abuse. 0 and null both mean
            unlimited; both read back as null.
          type:
            - integer
            - 'null'
          maximum: 100000000
        referral:
          description: Referral program configuration.
          properties:
            enabled:
              type: boolean
              default: false
              description: Turn the referral program on or off for this card.
            referrer_bonus:
              type: integer
              minimum: 0
              description: >-
                Bonus units (stamps or points, per card type) granted to the
                referrer when a referred signup completes. The schema bound is
                100,000,000,000. On a cashback card (`redemption_mode:
                "cashback"`) the value is still points; the dashboard shows and
                edits it as credit in the business currency: credit = points x
                `cashback_point_value` (200 points at 0.01 = EUR 2.00). The real
                limit depends on the card: stamps up to 1,000; points up to
                100,000; cashback about EUR 50 of credit in your currency (EUR
                50, CZK 1,250, HUF 20,000, PLN 200, RON 250, GBP 50, CHF 50, USD
                50), so the most points are that amount divided by
                `cashback_point_value`; discount about EUR 1,000 of spend (CZK
                25,000, HUF 400,000, PLN 4,000, RON 5,000, GBP 1,000, CHF 1,000,
                USD 1,000). Whole numbers only. On a discount card the bonus is
                spend in whole currency units (not minor units) that raises the
                level. Above the limit the request answers 422
                `validation_failed` with `field_errors[].code` `out_of_range` on
                `referral.referrer_bonus` or `referral.referee_bonus`. On PATCH
                only a changed value is checked, plus the stored value again
                when the change makes a point worth more.
              maximum: 100000000000
            referee_bonus:
              type: integer
              minimum: 0
              description: >-
                Bonus units granted to the newly referred customer. The schema
                bound is 100,000,000,000. On a cashback card (`redemption_mode:
                "cashback"`) the value is still points; the dashboard shows and
                edits it as credit in the business currency: credit = points x
                `cashback_point_value` (200 points at 0.01 = EUR 2.00). The real
                limit depends on the card: stamps up to 1,000; points up to
                100,000; cashback about EUR 50 of credit in your currency (EUR
                50, CZK 1,250, HUF 20,000, PLN 200, RON 250, GBP 50, CHF 50, USD
                50), so the most points are that amount divided by
                `cashback_point_value`; discount about EUR 1,000 of spend (CZK
                25,000, HUF 400,000, PLN 4,000, RON 5,000, GBP 1,000, CHF 1,000,
                USD 1,000). Whole numbers only. On a discount card the bonus is
                spend in whole currency units (not minor units) that raises the
                level. Above the limit the request answers 422
                `validation_failed` with `field_errors[].code` `out_of_range` on
                `referral.referrer_bonus` or `referral.referee_bonus`. On PATCH
                only a changed value is checked, plus the stored value again
                when the change makes a point worth more.
              maximum: 100000000000
          type:
            - object
            - 'null'
        issuer:
          type: object
          description: >-
            REQUIRED. Issuer details. company_name is REQUIRED because it maps
            to Apple PKPass.organizationName (shown on lock screen) and Google
            LoyaltyClass.issuerName (shown in pass list). Without it, Apple
            shows 'Unknown Issuer' and Google rejects the pass during review.
          required:
            - company_name
          properties:
            company_name:
              type: string
              minLength: 1
              maxLength: 100
              description: >-
                REQUIRED. Business legal name. Maps to Apple
                PKPass.organizationName (lock screen) and Google
                LoyaltyClass.issuerName (max 20 chars displayed on small
                screens). Use the short customer-facing name (e.g., 'Demo
                Bakery', not 'Pekáreň Vlna s.r.o.').
              example: Demo Bakery
            email:
              format: email
              description: >-
                Issuer support email shown on pass back side (Apple) and contact
                details (Google). Optional but strongly recommended for customer
                support.
              example: support@demobakery.sk
              maxLength: 255
              type:
                - string
                - 'null'
            phone:
              description: >-
                Issuer support phone shown on pass back side. E.164 format
                preferred.
              example: '+421905123456'
              pattern: ^\+[1-9]\d{1,14}$
              minLength: 8
              maxLength: 16
              type:
                - string
                - 'null'
        terms_of_use:
          description: >-
            Optional terms and conditions shown on the wallet pass back side.
            `text` supports per-language (string OR object {sk, cs, en, de}) -
            the pass renders the customer's own language variant when present,
            falling back to the card's default-language text otherwise.
            `full_url` is a single URL only (no per-language variant).
          properties:
            enabled:
              type: boolean
              default: false
              description: Toggle to show/hide T&C on the pass.
            text:
              description: >-
                Inline T&C text (max 2000 chars per language). Single string OR
                per-language object. The card's default_language variant is
                always what the dashboard editor shows and edits; other-language
                variants render on the pass only for a customer whose preferred
                language matches them, falling back to the default-language text
                otherwise.
              anyOf:
                - type: string
                  title: Single text
                  maxLength: 2000
                  example: Terms apply...
                - type: object
                  title: Per-language
                  additionalProperties:
                    type: string
                    maxLength: 2000
                  example:
                    sk: Podmienky platia...
                    en: Terms apply...
                  properties: {}
            full_url:
              type:
                - string
                - 'null'
              format: uri
              description: >-
                Your loyalty programme rules, as one https URL. The signup page
                links them next to VEXiON's card terms (they supplement them,
                never replace them); the dashboard lists this link as the
                programme rules. Sending a URL shows the link on the signup
                page; sending null removes it; omit the key to leave the current
                link unchanged. The URL must use https and be a valid absolute
                URL: javascript:, data:, ftp:, file:, plain http: and malformed
                links are rejected with 422 invalid_image_url naming the param,
                and nothing is stored. The server never fetches it. Single URL,
                no per-language variant. When the link is shown, the signup
                checkbox reads that the member accepts VEXiON's card terms, the
                privacy policy and your loyalty programme rules, and the link is
                stored with the member's consent record. If your rules conflict
                with VEXiON's card terms, the card terms prevail. You are
                responsible for the content, lawfulness and availability of your
                rules; VEXiON does not review them.
          type:
            - object
            - 'null'
        metadata:
          type: object
          additionalProperties:
            type: string
            maxLength: 500
          maxProperties: 50
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,40}$
          description: >-
            Free-form partner key-value map.


            Persisted on the card template and returned on reads. Omitted
            entirely when empty rather than sent as null. On PATCH the map is
            merged per key, so sending one key never clears the others.
          properties: {}
        links:
          type: array
          description: >-
            Card-attached links shown on the wallet pass back side - business
            links (website/social) and a review-prompt link. Max 10 items.
          maxItems: 10
          items:
            type: object
            required:
              - type
              - value
            properties:
              type:
                type: string
                enum:
                  - website
                  - instagram
                  - facebook
                  - google_review
                description: >-
                  Link type. 'website' = company website, 'instagram'/'facebook'
                  = social profiles, 'google_review' = leave-a-review prompt for
                  Google Business.
              value:
                type: string
                description: The link value - always a full URL.
                minLength: 1
                maxLength: 500
          example:
            - type: website
              value: https://democoffee.sk
            - type: instagram
              value: https://instagram.com/democoffee
            - type: google_review
              value: https://g.page/r/abc123/review
    Card:
      type: object
      description: >-
        A card template defines the loyalty program a merchant offers. The
        template controls visual design, mechanics
        (stamps/points/discount/etc.), the customer signup form, wallet pass
        content, and behavioral defaults. Customers are enrolled in exactly one
        card via POST /v1/customers. Cards are immutable on type after creation.
      required:
        - id
        - type
        - name
        - description
        - default_language
        - languages_supported
        - design
        - issuer
        - mechanics
      properties:
        id:
          type: string
          pattern: ^crd_[A-Za-z0-9]+$
          description: >-
            Card identifier - an opaque, server-issued token prefixed crd_.
            Immutable.
          example: crd_sophcrprtvpmmnwxah2lomnkee
        object:
          type: string
          const: card
          description: >-
            Resource type discriminator. Always 'card' for this resource. Useful
            for client libraries that handle polymorphic responses (e.g.,
            webhooks delivering mixed event types). Server always sets it.
          example: card
        type:
          type: string
          enum:
            - stamp
            - reward
            - discount
            - coupon
            - membership
            - gift_card
            - custom
          description: >-
            Card mechanics type. Immutable after creation. Drives which
            mechanics block applies, which initial_state overrides are valid on
            POST /v1/customers, and which wallet pass layout is used. 'custom'
            is partner-driven and has no built-in mechanics. Notifications reach
            every card type, including `custom`.
          example: stamp
        external_id:
          maxLength: 255
          description: >-
            Your stable reference for this card template (e.g., your internal
            product SKU). Useful for syncing with external systems. Persisted on
            the card template and returned on reads (null when unset). Not
            unique: two templates may share a reference, so `GET
            /v1/cards?external_id=` filters and can return more than one row.
            Send null to clear it.
          example: loyalty-coffee-stamp-2026
          type:
            - string
            - 'null'
        name:
          oneOf:
            - type: string
              minLength: 1
              maxLength: 100
            - type: object
              additionalProperties:
                type: string
                maxLength: 100
              description: Per-language map (sk, cs, en, de -> name).
              properties: {}
          description: >-
            Display name of the card template. Accepts a plain string (used in
            all languages) OR a per-language object {sk: '...', en: '...'} for
            localized names.
          example:
            sk: Káva karta
            en: Coffee card
        description:
          description: >-
            REQUIRED. Brief customer-facing description of the card/loyalty
            program. Required by Apple PKPass (description field, used for
            accessibility/voice-over) and Google LoyaltyClass (programName
            description for review approval). Can be a string OR per-language
            object for multi-language passes. Min 1, max 500 chars per language.
          oneOf:
            - type: string
              minLength: 1
              maxLength: 500
            - type: object
              additionalProperties:
                type: string
                minLength: 1
                maxLength: 500
              description: >-
                Per-language object, e.g., { 'sk': 'Zbieraj 10 pečiatok...',
                'en': 'Collect 10 stamps...' }
              example:
                sk: Zbieraj 10 peciatok, ziskaj 11. kavu zdarma.
                en: Collect 10 stamps, get the 11th coffee free.
              properties: {}
          example:
            sk: Zbieraj 10 peciatok, ziskaj 11. kavu zdarma.
            en: Collect 10 stamps, get the 11th coffee free.
        languages_supported:
          type: array
          items:
            type: string
            enum:
              - sk
              - cs
              - en
              - de
          minItems: 1
          description: >-
            Languages this card template supports. Customer.language must be one
            of these. Pass content and notification templates must provide
            values for each.
          example:
            - sk
            - en
        default_language:
          type: string
          enum:
            - sk
            - cs
            - en
            - de
          description: >-
            Default language used when the customer's preferred language is not
            in languages_supported.
          example: sk
        barcode_format:
          type: string
          enum:
            - QR
            - Aztec
            - PDF417
            - Code128
            - Code39
            - EAN8
            - EAN13
            - ITF14
            - UPCA
            - DataMatrix
            - Codabar
          default: QR
          description: >-
            Barcode format rendered on the wallet pass to identify customers at
            POS. QR/Aztec/PDF417/Code128 work on both Apple and Google Wallet;
            others are Google Wallet only.
          example: QR
        purchase_amount_required:
          type: boolean
          default: false
          description: >-
            Scanner UX flag. When true, staff MUST enter a purchase amount
            before they can record a transaction against this card. When false,
            the amount field is optional (staff can record visit-only /
            quick-redeem transactions). Affects scanner behavior across all card
            types.
          example: false
        design:
          type: object
          description: >-
            Visual design of the wallet pass. Required: design_mode, bg_color,
            text_color, icon_url (logo_url is optional; the pass falls back to
            text when omitted).


            VISUAL LAYOUT by mode + type:
              - design_mode=standard + stamp: mechanics.stamp_icon_url + (optional custom_background_image_url)
              - design_mode=individual + stamp: mechanics.stamp_count_images (N+1 per-position images; image_url accepts a plain URL or an OPTIONAL per-language map - every position must share the same language set; each customer sees the variant in their language, else sk, else en, else the first one)
              - design_mode=standard + non-stamp: pattern_type + pattern_icon_urls + (optional custom_background_image_url)
                Server AUTO-GENERATES the final pattern image per supported language: pass a per-language object as card description and each language gets its own image; single-language cards (or a plain-string description) get one image. Each customer is served the image matching the language they picked at signup (fallback sk, then en, then the first one). No image URLs are needed for the standard pattern.
              - design_mode=individual + non-stamp: individual_background_image_url REQUIRED (accepts a plain URL or an OPTIONAL per-language map; each customer sees the variant in their language, else sk, else en, else the first one)

            TIER OVERRIDES: tiered reward/discount cards use
            mechanics.tiers[i].background_image_urls (per-language) which
            overrides any card-level background for customers at that tier.
          required:
            - design_mode
            - bg_color
            - text_color
            - icon_url
          properties:
            design_mode:
              type: string
              enum:
                - standard
                - individual
              description: >-
                Design approach: 'standard' uses icons from our library (10,000+
                3D icons across categories). 'individual' uses fully custom
                uploaded images. For STAMP: standard = single repeating icon,
                individual = per-stamp position images. For NON-STAMP: standard
                = pattern + up to 3 library icons, individual = full custom
                background.
              example: standard
            bg_color:
              type: string
              pattern: ^#[0-9A-Fa-f]{6}$
              description: >-
                REQUIRED. Pass background color in #RRGGBB hex. Maps to Apple
                PKPass.backgroundColor + Google LoyaltyClass.hexBackgroundColor.
              example: '#1F4E79'
            text_color:
              type: string
              pattern: ^#[0-9A-Fa-f]{6}$
              description: >-
                REQUIRED. Foreground text color in #RRGGBB hex. Maps to Apple
                PKPass.foregroundColor. Must contrast bg_color for
                accessibility.
              example: '#FFFFFF'
            strip_color:
              type: string
              pattern: ^#[0-9A-Fa-f]{6}$
              description: >-
                Strip / accent color in #RRGGBB hex - the panel behind the
                stamps (stamp cards) or behind the auto-generated pattern
                (non-stamp cards). Required on write when design_mode is
                standard. Not used for individual designs, where the uploaded
                background image covers this area.
              example: '#36C6FA'
            logo_url:
              type:
                - string
                - 'null'
              format: uri
              description: >-
                Wide header logo, or null when the card has none (the pass then
                shows text: Apple logoText = card name, Google programName).
                Image rules: https only, PNG only (magic-byte verified), ≤ 3MB,
                3.2:1 aspect ratio (480x150, within 5% tolerance), minimum
                240x75px (no ceiling). VEXiON stores a copy of every card image.
                This is the VEXiON copy URL, not necessarily the URL you sent.
              example: >-
                https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
            icon_url:
              type: string
              format: uri
              description: >-
                REQUIRED. Small square icon shown in Wallet push notifications
                and list view (Apple PKPass icon.png / Google smallIconUrl) -
                the ONLY strictly-required image in the Apple PKPass spec;
                without it notifications fail to display. Image rules: https
                only, PNG only (magic-byte verified, never Content-Type), ≤ 3MB
                real streamed bytes, square (width equal to height, within 2%
                tolerance) between 88x88 and 2000x2000px. A non-library URL
                failing any of these returns 422 (invalid_image_url /
                image_format_invalid / image_too_large / image_not_square /
                image_resolution_too_small / image_resolution_too_large). A GET
                /v1/icons library icon is trusted pre-vetted square and only
                checked for https reachability. VEXiON stores a copy of the
                image. Reads return the VEXiON copy URL, not the URL you sent. A
                URL that VEXiON returned earlier (for example one you got back
                from this API, or a library icon) is kept as is.
              example: >-
                https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/icon-512x512.png?v=2026-09-17
            custom_background_image_url:
              format: uri
              description: >-
                STANDARD design_mode only (any card type). Optional photo
                BACKDROP rendered BEHIND the auto-generated content (stamp icons
                for stamp cards, pattern + icons for non-stamp cards). Adds
                branded photography to the standard layout. Ignored when
                design_mode='individual' (use individual_background_image_url or
                mechanics.stamp_count_images instead). Must match the pass strip
                aspect ratio (~2.61:1, i.e. 527:202) within 5% tolerance and be
                at least 527px wide; the server returns 422
                image_aspect_ratio_invalid otherwise. Image rules: https only,
                PNG only (magic-byte verified), ≤ 3MB, width between 527px and
                4000px - 422 image_format_invalid / image_too_large /
                image_resolution_too_small / image_resolution_too_large on
                violation, in addition to image_aspect_ratio_invalid above.
                VEXiON stores a copy of the image. Reads return the VEXiON copy
                URL, not the URL you sent. A URL that VEXiON returned earlier
                (for example one you got back from this API, or a library icon)
                is kept as is.
              example: null
              type:
                - string
                - 'null'
            individual_background_image_url:
              description: >-
                Image URL: either a single URL string (used for all languages)
                or an object mapping language codes (sk, cs, en, de) to URLs for
                per-language variants (use when text is baked into the image).
                OPTIONAL per-language: a single URL is valid and serves every
                customer via fallback. WRITE: a map stores every variant; on
                PATCH a plain string (or null) clears any previously stored map.
                Map keys must be a subset of the card's languages_supported
                (defaulting to [default_language]) or the server returns 422
                parameter_invalid naming the offending key and the allowed set.
                SERVE: each customer sees the variant in their language, else
                'sk', else 'en', else the first one. Must match the pass strip
                aspect ratio (~2.61:1, i.e. 527:202) within 5% tolerance and be
                at least 527px wide (every language variant is checked); the
                server returns 422 image_aspect_ratio_invalid otherwise. Image
                rules per variant: https only, PNG only (magic-byte verified), ≤
                3MB, width between 527px and 4000px - 422 image_format_invalid /
                image_too_large / image_resolution_too_small /
                image_resolution_too_large on violation. VEXiON stores a copy of
                the image. Reads return the VEXiON copy URL, not the URL you
                sent. A URL that VEXiON returned earlier (for example one you
                got back from this API, or a library icon) is kept as is. Each
                language variant is mirrored to its OWN object, independently.
              anyOf:
                - type: string
                  format: uri
                  title: Single URL (all languages)
                  example: >-
                    https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                - type: object
                  title: Per-language URLs
                  additionalProperties:
                    type: string
                    format: uri
                  minProperties: 1
                  example:
                    sk: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                    en: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                  properties: {}
            pattern_type:
              enum:
                - brick
                - radial
                - diagonal
                - scattered
                - null
              description: >-
                Background pattern layout for NON-STAMP cards in STANDARD
                design_mode (Reward / Discount / Coupon / Membership / GiftCard
                / Custom). Options: 'brick' (grid), 'radial' (circular),
                'diagonal' (stripes), 'scattered' (random). Combined with
                pattern_icon_urls to render the card background. Ignored on
                STAMP cards (which use stamp_icon_url from mechanics instead).
                REQUIRED (non-null) for a STANDARD, non-stamp card: the server
                has no other content source for the pass background and returns
                422 missing_required_field on design.pattern_type when it is
                absent for that combination.
              example: brick
              type:
                - string
                - 'null'
            pattern_icon_urls:
              type: array
              maxItems: 3
              description: >-
                NON-STAMP cards in STANDARD design_mode only. 1-3 icon URLs
                tiled in the chosen pattern_type. Pick from GET /v1/icons
                (library icons are trusted pre-vetted squares) or supply a
                custom HTTPS URL; a non-library URL MUST be a square image
                (width equal to height, within 2% tolerance) or the server
                returns 422 image_not_square. REQUIRED (at least 1 entry) for a
                STANDARD, non-stamp card: the server returns 422
                missing_required_field on design.pattern_icon_urls when
                empty/absent for that combination. Ignored on STAMP cards. Full
                asset contract for a non-library URL: https only, PNG only
                (magic-byte verified), ≤ 3MB, square (±2% tolerance) between
                88x88 and 2000x2000px - 422 image_format_invalid /
                image_too_large / image_resolution_too_small /
                image_resolution_too_large on violation, in addition to
                image_not_square above. VEXiON stores a copy of the image. Reads
                return the VEXiON copy URL, not the URL you sent. A URL that
                VEXiON returned earlier (for example one you got back from this
                API, or a library icon) is kept as is. Each entry is mirrored to
                its OWN object, independently.
              items:
                type: string
                format: uri
              example:
                - https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
            show_logo_at_issuing_form:
              type: boolean
              default: true
              description: >-
                When true, the logo is rendered at the top of the public signup
                form at /signup/{card_id}. 
            show_background_color_at_issuing_form:
              type: boolean
              default: true
              description: >-
                When true, bg_color is used as the signup form background. False
                = white form regardless of pass color. 
        pass_fields:
          $ref: '#/components/schemas/PassFields'
        form_builder:
          type: object
          description: >-
            Configuration for the public customer signup form at
            /signup/{card_id}. Lists which fields to collect, in what order, and
            which are required.
          properties:
            enabled:
              type: boolean
              default: true
              description: >-
                When false, the public signup URL is fully disabled - customers
                can only be enrolled via API or dashboard.
            default_language:
              type: string
              enum:
                - sk
                - cs
                - en
                - de
              description: Default language the form renders in (customer can switch).
            fields:
              type: array
              description: >-
                Ordered list of fields shown on the signup form. Mix of system
                fields (email, phone, etc.) and references to org-level
                custom_fields.
              items:
                type: object
                required:
                  - id
                  - type
                  - order
                properties:
                  id:
                    type: string
                    description: >-
                      Stable identifier (matches the key used in customer.custom
                      or top-level system field).
                  type:
                    type: string
                    enum:
                      - system
                      - custom
                    description: >-
                      Source of the field. 'system' = built-in customer
                      attribute (email, first_name, etc.). 'custom' = references
                      an org-level CustomField.
                  system_key:
                    enum:
                      - email
                      - first_name
                      - last_name
                      - phone
                      - date_of_birth
                      - consent
                      - preferred_language
                      - null
                    description: >-
                      SYSTEM TYPE ONLY. Which built-in customer attribute this
                      field collects.
                    type:
                      - string
                      - 'null'
                  custom_field_id:
                    pattern: ^cf_[A-Za-z0-9]+$
                    description: >-
                      CUSTOM TYPE ONLY. Reference to an org-level CustomField
                      definition.
                    type:
                      - string
                      - 'null'
                  required:
                    type: boolean
                    default: false
                    description: >-
                      Whether the customer must fill this field to submit the
                      form (and whether POST /v1/customers requires it). On the
                      API, enforcement covers the five system fields the
                      customer request carries: `email`, `first_name`,
                      `last_name`, `phone`, `date_of_birth`. The other two
                      system keys are collected by the public signup page ONLY
                      and are never required through the API: the customer
                      request declares no `consent` property, and `language`
                      always resolves to the card default_language, so neither
                      can ever be absent. Marking `consent` or
                      `preferred_language` required therefore changes nothing
                      about POST /v1/customers, and an API-created customer
                      records no marketing consent from a form consent checkbox.
                      Send `marketing_opt_in` to record consent deliberately.
                  order:
                    type: integer
                    minimum: 0
                    description: Display position on the form (0-indexed).
                  translations_override:
                    type: object
                    description: >-
                      Per-language label overrides for this field, keyed by
                      language code: `{ "sk": { "label": "Meno" }, "en": {
                      "label": "Name" } }`. Falls back to the built-in preset
                      for a system field.
                    additionalProperties:
                      type: object
                      properties:
                        label:
                          type: string
        mechanics:
          description: >-
            Type-specific mechanics block. REQUIRED but must match Card.type.
            Pick the matching tab. Custom = pick 'Custom (null)'.
          anyOf:
            - $ref: '#/components/schemas/CardMechanics_Stamp'
            - $ref: '#/components/schemas/CardMechanics_Reward'
            - $ref: '#/components/schemas/CardMechanics_Discount'
            - $ref: '#/components/schemas/CardMechanics_Coupon'
            - $ref: '#/components/schemas/CardMechanics_Membership'
            - $ref: '#/components/schemas/CardMechanics_GiftCard'
            - title: Custom (null - no built-in mechanics)
              type: 'null'
              description: >-
                For Card.type='custom'. Notifications reach custom cards like
                any other type.
        issue_limit:
          minimum: 0
          description: >-
            Maximum number of customers that can be enrolled in this card. Null
            = unlimited. When reached, POST /v1/customers returns 403
            card_limit_reached. 0 and null both mean unlimited; both read back
            as null.
          example: 1000
          type:
            - integer
            - 'null'
        issued_count:
          type: integer
          minimum: 0
          readOnly: true
          description: >-
            Current number of customers enrolled in this card (read-only).
            Updated on every successful POST /v1/customers.
          example: 234
        referral:
          description: >-
            Referral program configuration. When enabled, customers can refer
            friends via a unique referral URL; both parties earn a bonus when
            the referee enrolls and completes a qualifying action.
          properties:
            enabled:
              type: boolean
              default: false
              description: When true, referral URLs are generated for every customer.
            referrer_bonus:
              type: integer
              minimum: 0
              default: 0
              description: >-
                Bonus awarded to the referrer when a referee converts. On a
                cashback card (`redemption_mode: "cashback"`) the value is still
                points; the dashboard shows and edits it as credit in the
                business currency: credit = points x `cashback_point_value` (200
                points at 0.01 = EUR 2.00). The real limit depends on the card:
                stamps up to 1,000; points up to 100,000; cashback about EUR 50
                of credit in your currency (EUR 50, CZK 1,250, HUF 20,000, PLN
                200, RON 250, GBP 50, CHF 50, USD 50), so the most points are
                that amount divided by `cashback_point_value`; discount about
                EUR 1,000 of spend (CZK 25,000, HUF 400,000, PLN 4,000, RON
                5,000, GBP 1,000, CHF 1,000, USD 1,000). Whole numbers only. On
                a discount card the bonus is spend in whole currency units (not
                minor units) that raises the level. Above the limit the request
                answers 422 `validation_failed` with `field_errors[].code`
                `out_of_range` on `referral.referrer_bonus` or
                `referral.referee_bonus`. On PATCH only a changed value is
                checked, plus the stored value again when the change makes a
                point worth more.
            referee_bonus:
              type: integer
              minimum: 0
              default: 0
              description: >-
                Bonus awarded to the referee on enrollment. On a cashback card
                (`redemption_mode: "cashback"`) the value is still points; the
                dashboard shows and edits it as credit in the business currency:
                credit = points x `cashback_point_value` (200 points at 0.01 =
                EUR 2.00). The real limit depends on the card: stamps up to
                1,000; points up to 100,000; cashback about EUR 50 of credit in
                your currency (EUR 50, CZK 1,250, HUF 20,000, PLN 200, RON 250,
                GBP 50, CHF 50, USD 50), so the most points are that amount
                divided by `cashback_point_value`; discount about EUR 1,000 of
                spend (CZK 25,000, HUF 400,000, PLN 4,000, RON 5,000, GBP 1,000,
                CHF 1,000, USD 1,000). Whole numbers only. On a discount card
                the bonus is spend in whole currency units (not minor units)
                that raises the level. Above the limit the request answers 422
                `validation_failed` with `field_errors[].code` `out_of_range` on
                `referral.referrer_bonus` or `referral.referee_bonus`. On PATCH
                only a changed value is checked, plus the stored value again
                when the change makes a point worth more.
          example:
            enabled: true
            referrer_bonus: 50
            referee_bonus: 25
          type:
            - object
            - 'null'
        issuer:
          type: object
          description: >-
            REQUIRED. Issuer details. company_name is REQUIRED because it maps
            to Apple PKPass.organizationName (shown on lock screen) and Google
            LoyaltyClass.issuerName (shown in pass list). Without it, Apple
            shows 'Unknown Issuer' and Google rejects the pass during review.
          required:
            - company_name
          properties:
            company_name:
              type: string
              minLength: 1
              maxLength: 100
              description: >-
                REQUIRED. Business legal name. Maps to Apple
                PKPass.organizationName (lock screen) and Google
                LoyaltyClass.issuerName (max 20 chars displayed on small
                screens). Use the short customer-facing name (e.g., 'Demo
                Bakery', not 'Pekáreň Vlna s.r.o.').
              example: Demo Bakery
            email:
              format: email
              description: >-
                Issuer support email shown on pass back side (Apple) and contact
                details (Google). Optional but strongly recommended for customer
                support.
              example: support@demobakery.sk
              maxLength: 255
              type:
                - string
                - 'null'
            phone:
              description: >-
                Issuer support phone shown on pass back side. E.164 format
                preferred.
              example: '+421905123456'
              pattern: ^\+[1-9]\d{1,14}$
              minLength: 8
              maxLength: 16
              type:
                - string
                - 'null'
        terms_of_use:
          description: >-
            Optional terms and conditions shown on the wallet pass back side.
            `text` supports per-language (string OR object {sk, cs, en, de}) -
            the pass renders the customer's own language variant when present,
            falling back to the card's default-language text otherwise.
            `full_url` is a single URL only (no per-language variant).
          properties:
            enabled:
              type: boolean
              default: false
              description: Toggle to show/hide T&C on the pass.
            text:
              description: >-
                Inline T&C text (max 2000 chars per language). Single string OR
                per-language object. The card's default_language variant is
                always what the dashboard editor shows and edits; other-language
                variants render on the pass only for a customer whose preferred
                language matches them, falling back to the default-language text
                otherwise.
              anyOf:
                - type: string
                  title: Single text
                  maxLength: 2000
                  example: Terms apply...
                - type: object
                  title: Per-language
                  additionalProperties:
                    type: string
                    maxLength: 2000
                  example:
                    sk: Podmienky platia...
                    en: Terms apply...
                  properties: {}
            full_url:
              type: string
              format: uri
              description: >-
                Your loyalty programme rules, as one https URL. The signup page
                links them next to VEXiON's card terms (they supplement them,
                never replace them); the dashboard lists this link as the
                programme rules. Present only while the signup page shows the
                link; absent otherwise. Single URL, no per-language variant.
                When the link is shown, the signup checkbox reads that the
                member accepts VEXiON's card terms, the privacy policy and your
                loyalty programme rules, and the link is stored with the
                member's consent record. If your rules conflict with VEXiON's
                card terms, the card terms prevail. You are responsible for the
                content, lawfulness and availability of your rules; VEXiON does
                not review them.
          type:
            - object
            - 'null'
        registration_url:
          format: uri
          description: >-
            Public signup URL where customers self-enroll. Pattern:
            https://vexioncards.one/signup/?id={card_id}. Share via QR code,
            flyer, or email. Append ?loc={location_id} for per-location
            attribution (signup_location_id on the resulting customer). For
            per-location URLs of every location with this card enabled, call GET
            /v1/locations and check `enabled_card_ids` for this card's id (or
            read each location's `cards[]` (GET /v1/locations) - every entry
            carries its location-scoped `signup_url`). We previously had
            registration_urls_per_location on this response, but it duplicated
            data that lives on Location and forced an extra refresh whenever a
            location toggled the card on/off.
          example: https://vexioncards.one/signup/?id=crd_sophcrprtvpmmnwxah2lomnkee
          type:
            - string
            - 'null'
        metadata:
          type: object
          description: >-
            Free-form key-value map for partner-internal IDs and integration
            markers. NOT rendered on the wallet pass. Shape IS enforced (422
            `validation_failed`): max 50 keys, key must match
            `^[a-zA-Z0-9_]{1,40}$`, values must be strings of at most 500 chars.
            The platform never interprets the contents.


            Persisted on the card template and returned on reads. Omitted
            entirely when empty rather than sent as null. On PATCH the map is
            merged per key, so sending one key never clears the others.
            (Distinct from `Customer.metadata`, which is built and live.)
          additionalProperties:
            type: string
            maxLength: 500
          maxProperties: 50
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,40}$
          example:
            internal_campaign_id: Q3_2026_coffee_loyalty
            owner_team: marketing
          properties: {}
        status:
          type: string
          enum:
            - draft
            - active
            - paused
            - archived
          description: >-
            Template lifecycle. `draft` (created but never launched - not
            issuable, its public signup page turns customers away), `active`
            (issuable), `paused` (temporarily closed to NEW signups; existing
            passes keep working and keep earning), `archived` (retired -
            existing customer passes keep working, no new signups). A card
            created through this API is born `active`. DELETE archives, never
            destroys, and historical references stay resolvable forever.
          example: active
          default: active
        image_generation:
          type: object
          description: >-
            Server-side pass-image generation state. Standard-design cards get
            their wallet pass images (the per-stamp-state strip images, or the
            single pattern strip) rendered automatically after create and after
            design edits. The card is returned immediately and images render in
            the background, typically within seconds. processing: rendering in
            progress - the pass already works and images appear as soon as they
            are ready. completed: all images are live. failed: rendering hit an
            error and is retried automatically; the card itself is unaffected.
            not_applicable: individual-design cards (you supply the images) and
            cards with no generated imagery.
          properties:
            status:
              type: string
              enum:
                - not_applicable
                - processing
                - completed
                - failed
              description: Generation state.
              example: completed
            updated_at:
              description: >-
                Unix timestamp of the last generation state change. Null when
                not_applicable.
              example: 1782345600
              type:
                - integer
                - 'null'
            error:
              type:
                - string
                - 'null'
              description: >-
                Failure reason when status is failed (for example an unreachable
                custom image URL); null on every other status. Use it to
                self-diagnose and re-trigger generation with a corrected design
                PATCH.
          required:
            - status
        created_at:
          type: integer
          description: Unix timestamp when the card was created.
          example: 1782345600
        updated_at:
          type: integer
          description: Unix timestamp when any field on the card was last modified.
          example: 1782604800
        activated_at:
          type:
            - integer
            - 'null'
          description: >-
            Unix seconds when the card FIRST went live. Stamped on the first
            transition to `active` and never overwritten, so it survives
            pause/reactivate and un-archive cycles. Null for a card that has
            never been activated (a `draft`).
        paused_at:
          type:
            - integer
            - 'null'
          description: >-
            Unix seconds of the most recent transition to `paused`. Restamped on
            every pause. Null for a card that has never been paused. Not cleared
            when the card reactivates, so it records the last pause, not the
            current state - read `status` for that.
        archived_at:
          description: >-
            Unix timestamp when the card was archived (status='archived'). Null
            if active.
          example: null
          type:
            - integer
            - 'null'
        links:
          type: array
          description: >-
            Card-attached links shown on the wallet pass back side - business
            links (website/social) and a review-prompt link. Max 10 items.
          maxItems: 10
          items:
            type: object
            required:
              - type
              - value
            properties:
              type:
                type: string
                enum:
                  - website
                  - instagram
                  - facebook
                  - google_review
                description: >-
                  Link type. 'website' = company website, 'instagram'/'facebook'
                  = social profiles, 'google_review' = leave-a-review prompt for
                  Google Business.
              value:
                type: string
                description: The link value - always a full URL.
                minLength: 1
                maxLength: 500
          example:
            - type: website
              value: https://democoffee.sk
            - type: instagram
              value: https://instagram.com/democoffee
            - type: google_review
              value: https://g.page/r/abc123/review
        currency:
          $ref: '#/components/schemas/Currency'
          description: >-
            The business currency of this card. A business has one currency, so
            it is the same on every card. Set from the country of the first
            location and locked after the first transaction, customer balance or
            gift card purchase. See [Money and
            currency](/api-reference/money-and-currency).
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
          properties:
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - rate_limit_error
                - idempotency_error
                - api_error
            code:
              type: string
            message:
              type: string
            retry_after:
              type: integer
              description: >-
                Seconds to wait before retrying. Present on rate-limit (429) and
                idempotency-key-in-use (409) errors; mirrors the `Retry-After`
                HTTP header. Absent on error types that carry no retry guidance.
              example: 5
            param:
              type:
                - string
                - 'null'
            request_id:
              type: string
            doc_url:
              type: string
              format: uri
            field_errors:
              type: array
              description: >-
                Per-field errors on a 422. At most 100 entries: a request with
                more errors gets the first 100.
              items:
                type: object
                properties:
                  param:
                    type: string
                  code:
                    type: string
                  message:
                    type: string
                  expected_currency:
                    type: string
                    description: >-
                      On `currency_mismatch`: the business currency the request
                      must use. Absent on every other field error.
          description: >-
            The error payload: type (category), code (machine-readable), message
            (human-readable), retry_after (seconds, on
            rate-limit/idempotency-in-use errors only), param (offending field),
            request_id, doc_url, field_errors[].
      description: >-
        Standard error envelope. Branch on `error.code`; include
        `error.request_id` in support requests. See the Errors guide.
    CardDesign:
      type: object
      description: >-
        Visual design of the wallet pass. Required: design_mode, bg_color,
        text_color, icon_url (logo_url is optional; the pass falls back to text
        when omitted).


        VISUAL LAYOUT by mode + type:
          - design_mode=standard + stamp: mechanics.stamp_icon_url + (optional custom_background_image_url)
          - design_mode=individual + stamp: mechanics.stamp_count_images (N+1 per-position images; image_url accepts a plain URL or an OPTIONAL per-language map - every position must share the same language set; each customer sees the variant in their language, else sk, else en, else the first one)
          - design_mode=standard + non-stamp: pattern_type + pattern_icon_urls + (optional custom_background_image_url)
            Server AUTO-GENERATES the final pattern image per supported language: pass a per-language object as card description and each language gets its own image; single-language cards (or a plain-string description) get one image. Each customer is served the image matching the language they picked at signup (fallback sk, then en, then the first one). No image URLs are needed for the standard pattern.
          - design_mode=individual + non-stamp: individual_background_image_url REQUIRED (accepts a plain URL or an OPTIONAL per-language map; each customer sees the variant in their language, else sk, else en, else the first one)

        TIER OVERRIDES: tiered reward/discount cards use
        mechanics.tiers[i].background_image_urls (per-language) which overrides
        any card-level background for customers at that tier.
      required:
        - design_mode
        - bg_color
        - text_color
        - icon_url
      properties:
        design_mode:
          type: string
          enum:
            - standard
            - individual
          description: >-
            Design approach: 'standard' uses icons from our library (10,000+ 3D
            icons across categories). 'individual' uses fully custom uploaded
            images. For STAMP: standard = single repeating icon, individual =
            per-stamp position images. For NON-STAMP: standard = pattern + up to
            3 library icons, individual = full custom background.
          example: standard
        bg_color:
          type: string
          pattern: ^#[0-9A-Fa-f]{6}$
          description: >-
            REQUIRED. Pass background color in #RRGGBB hex. Maps to Apple
            PKPass.backgroundColor + Google LoyaltyClass.hexBackgroundColor.
          example: '#1F4E79'
        text_color:
          type: string
          pattern: ^#[0-9A-Fa-f]{6}$
          description: >-
            REQUIRED. Foreground text color in #RRGGBB hex. Maps to Apple
            PKPass.foregroundColor. Must contrast bg_color for accessibility.
          example: '#FFFFFF'
        logo_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Optional wide header logo (recommended about 480x150 px, roughly
            3.2:1; Apple renders it in a 160x50 pt box, Google as
            wideProgramLogo). When omitted or null the wallet pass falls back to
            TEXT: Apple shows the card name as logoText, Google shows
            programName next to the small icon_url logo. Image rules: https
            only, PNG only (magic-byte verified), ≤ 3MB, 3.2:1 aspect ratio
            (480x150, within 5% tolerance), minimum 240x75px (no ceiling). A
            failing URL returns 422 (invalid_image_url / image_format_invalid /
            image_too_large / image_aspect_ratio_invalid /
            image_resolution_too_small). VEXiON stores a copy of the image.
            Reads return the VEXiON copy URL, not the URL you sent. A URL that
            VEXiON returned earlier (for example one you got back from this API,
            or a library icon) is kept as is.
          example: >-
            https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/logo-480x150.png?v=2026-09-17
        icon_url:
          type: string
          format: uri
          description: >-
            REQUIRED. Small square icon shown in Wallet push notifications and
            list view (Apple PKPass icon.png / Google smallIconUrl) - the ONLY
            strictly-required image in the Apple PKPass spec; without it
            notifications fail to display. Image rules: https only, PNG only
            (magic-byte verified, never Content-Type), ≤ 3MB real streamed
            bytes, square (width equal to height, within 2% tolerance) between
            88x88 and 2000x2000px. A non-library URL failing any of these
            returns 422 (invalid_image_url / image_format_invalid /
            image_too_large / image_not_square / image_resolution_too_small /
            image_resolution_too_large). A GET /v1/icons library icon is trusted
            pre-vetted square and only checked for https reachability. VEXiON
            stores a copy of the image. Reads return the VEXiON copy URL, not
            the URL you sent. A URL that VEXiON returned earlier (for example
            one you got back from this API, or a library icon) is kept as is.
          example: >-
            https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/icon-512x512.png?v=2026-09-17
        custom_background_image_url:
          format: uri
          description: >-
            STANDARD design_mode only (any card type). Optional photo BACKDROP
            rendered BEHIND the auto-generated content (stamp icons for stamp
            cards, pattern + icons for non-stamp cards). Adds branded
            photography to the standard layout. Ignored when
            design_mode='individual' (use individual_background_image_url or
            mechanics.stamp_count_images instead). Must match the pass strip
            aspect ratio (~2.61:1, i.e. 527:202) within 5% tolerance and be at
            least 527px wide; the server returns 422 image_aspect_ratio_invalid
            otherwise. Image rules: https only, PNG only (magic-byte verified),
            ≤ 3MB, width between 527px and 4000px - 422 image_format_invalid /
            image_too_large / image_resolution_too_small /
            image_resolution_too_large on violation, in addition to
            image_aspect_ratio_invalid above. VEXiON stores a copy of the image.
            Reads return the VEXiON copy URL, not the URL you sent. A URL that
            VEXiON returned earlier (for example one you got back from this API,
            or a library icon) is kept as is.
          example: null
          type:
            - string
            - 'null'
        individual_background_image_url:
          description: >-
            Image URL: either a single URL string (used for all languages) or an
            object mapping language codes (sk, cs, en, de) to URLs for
            per-language variants (use when text is baked into the image).
            OPTIONAL per-language: a single URL is valid and serves every
            customer via fallback. WRITE: a map stores every variant; on PATCH a
            plain string (or null) clears any previously stored map. Map keys
            must be a subset of the card's languages_supported (defaulting to
            [default_language]) or the server returns 422 parameter_invalid
            naming the offending key and the allowed set. SERVE: each customer
            sees the variant in their language, else 'sk', else 'en', else the
            first one. Must match the pass strip aspect ratio (~2.61:1, i.e.
            527:202) within 5% tolerance and be at least 527px wide (every
            language variant is checked); the server returns 422
            image_aspect_ratio_invalid otherwise. Image rules per variant: https
            only, PNG only (magic-byte verified), ≤ 3MB, width between 527px and
            4000px - 422 image_format_invalid / image_too_large /
            image_resolution_too_small / image_resolution_too_large on
            violation. VEXiON stores a copy of the image. Reads return the
            VEXiON copy URL, not the URL you sent. A URL that VEXiON returned
            earlier (for example one you got back from this API, or a library
            icon) is kept as is. Each language variant is mirrored to its OWN
            object, independently.
          anyOf:
            - type: string
              format: uri
              title: Single URL (all languages)
              example: >-
                https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
            - type: object
              title: Per-language URLs
              additionalProperties:
                type: string
                format: uri
              minProperties: 1
              example:
                sk: >-
                  https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                en: >-
                  https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
              properties: {}
        pattern_type:
          enum:
            - brick
            - radial
            - diagonal
            - scattered
            - null
          description: >-
            REQUIRED for a standard-design card of any NON-STAMP type (reward /
            discount / coupon / custom): the auto-generated pattern needs it.
            Omitting it returns 422 missing_required_field. Stamp cards draw
            real stamp slots instead, so they do not need it. Background pattern
            layout for NON-STAMP cards in STANDARD design_mode (Reward /
            Discount / Coupon / Membership / GiftCard / Custom). Options:
            'brick' (grid), 'radial' (circular), 'diagonal' (stripes),
            'scattered' (random). Combined with pattern_icon_urls to render the
            card background. Ignored on STAMP cards (which use stamp_icon_url
            from mechanics instead). REQUIRED (non-null) for a STANDARD,
            non-stamp card: the server has no other content source for the pass
            background and returns 422 missing_required_field on
            design.pattern_type when it is absent for that combination.
          example: brick
          type:
            - string
            - 'null'
        pattern_icon_urls:
          type: array
          maxItems: 3
          description: >-
            NON-STAMP cards in STANDARD design_mode only. 1-3 icon URLs tiled in
            the chosen pattern_type. Pick from GET /v1/icons (library icons are
            trusted pre-vetted squares) or supply a custom HTTPS URL; a
            non-library URL MUST be a square image (width equal to height,
            within 2% tolerance) or the server returns 422 image_not_square.
            REQUIRED (at least 1 entry) for a STANDARD, non-stamp card: the
            server returns 422 missing_required_field on
            design.pattern_icon_urls when empty/absent for that combination.
            Ignored on STAMP cards. Full asset contract for a non-library URL:
            https only, PNG only (magic-byte verified), ≤ 3MB, square (±2%
            tolerance) between 88x88 and 2000x2000px - 422 image_format_invalid
            / image_too_large / image_resolution_too_small /
            image_resolution_too_large on violation, in addition to
            image_not_square above. VEXiON stores a copy of the image. Reads
            return the VEXiON copy URL, not the URL you sent. A URL that VEXiON
            returned earlier (for example one you got back from this API, or a
            library icon) is kept as is. Each entry is mirrored to its OWN
            object, independently.
          items:
            type: string
            format: uri
          example:
            - https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
        show_logo_at_issuing_form:
          type: boolean
          default: true
          description: >-
            When true, the logo is rendered at the top of the public signup form
            at /signup/{card_id}. 
        show_background_color_at_issuing_form:
          type: boolean
          default: true
          description: >-
            When true, bg_color is used as the signup form background. False =
            white form regardless of pass color. 
        strip_color:
          type: string
          pattern: ^#[0-9A-Fa-f]{6}$
          description: >-
            Strip / accent color in #RRGGBB hex - the panel behind the stamps
            (stamp cards) or behind the auto-generated pattern (non-stamp
            cards). Required on write when design_mode is standard. Not used for
            individual designs, where the uploaded background image covers this
            area.
          example: '#36C6FA'
      allOf:
        - if:
            properties:
              design_mode:
                const: standard
            required:
              - design_mode
          then:
            required:
              - strip_color
    PassFields:
      type: object
      description: >-
        The wallet pass layout - **three front slots + back rows**, identical
        model on Apple and Google (the same shape the platform's partner
        integrations have used in production):


        | slot | Apple | Google |

        |---|---|---|

        | `header` | `headerFields[0]` (top-right) | top text module |

        | `front_left` / `front_right` | `secondaryFields` row |
        `textModulesData` front row |

        | `back[]` | `backFields` (all rows) | text modules (**first 10 shown**
        - Google's per-object cap; remaining rows are Apple-only) |


        The PRIMARY front area (the big number - stamps, points, balance) is
        **owned by the card's mechanics** and never configurable here. Typed
        values (`{{custom.<key>}}` of type date/number/currency/boolean) render
        locale-formatted on both platforms automatically.


        **PATCH replaces this object as a WHOLE, it does not merge per slot.**
        Any slot you omit is REMOVED from the card. Sending `{"pass_fields":
        {"header": {...}}}` on a card that also had `front_left`, `front_right`,
        `back` or `barcode_alt_text` clears those. To change one slot, send the
        full `pass_fields` object including every slot you want to keep, or read
        the card first and send it back with your edit applied. This differs
        from `metadata` and `custom` on other resources, which ARE JSON Merge
        Patch (a per-key merge where `null` deletes one key).
      properties:
        header:
          anyOf:
            - $ref: '#/components/schemas/PassFieldSlot'
            - type: 'null'
              title: Empty slot
          description: Top-of-pass slot, next to the logo. Null = empty.
        front_left:
          anyOf:
            - $ref: '#/components/schemas/PassFieldSlot'
            - type: 'null'
              title: Empty slot
          description: Left front slot under the primary area. Null = empty.
        front_right:
          anyOf:
            - $ref: '#/components/schemas/PassFieldSlot'
            - type: 'null'
              title: Empty slot
          description: Right front slot under the primary area. Null = empty.
        back:
          type: array
          items:
            $ref: '#/components/schemas/PassBackField'
          maxItems: 20
          description: >-
            Back-of-pass rows in display order. Max 20 (Google shows the first
            10 - order accordingly).
        barcode_alt_text:
          type:
            - string
            - 'null'
          maxLength: 100
          description: >-
            Template rendered per-customer into the text shown directly BELOW
            the barcode/QR on the pass (Apple `barcode.altText`). Single
            language, short - Apple truncates hard. e.g. "Member
            {{custom.member_id}}". Null or omitted = no alt text, consistent
            with header/front_left/front_right, which all document 'Null =
            empty'. Previously this one slot rejected null while its three
            siblings accepted it, so a partner clearing every slot at once got a
            422 on this field alone.
      example:
        header:
          label:
            en: Member
            sk: Člen
          value_template: '{{custom.membership_no}}'
          change_message: null
        front_left:
          label:
            en: Department
            sk: Oddelenie
          value_template: '{{custom.department}}'
          change_message: null
        front_right:
          label:
            en: Valid until
            sk: Platné do
          value_template: '{{custom.valid_until}}'
          change_message: Validity extended to {{value}}
        back:
          - key: opening_hours
            label:
              en: Opening hours
              sk: Otváracie hodiny
            value_template: Mon-Fri 8:00-18:00
            change_message: null
          - key: support
            label:
              en: Support
              sk: Podpora
            value_template: +421 900 000 000
            change_message: null
    CardMechanics_Stamp:
      type: object
      description: >-
        Mechanics configuration for STAMP cards ('buy 10 get 1 free' style).
        Includes welcome bonus, prepaid subscription mode, per-stamp image
        variants. Note: visits are always tracked automatically; no opt-in
        needed.
      required:
        - type
        - stamps_required
      properties:
        type:
          type: string
          const: stamp
          description: Discriminator. Always 'stamp' for this mechanics block.
        stamps_required:
          type: integer
          minimum: 1
          maximum: 20
          default: 10
          description: Total stamps the customer must collect to earn the reward.
          example: 10
        is_prepaid:
          type: boolean
          default: false
          description: >-
            Prepaid subscription mode. When true, the customer pays upfront for
            a package of stamps that staff load via the scanner 'Charge card'
            action, and each use deducts one stamp. Welcome bonus
            (starting_stamps) is ignored in this mode (always starts at 0).
            Cannot be changed after creation: PATCH with the other value returns
            400 field_immutable with param mechanics.is_prepaid.
          example: false
        starting_stamps:
          type: integer
          minimum: 0
          default: 0
          description: >-
            Welcome bonus stamps the customer receives automatically on
            enrollment (when not in prepaid mode). Per-customer override
            available via initial_state.stamps_collected on POST /v1/customers.
            Limit (VEXiON-231 round 13): at most the card's `stamps_required`,
            and never above 1,000. Above the limit the request answers 422
            `validation_failed` with `field_errors[].code` `out_of_range` on
            `mechanics.starting_stamps`. On PATCH only a changed value is
            checked, plus the stored value again when the change lowers
            `stamps_required`.
          example: 1
          maximum: 1000
        reward_at_completion:
          maxLength: 500
          description: >-
            Description of what the customer gets when they fill the card (e.g.,
            'Free coffee', 'Free haircut'). Shown on the pass and in
            notifications. When the customer redeems, the system deducts
            stamps_required from their current count (if they have 12 stamps on
            a 10-stamp card, redemption deducts 10, leaving 2).
          example: Free coffee of your choice
          type:
            - string
            - 'null'
        stamp_count_images:
          description: >-
            STAMP cards in INDIVIDUAL design_mode only. Per-position custom
            images for each stamp slot. Length MUST equal stamps_required + 1
            (enforced; a mismatched set would leave stamp positions without
            artwork) (last image is shown when the card is full / reward
            earned). Customer-uploaded full-card images for fully bespoke
            designs.
          maxItems: 51
          items:
            type: object
            required:
              - stamp_number
              - image_url
            properties:
              stamp_number:
                type: integer
                minimum: 0
                description: >-
                  Position (0 = before any stamps, 1 = first stamp filled, ...,
                  N = full card / reward).
              image_url:
                description: >-
                  Image URL: either a single URL string (used for all languages)
                  or an object mapping language codes (sk, cs, en, de) to URLs
                  for per-language variants (use when text is baked into the
                  image). OPTIONAL per position: a single URL is valid and
                  serves every customer via fallback. WRITE: a map stores every
                  variant. Map keys must be a subset of the card's
                  languages_supported (defaulting to [default_language]) or the
                  server returns 422 parameter_invalid naming the offending key
                  and the allowed set. UNIFORM COVERAGE: every position in
                  stamp_count_images must carry the SAME language set - a
                  per-language set is complete or absent across ALL positions;
                  mixing a plain string with a map, or maps with different keys,
                  returns 422 stamp_count_images_language_mismatch naming the
                  first mismatching position. SERVE: each customer sees the
                  variant in their language, else 'sk', else 'en', else the
                  first one. Must match the pass strip aspect ratio (~2.61:1,
                  i.e. 527:202) within 5% tolerance and be at least 527px wide
                  (every language variant is checked); the server returns 422
                  image_aspect_ratio_invalid otherwise. Image rules per variant:
                  https only, PNG only (magic-byte verified), ≤ 3MB, width
                  between 527px and 4000px - 422 image_format_invalid /
                  image_too_large / image_resolution_too_small /
                  image_resolution_too_large on violation. VEXiON stores a copy
                  of the image. Reads return the VEXiON copy URL, not the URL
                  you sent. A URL that VEXiON returned earlier (for example one
                  you got back from this API, or a library icon) is kept as is.
                  Each position and each language variant is mirrored to its OWN
                  object, keyed by stamp count (not array position),
                  independently.
                anyOf:
                  - type: string
                    format: uri
                    title: Single URL (all languages)
                    example: >-
                      https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                  - type: object
                    title: Per-language URLs
                    additionalProperties:
                      type: string
                      format: uri
                    minProperties: 1
                    example:
                      sk: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                      en: >-
                        https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
                    properties: {}
          example: null
          type:
            - array
            - 'null'
        stamp_icon_url:
          format: uri
          description: >-
            STAMP cards in STANDARD design_mode only. Single icon URL used for
            all stamp positions on the wallet pass (the final position is
            automatically replaced with a gift icon for the reward). Pick from
            our library via GET /v1/icons or upload your own URL. Tightly
            coupled with this mechanics block because the icon is repeated
            stamps_required times. A non-library URL MUST be a square image
            (width equal to height, within 2% tolerance); the server returns 422
            image_not_square otherwise. A GET /v1/icons library icon is trusted
            pre-vetted square. Full asset contract for a non-library URL: https
            only, PNG only (magic-byte verified), ≤ 3MB, square (±2% tolerance)
            between 88x88 and 2000x2000px - 422 image_format_invalid /
            image_too_large / image_resolution_too_small /
            image_resolution_too_large on violation, in addition to
            image_not_square above. VEXiON stores a copy of the image. Reads
            return the VEXiON copy URL, not the URL you sent. A URL that VEXiON
            returned earlier (for example one you got back from this API, or a
            library icon) is kept as is.
          example: https://stamp-icons.s3.eu-central-1.amazonaws.com/coffee.png
          type:
            - string
            - 'null'
    CardMechanics_Reward:
      type: object
      description: >-
        Reward card mechanics. THREE LOGICAL CONFIGURATIONS:
          1. Flat + Product Rewards: single earn_rate for all, customer redeems for items from rewards[] catalog
          2. Flat + Cashback: single earn_rate for all, customer redeems points as cash discount (cashback_point_value)
          3. Tiered + Cashback: tiers[].earn_multiplier scales the base rate per tier, customer redeems as cashback
          4. (Allowed but rare) Tiered + Product: tiered earn + product catalog redemption

        earn_mode and redemption_mode are INDEPENDENT - any combination is
        valid. tier_demotion only fires when earn_mode='tiered' (no-op for
        flat).
      required:
        - type
        - earn_mode
        - earn_rate_per_unit
        - redemption_mode
      properties:
        type:
          type: string
          const: reward
          description: Discriminator. Always 'reward' for this mechanics block.
        earn_mode:
          type: string
          enum:
            - flat
            - tiered
          default: flat
          description: >-
            How points are earned: 'flat' = single earn_rate_per_unit for all
            customers; 'tiered' = base earn_rate scaled by
            tiers[].earn_multiplier based on the customer's qualifying spend
            (purchases that earned points, minus reversals). INDEPENDENT of
            redemption_mode - any combination works. Levels switch. Send
            earn_mode flat to switch levels off: the levels stay stored but GET
            returns tiers as an empty array and tier_demotion as null. Send
            tiered to switch them on: at least one level is required (422
            levels_need_a_level; a multiplier below 1 or a spend below 0 gives
            422 levels_invalid_level, both on mechanics.tiers). On switch-on
            every holder is recounted from the ledger in the background, so
            holder levels settle shortly after the 200 response, and a running
            level retention restarts its clock. When you edit levels while they
            are on, send the existing id of each level so renamed levels keep
            their holders.
          example: tiered
        earn_rate_per_unit:
          type: number
          minimum: 0
          default: 1
          description: >-
            Points earned per 1 unit of organization's currency spent (e.g., 1.0
            = 1 point per €1). Currency is set at Organization level
            (`default_currency` on GET /v1/organization) - not per card. For
            tiered mode, this is the BASE rate before tier multipliers. Capped
            at 1,000,000 to prevent overflow/absurd point calculations. 0 means
            the card earns no points from spend (on a cashback card it pays
            nothing). Leave the field out to get the default 1; a card with no
            rate set earns 1 point per currency unit. Cashback percent is
            earn_rate_per_unit x cashback_point_value x 100, times the level
            multiplier. The limit is 30 percent on a cashback card without
            levels and 60 percent per level (the level multiplier counts). Above
            the limit the request answers 422 `validation_failed` with
            `field_errors[].code` `out_of_range` on
            `mechanics.earn_rate_per_unit`, `mechanics.cashback_point_value` or
            `mechanics.tiers`, whichever you changed. Only a changed value is
            checked: a card that already pays more keeps saving other fields. On
            a card connected to Oberon or to Papaya with the card basis, the
            rate of the till counts for the limit instead of
            `cashback_point_value`.
          example: 1
          maximum: 1000000
        starting_points:
          type: integer
          minimum: 0
          default: 0
          description: >-
            Welcome bonus points awarded IMMEDIATELY when the customer enrolls
            (before any transaction). Set to 0 for no welcome bonus. The schema
            bound is 100,000,000,000, but the limit below applies. On a cashback
            card (`redemption_mode: "cashback"`) the value is still points; the
            dashboard shows and edits it as credit in the business currency:
            credit = points x `cashback_point_value` (200 points at 0.01 = EUR
            2.00). Limit (VEXiON-231 round 13, same as the referral bonus): a
            points card 100,000 points. A cashback card about EUR 50 of credit
            in the business currency (EUR 50, CZK 1,250, HUF 20,000, PLN 200,
            RON 250, GBP 50, CHF 50, USD 50), so the most points are that amount
            divided by `cashback_point_value` (EUR 50 at 0.01 allows 5,000
            points). Above the limit the request answers 422 `validation_failed`
            with `field_errors[].code` `out_of_range` on
            `mechanics.starting_points`. On PATCH only a changed value is
            checked, plus the stored value again when the change makes a point
            worth more (a higher `cashback_point_value`, or a switch into
            cashback).
          example: 100
          maximum: 100000000000
        redemption_mode:
          type: string
          enum:
            - product
            - cashback
          default: product
          description: >-
            How points are redeemed: 'product' = customer picks an item from
            rewards[] catalog (each item has points_required); 'cashback' =
            customer redeems points as cash discount at POS (1 point =
            cashback_point_value of currency). INDEPENDENT of earn_mode.
          example: cashback
        rewards:
          type: array
          description: >-
            PRODUCT REDEMPTION ONLY. Catalog of redeemable rewards, at most 20
            (more answers 422 `validation_failed` on `mechanics.rewards`). Each
            entry costs points_required and represents a specific product or
            perk.
          items:
            type: object
            required:
              - name
              - points_required
            properties:
              id:
                type: string
                description: >-
                  Stable identifier for this reward. GET always returns the
                  server's own opaque rwd_ id for a persisted reward; an id you
                  invent at create time is accepted but only becomes this
                  identity once you round-trip the real id back on a later
                  PATCH.
                example: rwd_uas4xm344kos2gac5ky77axquy
              points_required:
                type: integer
                minimum: 0
                description: >-
                  Points needed to redeem this reward, a whole number of at
                  least 1. Maximum 100,000,000,000.
                example: 500
                maximum: 100000000000
              value:
                description: >-
                  Fixed amount off in minor currency units, a whole number of at
                  least 1 (450 is EUR 4.50). It reads back for every currency,
                  not only the euro. A reward with neither `value` nor
                  `percentage` is a free product (100 percent off). A
                  percent-off reward carries `percentage` and reads back without
                  `value`. Send `value` or `percentage`, not both (422
                  `validation_failed` on the reward). You can send a reward back
                  unchanged in a PATCH: a reward with its `id` and with neither
                  `value` nor `percentage` keeps the stored reward. Maximum
                  100,000,000,000.
                example: 450
                type:
                  - number
                  - 'null'
                minimum: 0
                maximum: 100000000000
              percentage:
                type:
                  - integer
                  - 'null'
                minimum: 1
                maximum: 99
                description: >-
                  Percent off for a percent-off reward, a whole number from 1 to
                  99 (20 is 20 percent off the bill). Send it instead of
                  `value`. A free product has neither field. Outside 1 to 99
                  answers 422 `validation_failed` with `field_errors[].code`
                  `out_of_range`.
                example: 20
              name:
                type: string
                maxLength: 100
                description: >-
                  Display name of this reward (e.g., 'Free coffee', 'Free
                  t-shirt', '€5 off').
          example:
            - id: rwd_uas4xm344kos2gac5ky77axquy
              points_required: 500
              value: 450
              name: Free coffee
            - id: rwd_z6dnezeod6ee4ingbnd5mxai4m
              points_required: 300
              percentage: 20
              name: 20 percent off
            - id: rwd_3q8vfa7x2k5mnb4dcr6tywh9pe
              points_required: 800
              name: Free pastry
        cashback_point_value:
          default: 0.01
          description: >-
            CASHBACK REDEMPTION ONLY. Currency value of one point, already in
            MAJOR currency units (e.g., 0.01 = 1 point = EUR 0.01 = 100 points =
            EUR 1). This is a rate, not a minor-unit amount - **do not multiply
            by 100** before using it. The server does that conversion exactly
            once, internally, as `points * cashback_point_value * 100`, to
            produce `balance_after.cashback_minor`; multiplying it yourself
            first double-converts and inflates the money figure 100x. Capped at
            1,000,000 to prevent overflow/absurd cashback calculations. On a
            card connected to Dotykacka, Storyous, Papaya or Oberon, the rate of
            the connected till is used for money values and overrides this
            field. Send a value above 0 (0 and negative values answer 422
            `validation_failed`). The percent you pay back is limited, see
            `earn_rate_per_unit`.
          example: 0.01
          type:
            - number
            - 'null'
          maximum: 1000000
          minimum: 0
        tiers:
          type: array
          description: >-
            TIERED EARN MODE ONLY. Reward tier progression. Order from lowest to
            highest threshold (spend_to_achieve_minor, or visits_to_achieve for
            a Booqme/Bookio-linked card). Lowest tier MUST have that threshold =
            0 (everyone starts here). Each tier has an earn_multiplier that
            scales the base earn_rate_per_unit. Customer's tier is auto-computed
            from qualifying spend (purchases that earned points, minus
            reversals; or visit count, for a visit-gated card). Levels switch.
            Send earn_mode flat to switch levels off: the levels stay stored but
            GET returns tiers as an empty array and tier_demotion as null. Send
            tiered to switch them on: at least one level is required (422
            levels_need_a_level; a multiplier below 1 or a spend below 0 gives
            422 levels_invalid_level, both on mechanics.tiers). On switch-on
            every holder is recounted from the ledger in the background, so
            holder levels settle shortly after the 200 response, and a running
            level retention restarts its clock. When you edit levels while they
            are on, send the existing id of each level so renamed levels keep
            their holders. Level names are unique (a repeated name answers 422
            `validation_failed` with code `invalid_value` on `mechanics.tiers`),
            and each level needs a spend above the level before it.
          items:
            $ref: '#/components/schemas/CardTier_Reward'
          example:
            - id: tir_swbxxw6ogmmj2muabpnadygkee
              name: Bronze
              earn_multiplier: 1
              spend_to_achieve_minor: 0
            - id: tir_r4se33bb5tvfzqmm6czx3zltq4
              name: Silver
              earn_multiplier: 1.5
              spend_to_achieve_minor: 50000
            - id: tir_bu23sde3h6xbyb2vjplaehu2va
              name: Gold
              earn_multiplier: 2
              spend_to_achieve_minor: 200000
        tier_demotion:
          description: >-
            ONLY APPLIES when earn_mode='tiered' AND tiers.length >= 2. Level
            retention: a customer who makes no qualifying transaction for
            inactivity_days drops one tier (points are kept). Runs as a daily
            job; customers get a reminder 14 and 3 days before the drop. The
            date is on the customer as `level_valid_until`. No-op for flat earn
            mode.
          properties:
            enabled:
              type: boolean
              default: false
              description: >-
                Level retention switch. Omitted on create = false; omitted on
                PATCH keeps the stored value. Switching it on, or changing
                inactivity_days while it is on, starts one fresh window for
                every customer from that moment. Switching it off keeps every
                customer on the current tier.
            inactivity_days:
              type: integer
              minimum: 1
              maximum: 1095
              default: 365
              description: >-
                Number of days without a qualifying transaction after which the
                customer drops one tier. Range 1-1095 (up to ~3 years). Omitted
                on PATCH keeps the stored window (365 on create).
          example:
            enabled: true
            inactivity_days: 365
          type:
            - object
            - 'null'
    CardMechanics_Discount:
      type: object
      description: >-
        Discount card mechanics. Customer's current tier is determined by
        qualifying spend: cumulative spend, reset to the new tier's threshold
        when level retention drops the customer one tier. Single-tier cards:
        tiers array with 1 entry (everyone gets that percentage). Tiered cards:
        2+ entries, tier auto-computed from qualifying spend. Employee discount
        cards (is_employee_discount=true) add a monthly budget cap. Level
        retention (tier_demotion.enabled) lets dormant customers drop one tier
        after inactivity_days - applies only to tiered cards, not to employee
        discount cards.
      required:
        - type
        - tiers
      properties:
        type:
          type: string
          const: discount
          description: Discriminator. Always 'discount' for this mechanics block.
        tiers:
          type: array
          description: >-
            Discount tier progression. REQUIRED. Order from lowest to highest
            spend_to_achieve_minor. Lowest tier MUST have
            spend_to_achieve_minor=0 (everyone starts here). Single-tier cards:
            array with 1 entry. Tiered cards: 2+ entries. Customer's tier is
            computed from qualifying spend: lifetime cumulative spend minus the
            part removed when level retention dropped the customer a tier, so
            after a drop the customer climbs again from the lower tier's
            threshold. On an employee discount card the tiers are steps on this
            month's counted spend, not on lifetime spend. The counter follows
            employee.budget_counts_as and restarts on employee.reset_day. A
            higher threshold may carry a lower percentage (for example 100
            percent on the first 200, then 20 percent). The discount stops when
            the monthly limit is reached, unless employee.no_limit is true. A
            discount card needs at least 1 level (422 `validation_failed` on
            `mechanics.tiers`). Level names are unique and each level needs a
            spend above the level before it (code `invalid_value`). On an
            employee discount card the first step is at least 1 percent (code
            `out_of_range`). Only a changed value is checked: a card that was
            saved with an older step keeps saving other fields.
          items:
            $ref: '#/components/schemas/CardTier_Discount'
          example:
            - id: tir_xcwxqlyszbgqxkqtgszyqe6n3q
              name: Base
              spend_to_achieve_minor: 0
              percentage: 5
            - id: tir_tch3s2svgbwvmefsacsbcufp5q
              name: Silver
              spend_to_achieve_minor: 50000
              percentage: 10
            - id: tir_qbknazkjhjittxzwp2ijjvkqlq
              name: Gold
              spend_to_achieve_minor: 200000
              percentage: 15
        is_employee_discount:
          type: boolean
          default: false
          description: >-
            Employee discount mode. When true, the `employee` block sets a
            monthly limit or no limit. A new employee discount (on create, or
            when you switch a card to one) needs the `employee` block, else 422
            `parameter_missing` on `mechanics.employee.monthly_budget_minor`.
          example: false
        starting_points:
          type: integer
          minimum: 0
          default: 0
          description: >-
            Starting balance granted at enrollment, applied to the discount
            starting tier balance. Same field/semantics as reward mechanics'
            starting_points. Ignored (never applied) for an employee discount
            card (is_employee_discount=true) - the dashboard never offers a
            starting bonus for employee discount cards. The schema bound is
            100,000,000,000. The unit is whole or decimal major currency units
            (EUR 12.50 is 12.5), not minor units. It counts as spend toward the
            first level thresholds. Limit (VEXiON-231 round 13, same as the
            referral bonus): about EUR 1,000 of spend (CZK 25,000, HUF 400,000,
            PLN 4,000, RON 5,000, GBP 1,000, CHF 1,000, USD 1,000), in major
            currency units. Above the limit the request answers 422
            `validation_failed` with `field_errors[].code` `out_of_range` on
            `mechanics.starting_points`. On PATCH only a changed value is
            checked.
          example: 5
          maximum: 100000000000
        employee:
          description: >-
            EMPLOYEE DISCOUNT MODE ONLY. Budget configuration. With a monthly
            limit, the employee can spend up to monthly_budget_minor per period
            before discounts stop applying. With no_limit true, there is no
            stop: the highest step applies until the reset day.
          properties:
            monthly_budget_minor:
              type: integer
              minimum: 1
              description: >-
                Spending cap in minor currency units (e.g., 50000 = EUR 500.00).
                Maximum 100,000,000,000. Required when you send `employee`
                unless `no_limit` is true. Left out of responses when `no_limit`
                is true, because the card has no limit. Do not send it together
                with `no_limit` true. To switch a card from no limit to a limit,
                send it in the same request.
              example: 50000
              maximum: 100000000000
            no_limit:
              type: boolean
              default: false
              description: >-
                No monthly limit: the discount does not stop at a cap, and the
                highest step applies until the reset day. With no_limit true,
                the highest step must be below 100 % (else 422
                `validation_failed` on `mechanics.tiers`). Omitted on create =
                false. Omitted on PATCH keeps the stored value, except that
                sending a positive `monthly_budget_minor` without `no_limit`
                switches the limit back on. Sending `employee` with neither a
                limit nor `no_limit` true answers 422 `parameter_missing` on
                `mechanics.employee.monthly_budget_minor`. Sending `no_limit`
                true together with `monthly_budget_minor` answers 422
                `validation_failed` on the same parameter.
              example: false
            reset_day:
              type: integer
              minimum: 1
              maximum: 28
              default: 1
              description: >-
                EMPLOYEE DISCOUNT ONLY. Day of month when the monthly budget
                resets (1-28). Default 1 (first of month). The month counter
                restarts on this day.
            budget_counts_as:
              type: string
              enum:
                - grossSpend
                - netSpend
                - discountValueGiven
              default: grossSpend
              description: >-
                What counts against the budget. 'grossSpend' = full purchase
                amount. 'netSpend' = amount after discount. 'discountValueGiven'
                = only the discount value given. With netSpend every discount
                step must be below 100 percent, otherwise 422 validation_failed
                on mechanics.employee.budget_counts_as (or on mechanics.tiers
                when only the tiers are sent).
            hide_budget_on_pass:
              type: boolean
              default: false
              description: >-
                Hide the remaining budget from the customer's wallet pass (some
                employers do not want to disclose spending limits).
          example:
            monthly_budget_minor: 50000
            reset_day: 1
            budget_counts_as: grossSpend
            hide_budget_on_pass: false
          type:
            - object
            - 'null'
        tier_demotion:
          description: >-
            Level retention, same engine as reward tiers: a customer who makes
            no qualifying transaction for inactivity_days drops one tier. Runs
            as a daily job; customers get a reminder 14 and 3 days before the
            drop. The date is on the customer as `level_valid_until`. Applies
            only to cards with 2 or more tiers; Booqme and Bookio cards are not
            affected. On an employee discount card (is_employee_discount=true)
            the field is ignored on write and reads back as null. Default off.
          properties:
            enabled:
              type: boolean
              default: false
              description: >-
                Level retention switch. Omitted on create = false; omitted on
                PATCH keeps the stored value. Switching it on, or changing
                inactivity_days while it is on, starts one fresh window for
                every customer from that moment. Switching it off keeps every
                customer on the current tier.
            inactivity_days:
              type: integer
              minimum: 1
              maximum: 1095
              default: 365
              description: >-
                Number of days without a qualifying transaction after which the
                customer drops one tier. Range 1-1095 (up to ~3 years). Omitted
                on PATCH keeps the stored window (365 on create).
          example:
            enabled: true
            inactivity_days: 365
          type:
            - object
            - 'null'
    CardMechanics_Coupon:
      type: object
      description: >-
        Mechanics for COUPON cards. One-time or reusable single-redemption
        coupons. Used for promo campaigns, birthday rewards, and re-engagement.
      required:
        - type
        - is_reusable
      properties:
        type:
          type: string
          const: coupon
          description: Discriminator. Always 'coupon' for this mechanics block.
        is_reusable:
          type: boolean
          default: false
          description: >-
            When true, the coupon card can be mass-reactivated via POST
            /v1/cards/{id}/reissue_coupons after customers redeem. When false,
            the coupon is single-use per customer and reissue_coupons refuses it
            with 422 coupon_not_reissuable.
          example: false
        reset_count:
          type: integer
          minimum: 0
          readOnly: true
          description: >-
            REUSABLE COUPONS ONLY. Number of times this coupon card has been
            reset (read-only).
          example: 0
        last_reset_at:
          readOnly: true
          description: >-
            Unix timestamp of the most recent coupon reset (read-only). Null if
            never reset.
          example: null
          type:
            - integer
            - 'null'
        reward_value:
          description: >-
            A coupon needs reward_value or a linked_card_template. With neither,
            422 validation_failed on mechanics.reward_value. Freeform reward
            text the customer redeems (e.g., '33% off', '1+1 Drink', 'Free
            coffee', '€5 off any purchase'). Rendered on the wallet pass and on
            the scanner preview at redeem time. Single string OR per-language
            object.
          anyOf:
            - type: string
              title: Single string (all languages)
              maxLength: 200
              example: 33% off
            - type: object
              title: Per-language object
              additionalProperties:
                type: string
                maxLength: 200
              minProperties: 1
              example:
                sk: 33% ZĽAVA
                en: 33% off
                cs: 33% sleva
                de: 33% Rabatt
              properties: {}
        linked_card_template:
          description: >-
            OPTIONAL upgrade-on-redeem card. When the customer redeems this
            coupon, the server ATOMICALLY transforms the customer's card record
            into a NEW card of the type referenced here. Used to retain
            customers after a one-shot conversion event (e.g., welcome coupon →
            stamp card).


            Value: 'without' (or null) = no transform. Otherwise a card_id
            (crd_*) referencing the upgrade target. A card that does not exist,
            or belongs to another organization, answers 422
            `linked_card_not_found`.


            FOR COUPON: only available when is_reusable=false (single-use
            coupons). Reusable + linked are mutually exclusive - wizard hides
            the selector when is_reusable=true and the server returns
            `reusable_coupon_cannot_link` if you try via API. The transform is
            done by VEXiON.


            **TARGET-TYPE ALLOWLIST**: the linked card MUST be of type Stamp /
            Reward / Discount / Membership. Coupon→Coupon and any *→GiftCard
            transforms are rejected server-side (`unsupported_target_type`). The
            wizard surfaces a filtered card picker; the API enforces the same
            constraint.
          example: null
          type:
            - string
            - 'null'
    CardMechanics_Membership:
      type: object
      description: >-
        Mechanics for MEMBERSHIP cards (gym passes, club memberships, season
        tickets). Supports multiple duration options, validity schedules, and
        daily usage caps.
      required:
        - type
        - membership_plans
      properties:
        type:
          type: string
          const: membership
          description: Discriminator. Always 'membership' for this mechanics block.
        validity_schedule:
          type: string
          enum:
            - everyday
            - specific
          default: everyday
          description: >-
            When the membership is usable. 'everyday' = any day, any time
            (subject to valid_times). 'specific' = only the days listed in
            specific_days[].
          example: everyday
        specific_days:
          type: array
          description: >-
            SPECIFIC SCHEDULE ONLY. Per-day validity windows. day uses ISO
            0=Sunday, 6=Saturday.
          items:
            type: object
            properties:
              day:
                type: integer
                minimum: 0
                maximum: 6
                description: Day of week. 0=Sunday, 1=Monday, ..., 6=Saturday.
              all_day:
                type: boolean
                description: When true, valid the entire day (ignores start_time/end_time).
              start_time:
                type: string
                pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
                description: HH:MM 24h format start time.
                example: '06:00'
              end_time:
                type: string
                pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
                description: HH:MM 24h format end time.
                example: '22:00'
          example:
            - day: 1
              all_day: false
              start_time: '06:00'
              end_time: '22:00'
            - day: 2
              all_day: true
        valid_times:
          type: object
          description: >-
            Daily time window applied to all valid days (overridden by
            specific_days entries).
          properties:
            start:
              type: string
              pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
              default: '00:00'
              description: Earliest usable time each day.
            end:
              type: string
              pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
              default: '23:59'
              description: Latest usable time each day.
          example:
            start: '06:00'
            end: '22:00'
        daily_usage_limit:
          type: object
          description: Cap on how many times the membership can be used per calendar day.
          properties:
            enabled:
              type: boolean
              default: false
              description: When true, the cap is enforced. When false, unlimited daily use.
            limit:
              type: integer
              minimum: 1
              maximum: 99
              default: 1
              description: Max activations per calendar day.
          example:
            enabled: true
            limit: 1
        membership_plans:
          type: array
          minItems: 1
          description: >-
            List of membership plans customers can purchase (e.g., 'Monthly
            Premium €30', '3-month Standard €60', 'Annual Premium €200'). Each
            plan has duration, durationType, displayName, and price. Customer
            picks one plan at activation time via mechanics
            initial_state.duration_id.
          items:
            type: object
            required:
              - id
              - duration
              - duration_type
              - display_name
              - price_minor
            properties:
              id:
                type: string
                description: >-
                  Stable identifier for this duration option. Use in customer
                  initial_state.duration_id. GET always returns the server's own
                  opaque mpl_ id for a persisted plan; an id you invent at
                  create time is accepted but only becomes this identity once
                  you round-trip the real id back on a later PATCH.
                example: mpl_c4zdlocdwajf6wtmnvvkonv4oa
              duration:
                type: integer
                minimum: 1
                description: Length in units of duration_type.
              duration_type:
                type: string
                enum:
                  - days
                  - weeks
                  - months
                  - years
                description: Unit of duration.
              display_name:
                type: string
                maxLength: 100
                description: Human-readable label (e.g., 'Monthly Unlimited').
                example: Monthly Unlimited
              price_minor:
                type: integer
                minimum: 0
                description: >-
                  Price in minor currency units (e.g., 4900 = EUR 49.00).
                  Maximum 100,000,000,000.
                example: 4900
                maximum: 100000000000
          example:
            - id: mpl_dmpvemj7nhjqi7wvi757ymxuu4
              duration: 1
              duration_type: months
              display_name: Monthly Unlimited
              price_minor: 4900
            - id: mpl_ma63prjwtyni6bqpkojwc7j6gu
              duration: 1
              duration_type: years
              display_name: Annual Premium
              price_minor: 49000
    CardMechanics_GiftCard:
      type: object
      description: >-
        Mechanics for GIFT CARD cards. Supports preset values with optional
        bonus tiers, partial use, expiration, employee monthly-credit mode, and
        online sales.
      required:
        - type
        - expiration_months
      properties:
        type:
          type: string
          const: gift_card
          description: Discriminator. Always 'gift_card' for this mechanics block.
        preset_values:
          type: array
          description: >-
            Predefined purchase amounts shown to the buyer (e.g., EUR 25 / EUR
            50 / EUR 100). Each entry can include a bonus that gets added on
            top. Empty array = free amount entry only.
          items:
            type: object
            required:
              - amount_minor
            properties:
              amount_minor:
                type: integer
                minimum: 1
                description: >-
                  Purchase amount the buyer pays, in minor units (e.g., 5000 =
                  EUR 50.00). Maximum 100,000,000,000.
                example: 5000
                maximum: 100000000000
              bonus_minor:
                type: integer
                minimum: 0
                default: 0
                description: >-
                  Bonus added to the gift card balance on top of amount_minor.
                  Requires bonus_enabled=true. E.g., buy EUR 50 get EUR 5 extra
                  -> bonus_minor: 500. Maximum 100,000,000,000.
                example: 500
                maximum: 100000000000
              total_minor:
                type: integer
                minimum: 1
                description: >-
                  Resulting gift card balance (amount_minor + bonus_minor).
                  Server-computed; provided here for clarity. Maximum
                  100,000,000,000.
                example: 5500
                maximum: 100000000000
          example:
            - amount_minor: 2500
              bonus_minor: 0
              total_minor: 2500
            - amount_minor: 5000
              bonus_minor: 500
              total_minor: 5500
            - amount_minor: 10000
              bonus_minor: 1500
              total_minor: 11500
        bonus_enabled:
          type: boolean
          default: false
          description: >-
            When true, preset_values[].bonus_minor is honored. When false, bonus
            amounts are ignored.
          example: true
        expiration_months:
          type: integer
          minimum: 1
          default: 12
          description: >-
            Default expiration window in months from purchase date. Default: the
            organization's default expiry (24 months unless changed), then 12.
            Per-customer override available via initial_state.expires_at on POST
            /v1/customers.
          example: 12
        is_employee_card:
          type: boolean
          default: false
          description: >-
            Employee gift card mode. When true, the card is treated as a
            company-issued spending allowance rather than a regular gift.
            Activates employee_card config.
          example: false
        employee_card:
          description: >-
            EMPLOYEE GIFT CARD MODE ONLY. Monthly auto-refill configuration.
            Each refill_day the card balance is topped up to
            monthly_credit_minor.
          properties:
            enabled:
              type: boolean
              default: true
              description: When true, the monthly refill cron runs for this card.
            monthly_credit_minor:
              type: integer
              minimum: 1
              default: 20000
              description: >-
                Credit added each refill cycle, in minor units (e.g., 20000 =
                EUR 200.00). Maximum 100,000,000,000.
              example: 20000
              maximum: 100000000000
            refill_day:
              type: integer
              minimum: 1
              maximum: 28
              default: 1
              description: >-
                Day of month when the refill runs (1-28 to avoid month-length
                issues).
            hide_balance_on_pass:
              type: boolean
              default: true
              description: >-
                When true, the wallet pass renders an 'Employee Card' badge
                instead of the numeric balance (employer does not want to expose
                the spending limit to staff).
          example:
            enabled: true
            monthly_credit_minor: 20000
            refill_day: 1
            hide_balance_on_pass: true
          type:
            - object
            - 'null'
        allow_online_sales:
          type: boolean
          default: false
          description: >-
            When true, this gift card is listed on the merchant's public
            bio-link aggregator (e.g., links.vexioncards.one/<org>) for direct
            online purchase.
          example: false
        online_sale_location_id:
          pattern: ^loc_[A-Za-z0-9]+$
          description: >-
            Pinned location for online gift card sales attribution (when
            allow_online_sales=true). All online purchases of this gift card are
            attributed to this location for revenue reporting and POS
            reconciliation. Does NOT restrict the card to a single physical
            location - customers can REDEEM at any location where the card is
            enabled. When null, online sales fall back to the organization
            default location.
          example: null
          type:
            - string
            - 'null'
        linked_card_template:
          description: >-
            OPTIONAL upgrade-on-depletion card. When the customer's gift card
            balance hits 0 (via a gift_card.spend transaction), the server
            ATOMICALLY transforms the customer's card record into a NEW card of
            the type referenced here. Used to retain customers after they finish
            spending a gift card (e.g., gift card → reward card).


            Value: 'without' (or null) = no transform. Otherwise a card_id
            (crd_*) referencing the upgrade target. A card that does not exist,
            or belongs to another organization, answers 422
            `linked_card_not_found`.


            FOR POS-DRIVEN GIFT CARDS: when the depletion arrives from the
            connected POS, the POS has already deducted the balance server-side.
            The transform service skips the FE-only 'drain' step in that case
            (alreadyDrained:true) to avoid double-deducting. The transform is
            done by VEXiON.


            **TARGET-TYPE ALLOWLIST**: the linked card MUST be of type Stamp /
            Reward / Discount / Membership. Coupon→Coupon and any *→GiftCard
            transforms are rejected server-side (`unsupported_target_type`). The
            wizard surfaces a filtered card picker; the API enforces the same
            constraint.
          example: null
          type:
            - string
            - 'null'
    Currency:
      type: string
      enum:
        - EUR
        - CZK
        - GBP
        - HUF
        - PLN
        - RON
        - CHF
        - USD
      description: >-
        Three-letter ISO 4217 code. The 8 currencies a business can use: EUR,
        CZK, GBP, HUF, PLN, RON, CHF, USD. Amounts in this API are minor units
        with 2 decimals for every currency, also HUF. See [Money and
        currency](/api-reference/money-and-currency).
    PassFieldSlot:
      type: object
      description: >-
        One front-of-pass slot. The platform renders it on BOTH wallets from the
        same definition - Apple as a native pass field (typed formatting: dates,
        numbers, and currency render in the device locale automatically), Google
        as the matching text module (the platform pre-formats typed values
        server-side).
      required:
        - label
        - value_template
      properties:
        label:
          anyOf:
            - type: string
              title: Single label (all languages)
              maxLength: 40
            - type: object
              title: Per-language labels
              additionalProperties:
                type: string
                maxLength: 40
              minProperties: 1
          description: >-
            Small caption above the value. Keep short - wallet UIs truncate
            aggressively (~20 visible chars).
        value_template:
          type: string
          minLength: 1
          maxLength: 200
          description: >-
            Static text or `{{variables}}`: any `{{custom.<key>}}` (typed
            rendering follows the field's type), identity variables
            (`{{first_name}}`, ...), and the card-type mechanics variables. A
            slot whose variables all resolve empty renders as an empty slot.
          example: '{{custom.department}}'
        change_message:
          anyOf:
            - type: string
              title: Single template
              maxLength: 200
            - type: object
              title: Per-language templates
              additionalProperties:
                type: string
                maxLength: 200
              minProperties: 1
            - type: 'null'
              title: Silent (no banner)
          description: >-
            Apple Wallet lock-screen banner fired when this slot's rendered
            value changes - `{{value}}` substitutes the fresh value. Null
            (default) = silent. Apple only (Google caps visible messages at
            3/day, reserved for broadcasts). Fires ONLY on value change -
            broadcasts cannot trigger it.
    PassBackField:
      type: object
      description: >-
        One back-of-pass row. `key` is YOUR stable handle for addressing the row
        across updates (the platform derives the wallet-level identifiers from
        it).
      required:
        - key
        - label
        - value_template
      properties:
        key:
          type: string
          pattern: ^[a-z0-9_]{1,40}$
          description: >-
            Stable row identifier, unique within the card - lets you update one
            row without touching the rest.
          example: opening_hours
        label:
          anyOf:
            - type: string
              title: Single label (all languages)
              maxLength: 40
            - type: object
              title: Per-language labels
              additionalProperties:
                type: string
                maxLength: 40
              minProperties: 1
          description: >-
            Small caption above the value. Keep short - wallet UIs truncate
            aggressively (~20 visible chars).
        value_template:
          type: string
          minLength: 1
          maxLength: 2000
          description: >-
            Static text or `{{variables}}` - back rows fit long content (URLs
            and phone numbers become tappable on Apple).
          example: Mon-Fri 8:00-18:00
        change_message:
          anyOf:
            - type: string
              title: Single template
              maxLength: 200
            - type: object
              title: Per-language templates
              additionalProperties:
                type: string
                maxLength: 200
              minProperties: 1
            - type: 'null'
              title: Silent (no banner)
          description: >-
            Apple Wallet lock-screen banner fired when this slot's rendered
            value changes - `{{value}}` substitutes the fresh value. Null
            (default) = silent. Apple only (Google caps visible messages at
            3/day, reserved for broadcasts). Fires ONLY on value change -
            broadcasts cannot trigger it.
    CardTier_Reward:
      type: object
      description: >-
        Reward tier definition. Used when
        CardMechanics_Reward.earn_mode='tiered'. Customer's current tier is
        computed as the highest tier where their qualifying spend >=
        spend_to_achieve_minor (or, for a Booqme/Bookio-linked card, their
        cumulative visit count >= visits_to_achieve - see both fields' own
        descriptions; a tier carries exactly one of the two). Tier scales the
        base earn_rate via earn_multiplier.
      required:
        - name
        - earn_multiplier
      properties:
        id:
          type: string
          description: >-
            Stable identifier for this tier (auto-generated if omitted). GET
            always returns the server's own opaque tir_ id for a persisted tier;
            an id you invent at create time is accepted but only becomes this
            identity once you round-trip the real id back on a later PATCH.
          example: tir_c4zdlocdwajf6wtmnvvkonv4oa
        name:
          description: >-
            Display name of the tier shown on the wallet pass and notifications.
            Single string OR per-language object.
          anyOf:
            - type: string
              title: Single name (all languages)
              maxLength: 100
              example: Silver
            - type: object
              title: Per-language names
              additionalProperties:
                type: string
                maxLength: 100
              minProperties: 1
              example:
                sk: Strieborný
                cs: Stříbrný
                en: Silver
                de: Silber
              properties: {}
        earn_multiplier:
          type: number
          minimum: 1
          description: >-
            Multiplier applied to base earn rate at this tier (e.g., 1.0 = no
            bonus, 1.5 = 1.5x points, 2.0 = double points). Final points =
            mechanics.earn_rate_per_unit × spend × earn_multiplier. Capped at
            1,000 to prevent overflow/absurd point calculations.
          example: 1.5
          maximum: 1000
        background_image_urls:
          description: >-
            Per-language background image URLs shown when the customer reaches
            this tier. Keys: language codes (sk, cs, en, de) - must be a subset
            of the card's languages_supported (defaulting to
            [default_language]), or the server returns 422 parameter_invalid
            naming the offending key and the allowed set. OVERRIDES card-level
            backgrounds for customers at this tier. Same STRIP asset contract as
            the other backdrop fields: https only, PNG only (magic-byte
            verified), ≤ 3MB, matches the pass strip aspect ratio (~2.61:1, i.e.
            527:202) within 5% tolerance, width between 527px and 4000px - 422
            invalid_image_url / image_format_invalid / image_too_large /
            image_aspect_ratio_invalid / image_resolution_too_small /
            image_resolution_too_large on violation. VEXiON stores a copy of the
            image. Reads return the VEXiON copy URL, not the URL you sent. A URL
            that VEXiON returned earlier (for example one you got back from this
            API, or a library icon) is kept as is. Each language variant is
            mirrored to its OWN object, independently.
          additionalProperties:
            type: string
            format: uri
            description: >-
              HTTPS URL to the tier background image. Recommended 1125x432px.
              Must match the pass strip aspect ratio (~2.61:1) within 5%
              tolerance and be at least 527px wide; 422
              image_aspect_ratio_invalid otherwise.
          example:
            sk: >-
              https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
            en: >-
              https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
          properties: {}
          type:
            - object
            - 'null'
        spend_to_achieve_minor:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Qualifying spend (purchases that earned points, minus reversals; it
            restarts at the lower level's threshold after a retention drop), in
            minor units, that unlocks this tier - the MONEY-gated form. Set on
            every reward tier EXCEPT a Booqme/Bookio-linked card's tiers, which
            are VISIT-gated instead (see visits_to_achieve). Exactly one of
            spend_to_achieve_minor / visits_to_achieve is present per tier,
            never both - which one applies is derived from the card's own POS
            integration linkage, not chosen by the request. Supplying the field
            that does not match the card's mode returns 422.
          example: 500
        visits_to_achieve:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Cumulative VISIT COUNT that unlocks this tier - the visit-gated
            form, set ONLY on a Booqme- or Bookio-linked reward card's tiers (a
            POS-integration-only linkage; a card created directly through this
            API can never be visit-gated, so writing this field is not possible
            via POST /v1/cards today). Every other reward card uses
            spend_to_achieve_minor instead. Exactly one of the two is present
            per tier, never both.
          example: null
    CardTier_Discount:
      type: object
      description: >-
        Discount tier definition. Customer's current tier is computed as the
        highest tier where their cumulative spend >= spend_to_achieve_minor.
        Discount tiers are always money-gated - there is no visit-based discount
        card.
      required:
        - name
        - spend_to_achieve_minor
        - percentage
      properties:
        id:
          type: string
          description: >-
            Stable identifier for this tier (auto-generated if omitted, used to
            reference the tier in mechanics initial_state.tier_id). GET always
            returns the server's own opaque tir_ id for a persisted tier; an id
            you invent at create time is accepted but only becomes this identity
            once you round-trip the real id back on a later PATCH.
          example: tir_c4zdlocdwajf6wtmnvvkonv4oa
        name:
          description: >-
            Display name of the tier shown on the wallet pass and notifications.
            Single string OR per-language object.
          anyOf:
            - type: string
              title: Single name (all languages)
              maxLength: 100
              example: Silver
            - type: object
              title: Per-language names
              additionalProperties:
                type: string
                maxLength: 100
              minProperties: 1
              example:
                sk: Strieborný
                cs: Stříbrný
                en: Silver
                de: Silber
              properties: {}
        spend_to_achieve_minor:
          type: integer
          minimum: 0
          description: >-
            Cumulative qualifying spend (minor units) at which this tier
            activates. Tiers auto-promote as spend crosses thresholds. When
            `tier_demotion` is enabled and the customer drops a tier, qualifying
            spend restarts at the lower tier's threshold. Maximum
            100,000,000,000.
          example: 50000
          maximum: 100000000000
        percentage:
          type: number
          minimum: 0
          maximum: 100
          description: >-
            Discount percentage applied at POS when the customer is in this tier
            (e.g., 10 = 10% off every purchase).
          example: 10
        background_image_urls:
          description: >-
            Per-language background image URLs shown when the customer reaches
            this tier. Keys: language codes (sk, cs, en, de) - must be a subset
            of the card's languages_supported (defaulting to
            [default_language]), or the server returns 422 parameter_invalid
            naming the offending key and the allowed set. OVERRIDES card-level
            backgrounds for customers at this tier. Same STRIP asset contract as
            the other backdrop fields: https only, PNG only (magic-byte
            verified), ≤ 3MB, matches the pass strip aspect ratio (~2.61:1, i.e.
            527:202) within 5% tolerance, width between 527px and 4000px - 422
            invalid_image_url / image_format_invalid / image_too_large /
            image_aspect_ratio_invalid / image_resolution_too_small /
            image_resolution_too_large on violation. VEXiON stores a copy of the
            image. Reads return the VEXiON copy URL, not the URL you sent. A URL
            that VEXiON returned earlier (for example one you got back from this
            API, or a library icon) is kept as is. Each language variant is
            mirrored to its OWN object, independently.
          additionalProperties:
            type: string
            format: uri
            description: >-
              HTTPS URL to the tier background image. Recommended 1125x432px.
              Must match the pass strip aspect ratio (~2.61:1) within 5%
              tolerance and be at least 527px wide; 422
              image_aspect_ratio_invalid otherwise.
          example:
            sk: >-
              https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
            en: >-
              https://stamp-icons.s3.eu-central-1.amazonaws.com/samples/backdrop-527x202.png?v=2026-09-17
          properties: {}
          type:
            - object
            - 'null'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        Your secret API key as a Bearer token: `Authorization: Bearer
        vxn_sk_...`. Keys are created and scoped in the dashboard - see
        [Authentication](/api-reference/introduction#authentication).

````

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