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

# Send the wallet card to the customer by email or SMS

> Sends (or re-sends) the wallet claim link to the customer over email, SMS, or both. Subject to a 5-minute per-channel cooldown to prevent abuse: a tight retry loop returns 429. The customer must already have the contact field for the chosen channel (email for `channel=email`, phone for `channel=sms`, both for `channel=both`) or the request returns `422 contact_missing_for_channel`. Templates and copy follow the customer's `language`, overridable via `language`. A successful delivery sets `delivery.status='sent'`, returns the updated Customer, and fires `customer.updated`. For first-time enrolment, prefer passing `deliver` inline on POST /v1/customers instead of this two-step flow.



## OpenAPI

````yaml /openapi.json post /v1/customers/{id}/deliver
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/customers/{id}/deliver:
    post:
      tags:
        - Customers
      summary: Send the wallet card to the customer by email or SMS
      description: >-
        Sends (or re-sends) the wallet claim link to the customer over email,
        SMS, or both. Subject to a 5-minute per-channel cooldown to prevent
        abuse: a tight retry loop returns 429. The customer must already have
        the contact field for the chosen channel (email for `channel=email`,
        phone for `channel=sms`, both for `channel=both`) or the request returns
        `422 contact_missing_for_channel`. Templates and copy follow the
        customer's `language`, overridable via `language`. A successful delivery
        sets `delivery.status='sent'`, returns the updated Customer, and fires
        `customer.updated`. For first-time enrolment, prefer passing `deliver`
        inline on POST /v1/customers instead of this two-step flow.
      operationId: deliverCustomerCard
      parameters:
        - name: id
          in: path
          description: Customer id with cus_ prefix.
          required: true
          example: cus_d31a90e6b8c47fb251f0
          schema:
            type: string
            pattern: ^cus_[A-Za-z0-9]+$
        - name: Idempotency-Key
          in: header
          description: Replay-safe key, 24h cache. UUID v4 recommended.
          required: true
          example: 7d8e2c9a-1f4b-4e2a-8d5c-3b6f1c8a9e2d
          schema:
            type: string
            maxLength: 255
            format: uuid
            minLength: 8
            pattern: ^[A-Za-z0-9_-]{8,255}$
        - name: Accept-Language
          in: header
          description: One of en, sk, cs, de. Defaults to en.
          required: false
          example: sk
          schema:
            type: string
            enum:
              - en
              - sk
              - cs
              - de
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryRequest'
            examples:
              Email SK:
                summary: Email SK
                value:
                  channel: email
                  language: sk
              SMS SK:
                summary: SMS SK
                value:
                  channel: sms
                  language: sk
              Both DE:
                summary: Both DE
                value:
                  channel: both
                  language: de
              Language omitted:
                summary: Language omitted
                value:
                  channel: email
        required: true
      responses:
        '200':
          description: Updated customer - `last_delivery` reflects this send.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
              examples:
                Delivered email:
                  summary: Delivered email
                  value:
                    id: cus_d31a90e6b8c47fb251f0
                    object: customer
                    email: lucy.morgan@example.com
                    phone: '+421948555200'
                    first_name: Lucia
                    language: sk
                    gdpr_redacted_at: null
                    card_id: crd_zqpuluiwik4cs2aulxk3e54haa
                    card_type: custom
                    status: null
                    device: null
                    installed_at: null
                    claim_url: https://vexioncards.one/card/add/84ad785b092fd348211c8dcc
                    last_delivery:
                      channel: email
                      status: sent
                      to: lucy.morgan@example.com
                      sent_at: 1782691800
                    referral_stats: null
                    created_at: 1782604900
                    updated_at: 1782691800
                    last_visited_at: null
                    last_visited_location_id: null
                    deleted_at: null
                Delivered both:
                  summary: Delivered both
                  value:
                    id: cus_d31a90e6b8c47fb251f0
                    object: customer
                    email: lucy.morgan@example.com
                    phone: '+421948555200'
                    first_name: Lucia
                    language: sk
                    gdpr_redacted_at: null
                    card_id: crd_zqpuluiwik4cs2aulxk3e54haa
                    card_type: custom
                    status: null
                    device: null
                    installed_at: null
                    claim_url: https://vexioncards.one/card/add/84ad785b092fd348211c8dcc
                    last_delivery:
                      channel: both
                      status: sent
                      to: lucy.morgan@example.com, +421948555200
                      sent_at: 1782691900
                    referral_stats: null
                    created_at: 1782604900
                    updated_at: 1782691900
                    last_visited_at: null
                    last_visited_location_id: null
                    deleted_at: null
          headers: {}
        '400':
          description: Malformed body or missing required field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: invalid_request_error
                  code: invalid_query_parameter
                  message: Invalid value for a query parameter.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#invalid_query_parameter
                  request_id: req_4d8b1e2f9c3a5
              examples:
                Http 400 default:
                  summary: Http 400 default
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_query_parameter
                      message: Invalid value for a query parameter.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_query_parameter
                      request_id: req_4d8b1e2f9c3a5
          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_01jBhZL5K2N3P4Q5R6S7T8U9V0
              examples:
                No API key:
                  summary: No 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_01jBhZL5K2N3P4Q5R6S7T8U9V0
                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_PxaYN77Qmrwg8fxyBP54AWvj9Q
                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_W5d5q9Y9GGDZAaCMQTQJdxFXsD
          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 customers:write scope.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                  request_id: req_01jBhZLFL3P4Q5R6S7T8U9V0W1
              examples:
                Insufficient scope:
                  summary: Insufficient scope
                  value:
                    error:
                      type: permission_error
                      code: insufficient_scope
                      message: This API key does not have the customers:write scope.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                      request_id: req_01jBhZLFL3P4Q5R6S7T8U9V0W1
          headers: {}
        '404':
          description: Resource not found in your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: not_found_error
                  code: resource_missing
                  message: >-
                    Customer 'cus_does_not_exist' was not found in this
                    organization.
                  param: id
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#resource_missing
                  request_id: req_01jBhZLRM4Q5R6S7T8U9V0W1X2
              examples:
                Customer not found:
                  summary: Customer not found
                  value:
                    error:
                      type: not_found_error
                      code: resource_missing
                      message: >-
                        Customer 'cus_does_not_exist' was not found in this
                        organization.
                      param: id
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#resource_missing
                      request_id: req_01jBhZLRM4Q5R6S7T8U9V0W1X2
          headers: {}
        '409':
          description: >-
            Idempotency-key reuse, balance constraint violated, or
            already-processed conflict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                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_3f1e9d7c5b2a4
              examples:
                Http 409 default:
                  summary: Http 409 default
                  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_3f1e9d7c5b2a4
          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'
              example:
                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_8b4d2f6e1c9a3
              examples:
                Http 422 default:
                  summary: Http 422 default
                  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_8b4d2f6e1c9a3
          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: deliver_cooldown_active
                  message: >-
                    A wallet-link email was sent to this customer 2m17s ago. The
                    per-channel cooldown is 5 minutes. Retry after 163 seconds.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#deliver_cooldown_active
                  request_id: req_01jBhZM2N5R6S7T8U9V0W1X2Y3
              examples:
                Cooldown active:
                  summary: Cooldown active
                  value:
                    error:
                      type: rate_limit_error
                      code: deliver_cooldown_active
                      message: >-
                        A wallet-link email was sent to this customer 2m17s ago.
                        The per-channel cooldown is 5 minutes. Retry after 163
                        seconds.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#deliver_cooldown_active
                      request_id: req_01jBhZM2N5R6S7T8U9V0W1X2Y3
          headers: {}
        '500':
          description: Server error. Retry-safe on idempotent calls.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: api_error
                  code: delivery_failed
                  message: >-
                    Delivery of the wallet card link failed. The request may be
                    retried with the same Idempotency-Key.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#delivery_failed
                  request_id: req_5f8b2c4a9e1d3
              examples:
                Delivery failed:
                  summary: Delivery failed
                  value:
                    error:
                      type: api_error
                      code: delivery_failed
                      message: >-
                        Delivery of the wallet card link failed. The request may
                        be retried with the same Idempotency-Key.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#delivery_failed
                      request_id: req_5f8b2c4a9e1d3
          headers: {}
      deprecated: false
