> ## 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 custom fields

> Lists your reusable custom-field definitions, cursor-paginated, with live usage bookkeeping on every row: `usage_count` and `cards_using[]` show exactly which card templates reference each field - the numbers that decide which edits are allowed (renames, type changes, and option removals are blocked while a field is in use). Scope: `custom_fields:read`.

Filter by `field_type`.



## OpenAPI

````yaml /openapi.json get /v1/custom_fields
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/custom_fields:
    get:
      tags:
        - Custom fields
      summary: List custom fields
      description: >-
        Lists your reusable custom-field definitions, cursor-paginated, with
        live usage bookkeeping on every row: `usage_count` and `cards_using[]`
        show exactly which card templates reference each field - the numbers
        that decide which edits are allowed (renames, type changes, and option
        removals are blocked while a field is in use). Scope:
        `custom_fields:read`.


        Filter by `field_type`.
      operationId: listCustomFields
      parameters:
        - name: limit
          in: query
          required: false
          description: Page size, 1-100. Default 25.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: starting_after
          in: query
          description: >-
            Cursor: object id from the previous page (forward walk). Numeric
            offset or page parameters are not supported; cursors are the only
            supported paging mechanism.
          required: false
          schema:
            type: string
        - name: ending_before
          in: query
          description: 'Cursor: object id from the previous page (reverse walk).'
          required: false
          schema:
            type: string
        - name: field_type
          in: query
          description: >-
            Filter by field type (the 8-type registry - see the Custom Field
            object).
          required: false
          schema:
            type: string
            enum:
              - text
              - textarea
              - number
              - currency
              - date
              - boolean
              - select
              - multiselect
      responses:
        '200':
          description: Paginated custom field list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomFieldList'
              examples:
                Fields:
                  summary: One in-use select field
                  value:
                    object: list
                    data:
                      - id: cf_01HW9R9Z4EF7N8MTX1RVCFLD01
                        object: custom_field
                        key: department
                        description: Clinic department the patient is assigned to.
                        field_type: select
                        position: 2
                        options:
                          - value: cardio
                            label: Cardiology
                          - value: neuro
                            label: Neurology
                          - value: ortho
                            label: Orthopedics
                        date_granularity: date
                        usage_count: 3
                        cards_using:
                          - crd_l47u555jitul2m63lryijy4wya
                          - crd_eyua5rb6gzr3jtydy3fgbhcgpy
                          - crd_pkmywxjb63cng2aupvzyymxuzm
                        created_at: 1750000000
                        updated_at: 1750000000
                        label:
                          sk: Oddelenie
                          en: Department
                    has_more: false
                    url: /v1/custom_fields
          headers: {}
        '400':
          description: >-
            Invalid pagination parameter: limit outside 1-100, or a malformed
            cursor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Invalid limit:
                  summary: limit out of range
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_limit
                      message: limit must be an integer between 1 and 100.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_limit
                      request_id: req_5f8b2c4a9e1d3
                      param: limit
                Malformed cursor:
                  summary: starting_after is not a valid cursor
                  value:
                    error:
                      type: invalid_request_error
                      code: invalid_cursor
                      message: starting_after is not a valid cf_ cursor.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#invalid_cursor
                      request_id: req_5f8b2c4a9e1d3
                      param: starting_after
          headers: {}
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '429':
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          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:
    CustomFieldList:
      allOf:
        - $ref: '#/components/schemas/ListEnvelope'
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/CustomField'
            url:
              type: string
              const: /v1/custom_fields
    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.
    ListEnvelope:
      type: object
      required:
        - object
        - data
        - has_more
      properties:
        object:
          type: string
          const: list
          description: Always `"list"`.
        data:
          type: array
          items: {}
          description: Array of the requested resource type.
        has_more:
          type: boolean
          description: >-
            True when more pages exist - pass the last item id as
            `starting_after`.
        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.
      description: >-
        Standard cursor-pagination envelope for list endpoints. See the
        Pagination guide.
    CustomField:
      type: object
      description: >-
        A reusable custom field DEFINITION - the single vocabulary used across
        the whole platform: the signup form (via each card's `form_builder`),
        the customer record (`Customer.custom.<key>`), wallet pass slots
        (`{{custom.<key>}}` in `Card.pass_fields`), and notification templates.
        Define once, reference everywhere.


        The definition owns IDENTITY (key, type, options, granularity) and
        DEFAULTS (label translations, required, position); each card's form owns
        USAGE (which fields, per-card required/order/label overrides). Limits:
        50 definitions per organization (`409 custom_field_limit_reached`), 20
        fields per card form.
      required:
        - id
        - object
        - key
        - label
        - field_type
        - created_at
      properties:
        id:
          type: string
          pattern: ^cf_[A-Za-z0-9]+$
          description: Opaque, stable, immutable. `cf_` prefix.
          example: cf_01HW9R9Z4EF7N8MTX1RVCFLD01
        object:
          type: string
          const: custom_field
          description: Always `"custom_field"`.
        key:
          type: string
          pattern: ^[a-z][a-z0-9_]{1,49}$
          description: >-
            Stable machine identifier, unique within the organization (2-50
            chars, lowercase snake). Used in `{{custom.<key>}}` templates and
            Customer `custom.<key>` payloads. **Renames are blocked while the
            field is in use** (usage_count > 0) to prevent keys diverging across
            cards.
          example: department
        label:
          anyOf:
            - type: string
              title: Single label (all languages)
              minLength: 1
              maxLength: 200
            - type: object
              title: Per-language labels
              additionalProperties:
                type: string
                minLength: 1
                maxLength: 200
              minProperties: 1
              description: >-
                Keys are language codes (`sk`, `cs`, `en`, `de`, `hu`, `hr`,
                `ro`). On write this collapses immediately to a single stored
                string - `en` if present, otherwise the first language present -
                it is not resolved per customer language at read time. Real
                per-language display lives per CARD: see
                `Card.formBuilder.fields[].translations[]`.
          description: >-
            Customer-facing label - shown on the signup form, referenced on
            wallet passes, and used in the dashboard. Plain string or
            per-language object (the platform localization pattern, same as
            `Card.name`). This is the DEFAULT - each card's `form_builder` can
            override the label per card.
        description:
          type:
            - string
            - 'null'
          maxLength: 500
          description: Internal helper text for the dashboard.
          example: Clinic department the patient is assigned to.
        field_type:
          type: string
          enum:
            - text
            - textarea
            - number
            - currency
            - date
            - boolean
            - select
            - multiselect
          description: >-
            What kind of data the field holds - drives form input, validation,
            and typed pass/notification rendering:


            | type | value shape | pass rendering |

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

            | `text` | string ≤200 | as-is |

            | `textarea` | string ≤2000 | back of pass only |

            | `number` | number (`number_format` controls integer vs decimal) |
            locale-formatted number |

            | `currency` | integer, minor units, organization currency |
            locale-formatted amount (native on Apple via ISO 4217;
            server-formatted on Google) |

            | `date` | string per `date_granularity` (`YYYY` / `YYYY-MM` /
            `YYYY-MM-DD`) | locale-formatted date |

            | `boolean` | true / false | localized Yes/No |

            | `select` | one option `value` | the option's label, customer
            language |

            | `multiselect` | array of option `value`s | comma-joined labels |


            Immutable while the field is used by any card (`409
            custom_field_in_use`).
          example: select
        position:
          type: integer
          description: >-
            DEFAULT form position when added to a card; each card's
            `form_builder` ordering overrides.
          example: 2
        options:
          type: array
          items:
            type: object
            properties:
              value:
                type: string
                description: >-
                  Stored machine value - immutable once any customer has stored
                  it.
              label:
                anyOf:
                  - type: string
                    title: Single label (all languages)
                    minLength: 1
                    maxLength: 100
                  - type: object
                    title: Per-language labels
                    additionalProperties:
                      type: string
                      minLength: 1
                      maxLength: 100
                    minProperties: 1
                    description: >-
                      Keys are language codes (`sk`, `cs`, `en`, `de`, `hu`,
                      `hr`, `ro`). On write this collapses immediately to a
                      single stored string - `en` if present, otherwise the
                      first language present - it is not resolved per customer
                      language at read time. Real per-language display lives per
                      CARD: see `Card.formBuilder.fields[].translations[]`.
                description: >-
                  Display label for the option - localizable; shown on the form
                  and rendered on passes/notifications in the customer's
                  language. Always editable.
          description: >-
            Choices for `select` / `multiselect` (required for those types).
            Each option is `{value, label}` - **labels are always editable**
            (display only); **values are immutable once any customer has stored
            them** and removing a used value is blocked (`409
            custom_field_in_use`). Adding options is always allowed.
          example:
            - value: cardio
              label: Cardiology
            - value: neuro
              label: Neurology
        date_granularity:
          type: string
          enum:
            - year
            - monthYear
            - date
          description: >-
            DATE type only - controls the stored format: `date` → YYYY-MM-DD,
            `monthYear` → YYYY-MM, `year` → YYYY. Forced to `date` for non-date
            types.
          example: date
        usage_count:
          type: integer
          description: >-
            Read-only. Number of cards referencing this field. Renames + type
            changes + deletes are blocked while > 0.
          example: 3
        cards_using:
          type: array
          items:
            type: string
          description: Read-only. The referencing card ids.
          example:
            - crd_sophcrprtvpmmnwxah2lomnkee
        created_at:
          type: integer
          description: Unix seconds.
          example: 1750000000
        updated_at:
          type: integer
          description: Unix seconds.
          example: 1750000000
        number_format:
          type:
            - string
            - 'null'
          enum:
            - integer
            - decimal
            - null
          description: >-
            `number` fields only: `integer` rejects fractional input; `decimal`
            (default) allows it. Null on other types.
  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.