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

# List customers

> Returns a cursor-paginated list of customers in the calling organization, sorted by `created_at` **descending** (newest first).

## Filtering

Filters combine with **AND**. Two categories:

- **Lookup filters** (rate-limited more strictly - used to find a specific record): `email`, `phone`, `external_id`
- **Scan filters** (standard rate limit - used to iterate a subset): `card_id`, `location_id`, `status`, `device`, `created[gte|lte]`, `updated[gte|lte]`
- **Serial number lookup** (standard rate limit - returns at most one customer): `serial_number`, the value in the customer's wallet pass QR code or barcode, or the number on a physical card. Send it exactly as you have it; it is not the customer `id`.

Lookup filters are stricter because they can be used to enumerate the organization's customers.

See [Pagination](/api-reference/pagination) for the cursor pattern and [Rate Limits](/api-reference/rate-limits#default-limits) for the per-key ceilings.

## Date filters

`created[gte|lte]` and `updated[gte|lte]` accept **ISO-8601 timestamps** (with timezone) or **Unix seconds** - either format works interchangeably.



## OpenAPI

````yaml /openapi.json get /v1/customers
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:
    get:
      tags:
        - Customers
      summary: List customers
      description: >-
        Returns a cursor-paginated list of customers in the calling
        organization, sorted by `created_at` **descending** (newest first).


        ## Filtering


        Filters combine with **AND**. Two categories:


        - **Lookup filters** (rate-limited more strictly - used to find a
        specific record): `email`, `phone`, `external_id`

        - **Scan filters** (standard rate limit - used to iterate a subset):
        `card_id`, `location_id`, `status`, `device`, `created[gte|lte]`,
        `updated[gte|lte]`

        - **Serial number lookup** (standard rate limit - returns at most one
        customer): `serial_number`, the value in the customer's wallet pass QR
        code or barcode, or the number on a physical card. Send it exactly as
        you have it; it is not the customer `id`.


        Lookup filters are stricter because they can be used to enumerate the
        organization's customers.


        See [Pagination](/api-reference/pagination) for the cursor pattern and
        [Rate Limits](/api-reference/rate-limits#default-limits) for the per-key
        ceilings.


        ## Date filters


        `created[gte|lte]` and `updated[gte|lte]` accept **ISO-8601 timestamps**
        (with timezone) or **Unix seconds** - either format works
        interchangeably.
      operationId: listCustomers
      parameters:
        - name: card_id
          in: query
          description: Restrict to customers enrolled on this card template.
          required: false
          example: crd_sophcrprtvpmmnwxah2lomnkee
          schema:
            type: string
            pattern: ^crd_[A-Za-z0-9]+$
        - name: email
          in: query
          description: Exact-match lookup. Rate-limited (5/sec/org). Case-insensitive.
          required: false
          example: anna.smith@example.sk
          schema:
            type: string
            format: email
            minLength: 3
            maxLength: 254
        - name: phone
          in: query
          description: Exact-match lookup in E.164 form. Rate-limited (5/sec/org).
          required: false
          example: '+421905123456'
          schema:
            type: string
            minLength: 4
            maxLength: 20
            pattern: ^\+[1-9]\d{1,14}$
        - name: external_id
          in: query
          description: >-
            Exact-match lookup against your partner-side reference.
            Highest-priority dedupe field on POST.
          required: false
          example: crm-customer-7421
          schema:
            type: string
            minLength: 1
            maxLength: 191
        - name: serial_number
          in: query
          description: >-
            Look up a customer by serial number: the value encoded in their
            wallet pass QR code or barcode and printed on physical cards (see
            `Customer.serial_number`). Send it exactly as you have it, for
            example as scanned, typed or stored from a webhook. Accepts the
            platform-assigned serial (a 24-character hex value), a referral-link
            QR (`https://www.vexioncards.one/ref/<serial>`), a gift-card QR
            (`https://www.vexioncards.one/gfc/<serial>`, returns the gift-card
            customer) and a merchant-assigned custom serial
            (`serial_number_locked: true`). Returns at most one customer; an
            unknown value returns an empty list, not an error. Standard rate
            limit (not the 5/sec lookup tier).
          required: false
          example: 6ac53be9fe06508914775125
          schema:
            type: string
            minLength: 1
            maxLength: 256
        - name: created[gte]
          in: query
          description: >-
            Lower bound on created_at, inclusive. ISO-8601 datetime with
            timezone, or unix seconds.
          required: false
          example: '2026-06-01T00:00:00+02:00'
          schema:
            anyOf:
              - type: integer
                title: Unix seconds
              - type: string
                format: date-time
                title: ISO-8601
        - name: created[lte]
          in: query
          description: Upper bound on created_at, inclusive.
          required: false
          example: '2026-06-30T23:59:59+02:00'
          schema:
            anyOf:
              - type: integer
                title: Unix seconds
              - type: string
                format: date-time
                title: ISO-8601
        - name: updated[gte]
          in: query
          description: >-
            Lower bound on updated_at, inclusive. Use this for delta-sync loops
            driven by `customer.updated` webhooks.
          required: false
          example: '2026-06-27T00:00:00+02:00'
          schema:
            anyOf:
              - type: integer
                title: Unix seconds
              - type: string
                format: date-time
                title: ISO-8601
        - name: updated[lte]
          in: query
          description: Upper bound on updated_at, inclusive.
          required: false
          example: '2026-06-27T23:59:59+02:00'
          schema:
            anyOf:
              - type: integer
                title: Unix seconds
              - type: string
                format: date-time
                title: ISO-8601
        - name: limit
          in: query
          description: >-
            Page size, 1-100. Default 25. Server may return fewer if the page
            reaches the end.
          required: false
          example: 25
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: starting_after
          in: query
          description: >-
            Cursor: customer id from the previous page. Returns customers
            created strictly BEFORE this one. Mutually exclusive with
            ending_before. Numeric offset or page parameters are not supported;
            cursors are the only supported paging mechanism.
          required: false
          example: cus_b8c2e0a4f59d4a73a1e9
          schema:
            type: string
            pattern: ^cus_[A-Za-z0-9]+$
        - name: ending_before
          in: query
          description: >-
            Cursor: customer id from the previous page. Returns customers
            created strictly AFTER this one (reverse pagination). Mutually
            exclusive with starting_after.
          required: false
          example: cus_a4f72c91e0bd4185b6c2
          schema:
            type: string
            pattern: ^cus_[A-Za-z0-9]+$
        - name: location_id
          in: query
          description: Filter by signup_location_id (where the customer first joined).
          required: false
          example: loc_ibjeubrxvvruf6aq5jfnhrz6fa
          schema:
            type: string
            pattern: ^loc_[A-Za-z0-9]+$
        - name: status
          in: query
          description: >-
            Filter by the customer's card pass-install status. Matches the
            `status` field on the customer's `card` object in the response.
          required: false
          example: installed
          schema:
            type: string
            enum:
              - installed
              - not_installed
              - deleted
              - disputed
        - name: device
          in: query
          description: >-
            Filter by which wallet the customer's pass is installed on. Matches
            the `device` field on the customer's `card` object in the response.
          required: false
          example: apple_wallet
          schema:
            type: string
            enum:
              - apple_wallet
              - google_wallet
      responses:
        '200':
          description: The customer object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerList'
              examples:
                First page:
                  summary: First page
                  value:
                    object: list
                    url: /v1/customers
                    has_more: true
                    data:
                      - id: cus_b8c2e0a4f59d4a73a1e9
                        object: customer
                        external_id: null
                        email: anna.smith@example.com
                        phone: null
                        first_name: null
                        last_name: null
                        language: sk
                        gdpr_redacted_at: null
                        card_id: crd_sophcrprtvpmmnwxah2lomnkee
                        card_type: stamp
                        card:
                          card_id: crd_sophcrprtvpmmnwxah2lomnkee
                          card_type: stamp
                          status: installed
                          total_visits: 0
                          first_transaction_at: null
                          last_transaction_at: null
                          created_at: 1782604800
                          updated_at: 1782604800
                          remaining_stamps: 10
                          total_stamps: 10
                          stamps_pending: 0
                        status: not_installed
                        device: null
                        installed_at: null
                        claim_url: >-
                          https://vexioncards.one/card/add/2ea6adef9d5ba7a4d358ad46
                        custom: {}
                        metadata: {}
                        referral_stats: null
                        created_at: 1782604800
                        updated_at: 1782604800
                        last_visited_at: null
                        last_visited_location_id: null
                        deleted_at: null
                      - id: cus_yovo5wk634htfojgkzdpmzvu5a
                        object: customer
                        external_id: crm-customer-7421
                        email: thomas.wright@example.com
                        first_name: Tomáš
                        last_name: Varga
                        language: sk
                        gdpr_redacted_at: null
                        card_id: crd_2hh2fekk5zazfyyvian4uhepyq
                        card_type: reward
                        card:
                          card_id: crd_2hh2fekk5zazfyyvian4uhepyq
                          card_type: reward
                          status: installed
                          total_visits: 12
                          first_transaction_at: 1779580800
                          last_transaction_at: 1782345600
                          created_at: 1779494400
                          updated_at: 1782345600
                          points: 480
                          points_pending: 0
                          reward_tier:
                            tier_name: Silver
                            earn_multiplier: 1.5
                            tier_effective_date: 1781481600
                            previous_tier_name: Bronze
                        status: installed
                        device: apple_wallet
                        installed_at: 1779494400
                        claim_url: >-
                          https://vexioncards.one/card/add/afbd9cbc8b9ab72f43bf6ffc
                        custom: {}
                        metadata: {}
                        referral_stats: null
                        created_at: 1779494400
                        updated_at: 1782345600
                        last_visited_at: 1782345600
                        last_visited_location_id: null
                        deleted_at: null
                Serial number lookup:
                  summary: >-
                    Serial number lookup (GET
                    /v1/customers?serial_number=6ac53be9fe06508914775125)
                  value:
                    object: list
                    url: /v1/customers
                    has_more: false
                    data:
                      - id: cus_b8c2e0a4f59d4a73a1e9
                        object: customer
                        external_id: null
                        email: anna.smith@example.com
                        phone: null
                        first_name: null
                        last_name: null
                        language: sk
                        gdpr_redacted_at: null
                        card_id: crd_sophcrprtvpmmnwxah2lomnkee
                        card_type: stamp
                        card:
                          card_id: crd_sophcrprtvpmmnwxah2lomnkee
                          card_type: stamp
                          status: installed
                          total_visits: 0
                          first_transaction_at: null
                          last_transaction_at: null
                          created_at: 1782604800
                          updated_at: 1782604800
                          remaining_stamps: 10
                          total_stamps: 10
                          stamps_pending: 0
                        status: not_installed
                        device: null
                        installed_at: null
                        claim_url: >-
                          https://vexioncards.one/card/add/6ac53be9fe06508914775125
                        custom: {}
                        metadata: {}
                        referral_stats: null
                        created_at: 1782604800
                        updated_at: 1782604800
                        last_visited_at: null
                        last_visited_location_id: null
                        deleted_at: null
                        serial_number: 6ac53be9fe06508914775125
                        serial_number_locked: false
                Empty list:
                  summary: Empty list
                  value:
                    object: list
                    url: /v1/customers
                    has_more: false
                    data: []
          headers: {}
        '400':
          description: Malformed body or missing required field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Invalid cursor:
                  summary: Invalid cursor
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_cursor
                      message: >-
                        starting_after must be the id of a customer within this
                        organization. Got 'cus_invalid_xyz'.
                      param: starting_after
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_cursor
                      request_id: req_01jBhZA1b2C3D4E5F6G7H8I9J0
                Limit out of range:
                  summary: Limit out of range
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_limit
                      message: limit must be between 1 and 100. Got 250.
                      param: limit
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_limit
                      request_id: req_01jBhZBL3K4M5N6P7Q8R9S0T1U
          headers: {}
        '401':
          description: Missing, invalid, revoked, or paused API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: authentication_error
                  code: invalid_api_key
                  message: >-
                    The provided API key is invalid. Check for typos and that
                    you copied the full key value.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#invalid_api_key
                  request_id: req_01jBhZBV4L5M6N7P8Q9R0S1T2V
              examples:
                Invalid API key:
                  summary: Invalid API key
                  value:
                    error:
                      type: authentication_error
                      code: invalid_api_key
                      message: >-
                        The provided API key is invalid. Check for typos and
                        that you copied the full key value.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_api_key
                      request_id: req_01jBhZBV4L5M6N7P8Q9R0S1T2V
                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_ZXZtaYeHPr95YA4yRj2ffJPrYy
                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_sDbS6ERkYKTqnd0GWHAqqdB24c
          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:read scope.
                    Required: customers:read.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                  request_id: req_01jBhZC65M6N7P8Q9R0S1T2V3W
              examples:
                Insufficient scope:
                  summary: Insufficient scope
                  value:
                    error:
                      type: permission_error
                      code: insufficient_scope
                      message: >-
                        This API key does not have the customers:read scope.
                        Required: customers:read.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#insufficient_scope
                      request_id: req_01jBhZC65M6N7P8Q9R0S1T2V3W
          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: >-
                    Lookup by email is limited to 5 requests/sec/organization.
                    Retry after 1 second.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#rate_limited
                  request_id: req_01jBhZCH6N7P8Q9R0S1T2V3W4X
              examples:
                Lookup rate limited:
                  summary: Lookup rate limited
                  value:
                    error:
                      type: rate_limit_error
                      code: rate_limited
                      message: >-
                        Lookup by email is limited to 5
                        requests/sec/organization. Retry after 1 second.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#rate_limited
                      request_id: req_01jBhZCH6N7P8Q9R0S1T2V3W4X
          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: {}
      deprecated: false
components:
  schemas:
    CustomerList:
      type: object
      description: Cursor-paginated list of Customer objects.
      required:
        - object
        - data
        - has_more
      properties:
        object:
          type: string
          enum:
            - list
          description: Always 'list'. Type discriminator.
        data:
          type: array
          description: >-
            Array of Customer objects on this page. Up to `limit` items (default
            25, max 100).
          items:
            $ref: '#/components/schemas/Customer'
        has_more:
          type: boolean
          description: True if more results exist beyond this page.
        next_cursor:
          type:
            - string
            - 'null'
          description: >-
            Cursor for the next page **in the direction you are already
            paging**, or `null` when this page ends the collection. `has_more:
            true` always comes with a non-null `next_cursor`.


            Feed it back to the SAME parameter you used: `starting_after` when
            paging forward (the default), or `ending_before` when paging
            backward. Treat it as opaque and do not parse it.


            You no longer need to take the last object's `id` yourself. That
            still works and is unchanged, but deriving the cursor from the page
            put the burden of knowing the sort direction on you, and it broke
            silently on any endpoint whose cursor is not the object id.
          example: cus_iofv5j4hn6bphinmmyat27tc5y
        url:
          type: string
          description: Stable collection URL.
    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.
    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'
    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.