components:
  schemas:
    DeliveryRequest:
      type: object
      required:
        - channel
      properties:
        channel:
          type: string
          enum:
            - email
            - sms
            - both
          description: >-
            Where to send the claim link: `email`, `sms`, or `both`. The
            customer record must carry the matching contact (`422
            contact_missing_for_channel` otherwise). SMS goes out only to E.164
            numbers.
        language:
          type: string
          enum:
            - en
            - sk
            - cs
            - de
          description: >-
            Message language. Defaults to the customer's preferred language,
            then the card default.
      description: Deliver the wallet card link to the customer by email or SMS.
    Customer:
      type: object
      required:
        - id
        - object
        - card_id
      properties:
        id:
          type: string
          description: >-
            Customer identifier - an opaque, server-issued token prefixed cus_.
            Encodes the internal record through a keyed cipher (AES-128 + an
            integrity tag, base32-encoded) - not decodable or guessable
            client-side; the prefix lets clients route on resource type without
            parsing the ID.
          pattern: ^cus_[A-Za-z0-9]+$
          example: cus_g2y5z2ylfkrybsthe2qnweualy
        object:
          type: string
          const: customer
          description: >-
            Always 'customer'. Type discriminator for consumers that handle
            multiple resource families.
        external_id:
          description: >-
            Your stable reference for this customer (CRM ID, internal account
            number, etc.). Highest-priority dedupe field on POST.
          type:
            - string
            - 'null'
        email:
          format: email
          description: >-
            Customer's email address. Used for wallet pass delivery, account
            recovery, and dedupe.
          maxLength: 255
          example: customer@example.com
          type:
            - string
            - 'null'
        phone:
          description: >-
            Customer's phone number in E.164 format. Used for SMS pass delivery
            and dedupe.
          pattern: ^\+[1-9]\d{1,14}$
          minLength: 8
          maxLength: 16
          example: '+14155552671'
          type:
            - string
            - 'null'
        first_name:
          description: Given name. Shown on the wallet pass when configured.
          type:
            - string
            - 'null'
        last_name:
          description: Family name. Shown on the wallet pass when configured.
          type:
            - string
            - 'null'
        date_of_birth:
          format: date
          description: >-
            Customer's date of birth in ISO 8601 format (YYYY-MM-DD). Used for
            birthday campaigns and age-gated rewards.
          type:
            - string
            - 'null'
        language:
          type: string
          enum:
            - sk
            - cs
            - en
            - de
            - hu
            - hr
            - ro
          description: >-
            Preferred display language for the wallet pass and notifications.
            Writable values are sk, cs, en, de; reads may additionally return
            hu, hr, or ro for legacy customers enrolled before the language set
            was narrowed.
        marketing_opt_in:
          type: boolean
          description: >-
            A record of the customer's marketing consent, stored and returned
            for your own use and audits. VEXiON does not apply it when sending
            notifications: a notification sent through the API is not filtered
            by this flag or by the customer's marketing switch in the dashboard.
            To respect opt-outs on API sends, send only to customers with
            marketing_opt_in true, using explicit `customer_ids`.
        gdpr_redacted_at:
          description: >-
            Unix timestamp when the customer requested GDPR
            right-to-be-forgotten. When set, all PII fields are anonymized but
            the skeleton record is preserved for analytics integrity. Distinct
            from deleted_at (soft-delete from admin or DELETE
            /v1/customers/{id}).
          example: null
          type:
            - integer
            - 'null'
        card_id:
          type: string
          description: Card template (crd_) this customer is enrolled in. Immutable.
        card_type:
          type: string
          enum:
            - stamp
            - reward
            - discount
            - coupon
            - membership
            - gift_card
            - custom
          description: >-
            Echo of the parent card's type for routing convenience. Mirrors
            card.type.
        card:
          $ref: '#/components/schemas/CustomerCardSlot'
          description: >-
            Type-specific mechanics data (stamps collected, points balance,
            tier, gift-card balance, etc.). Shape varies per card_type.
        signup_location_id:
          description: >-
            Location whose signup QR/link enrolled this customer (`?loc=` on the
            signup URL). Null for customers created via API or dashboard - only
            public signup-form enrollments carry it.
          type:
            - string
            - 'null'
        status:
          enum:
            - installed
            - not_installed
            - deleted
            - disputed
            - null
          description: >-
            Wallet pass install state on the customer's device. installed = pass
            on at least one device, not_installed = signed up but pass not yet
            added, deleted = customer removed the pass, disputed = wallet
            flagged the pass. A customer with no stored status reads
            `not_installed`; the value is never null.
          type:
            - string
            - 'null'
        device:
          enum:
            - apple_wallet
            - google_wallet
            - null
          description: >-
            Wallet platform holding the pass: `apple_wallet` or `google_wallet`.
            Null when no wallet pass is installed. The platform tracks Apple and
            Google Wallet only - any other install channel reads as null.
          type:
            - string
            - 'null'
        installed_at:
          description: >-
            Unix seconds when the pass was FIRST added to a wallet (Apple/Google
            install callback). Null until installed. Distinct from `created_at`
            (when the customer record was created at signup) - the gap between
            the two is your activation funnel.
          example: 1779494400
          type:
            - integer
            - 'null'
        apple_wallet_url:
          description: >-
            Direct download URL for the .pkpass file. Sent to iPhone customers
            via SMS or email.
          type:
            - string
            - 'null'
        google_wallet_url:
          description: >-
            Save-to-Google-Wallet URL for this customer's card, or `null` if not
            yet generated. Generated lazily: `null` on a freshly created
            customer, and populated on the first `GET /v1/customers/{id}` for
            that customer (not on create, and not on the list endpoint) -
            persisted after the first successful generation, so later reads
            return the same URL without regenerating it. A transient generation
            failure leaves this `null`; retry by reading the customer again.
          type:
            - string
            - 'null'
        claim_url:
          type: string
          description: >-
            Public claim URL the customer visits to add the pass to their
            wallet. Auto-detects device and serves the right format. Treat the
            URL as opaque - never construct or parse it; its internal format may
            change.
        serial_number:
          type: string
          description: >-
            The value encoded in the customer's wallet pass QR code or barcode,
            and the number printed on physical cards. A merchant-assigned custom
            number when `serial_number_locked` is true; otherwise a
            platform-assigned 24-character hex value, or a referral link ending
            in it when the card has referrals enabled. NOT the customer `id`: to
            find the customer behind a scanned code, call `GET
            /v1/customers?serial_number=<scanned value>`.
        serial_number_locked:
          type: boolean
          description: >-
            True when the serial number was custom-set by staff (do not
            regenerate). False when auto-generated.
        custom:
          type: object
          additionalProperties: true
          description: >-
            Custom field VALUES for this customer, keyed by the definition's
            `key`. Validated against the card's form and the `cf_` definitions -
            unknown keys are rejected (`422 unknown_custom_field`).


            Value shapes by field type: `text`/`textarea` → string; `number` →
            number (integer when the definition says so); `currency` → integer
            minor units; `date` → `YYYY` / `YYYY-MM` / `YYYY-MM-DD` per
            granularity; `boolean` → true/false; `select` → one option value;
            `multiselect` → array of option values.


            Renderable everywhere via `{{custom.<key>}}`: pass slots,
            notification title/body - typed values format in the customer's
            locale.
          properties: {}
        metadata:
          type: object
          description: >-
            Partner-internal `{key: value}` map (max 50 keys, key
            `^[a-zA-Z0-9_]{1,40}$`, string values ≤500 chars). NEVER rendered to
            the customer - for CRM ids, import batches, integration markers.
            Customer-facing typed data belongs in `custom` (validated against
            custom field definitions, renderable on passes/notifications).


            **Update semantics (JSON Merge Patch):** PATCH merges per key - send
            only the keys you change; `"key": null` deletes that key;
            `"metadata": null` clears the whole map; omitting `metadata` leaves
            it untouched.
          additionalProperties:
            type: string
            maxLength: 500
          maxProperties: 50
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,40}$
          properties: {}
        last_delivery:
          properties:
            channel:
              type: string
              enum:
                - email
                - sms
                - both
            status:
              type: string
              enum:
                - sent
                - failed
              description: >-
                `sent` = the provider accepted the message (not proof of
                receipt); `failed` = provider rejection (bad number, bounced
                address).
            to:
              type: string
            sent_at:
              type: integer
            error:
              type:
                - string
                - 'null'
              description: >-
                Provider failure reason when `status: "failed"` (bounced
                address, invalid number). Null on success.
          description: >-
            Most recent email/SMS delivery attempt for this customer's wallet
            pass. Null if never delivered via API. Contains channel, destination
            (redacted email/phone), status, sent_at timestamp, and error message
            if delivery failed.
          type:
            - object
            - 'null'
        referral_stats:
          description: >-
            Referral program statistics for this customer. Null when the card
            template has no referral program configured. All counts are
            non-negative integers.
          properties:
            total_referrals:
              type: integer
              minimum: 0
              description: >-
                Total referrals sent by this customer (whether they converted or
                not).
              example: 3
            successful_referrals:
              type: integer
              minimum: 0
              description: Referrals that converted into active customers.
              example: 2
            pending_referrals:
              type: integer
              minimum: 0
              description: >-
                Referrals sent but not yet converted. A referral waits 90 days
                after the friend signs up for the first purchase; after that it
                expires and is not counted here.
              example: 1
            total_bonuses_earned:
              type: integer
              minimum: 0
              description: >-
                Total bonus stamps or points awarded for successful referrals -
                the same unit as the card's
                `referral.referrer_bonus`/`referral.referee_bonus`, and as
                `unit` on this object names directly (no need to cross-reference
                the enrolled card's `type` separately anymore).
              example: 200
            unit:
              type: string
              enum:
                - stamps
                - points
              description: >-
                What `total_bonuses_earned` counts, resolved from the enrolled
                card's type: `stamp` cards award `stamps`; every other card type
                (`reward`, including cashback redemption mode, which rides the
                same points ledger as any other Reward card) awards `points`.
                Mirrors the `unit` convention on Transaction.
              example: points
            last_referral_at:
              description: >-
                Unix timestamp of most recent referral sent. Null if never
                referred.
              example: 1782000000
              type:
                - integer
                - 'null'
          required:
            - total_referrals
            - successful_referrals
            - pending_referrals
            - total_bonuses_earned
            - unit
          type:
            - object
            - 'null'
        created_at:
          type: integer
          description: Unix timestamp when the customer record was created (signup time).
        updated_at:
          type: integer
          description: Unix timestamp when any field on the customer was last modified.
        last_visited_at:
          description: >-
            Unix seconds of the customer's most recent confirmed transaction -
            ANY channel (POS, scanner, API, online). Stored denormalized for
            fast filtering; equals the newest ledger entry (see GET
            /v1/transactions), except that gift card lock, employee refill and
            merchant issue rows do not move it.
          example: 1782604800
          type:
            - integer
            - 'null'
        level_valid_until:
          description: >-
            Unix seconds until which the customer keeps the current discount or
            reward tier (level retention, `mechanics.tier_demotion`). It is the
            end of the Bratislava calendar day: a qualifying transaction on or
            before that day keeps the level, and the first daily run after it
            moves the customer down one tier. Null when level retention is off
            on the card, the customer is on the lowest tier, the card cannot use
            level retention (employee discount, Booqme or Bookio card, flat
            reward, fewer than 2 tiers), or the card entry is deleted or
            disputed. Reminders go out 14 and 3 days before this date.
          example: 1807833599
          type:
            - integer
            - 'null'
        last_visited_location_id:
          description: >-
            Location of the most recent confirmed transaction - ANY channel.
            Stored denormalized for fast filtering; equals the newest ledger
            entry (see GET /v1/transactions), except that gift card lock,
            employee refill and merchant issue rows do not move it.
          example: loc_g2f2cbt7cbuz3hambazorvws2q
          type:
            - string
            - 'null'
        deleted_at:
          description: >-
            Unix seconds when the customer was soft-deleted (DELETE
            /v1/customers/{id} or dashboard). Null while active. Soft-deleted
            customers keep their FULL transaction ledger - history is never
            destroyed. Distinct from `gdpr_redacted_at` (right-to-be-forgotten
            anonymization).
          type:
            - integer
            - 'null'
    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)
                errors and on the retryable 409 conflicts
                (`idempotency_key_in_use`, `create_in_progress`,
                `concurrent_update`); 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 errors and
            retryable 409 conflicts 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.
    CustomerCardSlot:
      type: object
      description: >-
        Per-customer single card slot, trimmed by the parent card_type. Common
        fields (visit + transaction aggregates, slot timestamps) appear on every
        type; the type-specific block is selected by card_type. Points and
        cashback balance never coexist on one reward slot. Lifetime aggregates
        live in /v1/transactions, not here. See the Customers API reference for
        the per-type field tables.
      required:
        - card_id
        - card_type
      additionalProperties: false
      properties:
        card_id:
          type: string
          description: Public id of the parent card template (crd_).
        card_type:
          type: string
          enum:
            - stamp
            - reward
            - discount
            - coupon
            - membership
            - gift_card
            - custom
          description: >-
            Branch on this to read the variant fields. 'custom' carries no
            type-specific block - only the common fields above.
        status:
          type:
            - string
            - 'null'
          enum:
            - installed
            - not_installed
            - deleted
            - disputed
            - null
          description: Pass-install status for this slot.
        total_visits:
          type: integer
          description: Lifetime visit count on this slot.
        first_transaction_at:
          type:
            - integer
            - 'null'
          description: Unix seconds of the first transaction on this slot.
        last_transaction_at:
          type:
            - integer
            - 'null'
          description: Unix seconds of the most recent transaction on this slot.
        created_at:
          type: integer
          description: Unix seconds the slot was created.
        updated_at:
          type: integer
          description: Unix seconds the slot was last updated.
        remaining_stamps:
          type: integer
          description: 'Stamp only: stamps left until the next reward.'
        total_stamps:
          type: integer
          description: 'Stamp only: stamps required to complete one row.'
        stamps_pending:
          type: integer
          description: 'Stamp only: stamps awaiting confirmation.'
        points:
          type: number
          description: >-
            Reward (points modes) only: current points balance. Can carry up to
            4 decimals (see Transaction `value`).
        points_pending:
          type: number
          description: 'Reward (points modes) only: points awaiting confirmation.'
        reward_tier:
          type:
            - object
            - 'null'
          description: >-
            Reward cards with levels in use (tiered earn mode and at least one
            tier) only, points and cashback alike: the customer's current tier.
            The name and the multiplier are resolved live from the stored spend
            against the card's current tiers, so a tier edit shows at once.
            Absent when the card's levels are off. `tier_effective_date` and
            `previous_tier_name` are as stored and can lag `tier_name` after a
            tier edit.
          additionalProperties: false
          properties:
            tier_name:
              type:
                - string
                - 'null'
            earn_multiplier:
              type: number
            tier_effective_date:
              type:
                - integer
                - 'null'
            previous_tier_name:
              type:
                - string
                - 'null'
        cashback_balance_minor:
          type: integer
          description: >-
            Reward (cashback redemption mode) only: cashback balance in minor
            units. This is the credit the customer sees on the wallet pass and
            in push messages (points x `cashback_point_value`). On a card
            connected to a till that sets its own point value (Dotykacka,
            Storyous, Papaya, Oberon), the rate of the till is used, as on the
            wallet pass.
        cashback_pending_minor:
          type:
            - integer
            - 'null'
          description: >-
            Reward (cashback redemption mode) only. Always `null` in the current
            implementation - cashback redemptions finalize immediately at the
            till, so there is no pending/reservation state to report. Reserved
            for a possible future async confirmation flow.
        discount:
          type: object
          description: 'Discount only: current discount state.'
          additionalProperties: false
          properties:
            discount_level:
              type:
                - integer
                - 'null'
              description: Current tier percent off.
            period_spend_minor:
              type:
                - integer
                - 'null'
              description: Spend accumulated in the current window, in minor units.
            period_start_date:
              type:
                - string
                - 'null'
              description: YYYY-MM-DD the current window began.
            next_reset_date:
              type:
                - string
                - 'null'
              description: YYYY-MM-DD the window resets.
        coupon_status:
          type:
            - string
            - 'null'
          description: >-
            Coupon only: `active`, `used`, `redeemed` or `expired`. Always
            lowercase on the wire. The platform stores this value mixed case
            internally for historical reasons; the API normalises it, so branch
            on the lowercase form. `used` and `expired` are reserved: no current
            code path produces them, so a branch on either is dead code today.
          enum:
            - active
            - used
            - redeemed
            - expired
            - null
        membership:
          type: object
          description: 'Membership only: current membership state.'
          additionalProperties: false
          properties:
            is_active:
              type: boolean
            activated_at:
              type:
                - integer
                - 'null'
            expires_at:
              type:
                - integer
                - 'null'
            last_used_at:
              type:
                - integer
                - 'null'
            uses_today:
              type: integer
            total_uses:
              type: integer
        balance_minor:
          type: integer
          description: 'Gift card only: current balance in minor units.'
        expires_at:
          type:
            - integer
            - 'null'
          description: 'Gift card only: unix seconds the balance expires.'
        currency:
          $ref: '#/components/schemas/Currency'
          description: >-
            The business currency of the card. It is the currency of
            `cashback_balance_minor` and `balance_minor`. Also sent on the
            customer webhooks.
    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).
  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.