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

# Retrieve a transaction

> Returns a single ledger entry by id.

Reversal navigation: on a `kind: "reversal"` row, follow `reverses.id` to the original (its type is inlined as `reverses.type` so you can branch without the second GET). On an original with `reversed: true`, follow `reversed_by`.



## OpenAPI

````yaml /openapi.json get /v1/transactions/{id}
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/transactions/{id}:
    get:
      tags:
        - Transactions
      summary: Retrieve a transaction
      description: >-
        Returns a single ledger entry by id.


        Reversal navigation: on a `kind: "reversal"` row, follow `reverses.id`
        to the original (its type is inlined as `reverses.type` so you can
        branch without the second GET). On an original with `reversed: true`,
        follow `reversed_by`.
      operationId: getTransaction
      parameters:
        - name: id
          in: path
          required: true
          description: Transaction identifier (`txn_` prefix).
          example: txn_01HW9R5Q3AB7N8MTX1RVTXNDEF
          schema:
            type: string
            pattern: ^txn_[A-Za-z0-9]+$
      responses:
        '200':
          description: Transaction record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
              examples:
                Original entry:
                  summary: 'points.earn (source: api)'
                  value:
                    id: txn_01HW9R5Q3AB7N8MTX1RVTXNDEF
                    object: transaction
                    kind: normal
                    type: points.earn
                    unit: points
                    card_id: crd_01HW9R4P2ZK7N8MTX1RVCARD01
                    card_type: reward
                    customer_id: cus_01HW9R4P2ZK7N8MTX1RVABCDEF
                    location_id: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                    team_member_id: null
                    action: award
                    value: 18
                    amount_minor: 1250
                    eligible_spend_minor: null
                    saving_minor: null
                    currency: EUR
                    balance_after:
                      points: 124
                    tier_at_earning: silver
                    applied_multiplier: 1.5
                    source: api
                    external_id: demo-receipt-abc-123
                    reversed: false
                    reverses: null
                    reversed_by: null
                    reversed_at: null
                    products:
                      - name: Flat White
                        sku: FW-12
                        quantity: 1
                        amount_minor: 350
                        currency: EUR
                        category: drinks
                      - name: Cinnamon Roll
                        sku: CR-04
                        quantity: 1
                        amount_minor: 900
                        currency: EUR
                        category: pastry
                    metadata:
                      till_id: till-3
                    created_at: 1750691210
                    occurred_at: 1750691210
                POS-originated entry (read-only):
                  summary: stamp.add pushed by a POS bridge
                  value:
                    id: txn_01HXR9JNEW00000000000000A2
                    object: transaction
                    kind: normal
                    type: stamp.add
                    unit: stamps
                    card_id: crd_01HW9R4P2ZK7N8MTX1RVCARD02
                    card_type: stamp
                    customer_id: cus_01HW9R4P2ZK7N8MTX1RVABCDEF
                    location_id: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                    team_member_id: null
                    action: award
                    value: 1
                    amount_minor: 350
                    eligible_spend_minor: null
                    saving_minor: null
                    currency: EUR
                    balance_after:
                      stamps_collected: 7
                    tier_at_earning: null
                    applied_multiplier: null
                    source: pos
                    external_id: null
                    reversed: false
                    reverses: null
                    reversed_by: null
                    reversed_at: null
                    products: []
                    metadata: {}
                    created_at: 1750691100
                    occurred_at: 1750691100
                Reversal row:
                  summary: kind=reversal with reverses pointer
                  value:
                    id: txn_01HW9R6P5BCDEFGHJKMNPQRSTV
                    object: transaction
                    kind: reversal
                    type: reversal
                    unit: points
                    card_id: crd_01HW9R4P2ZK7N8MTX1RVCARD01
                    card_type: reward
                    customer_id: cus_01HW9R4P2ZK7N8MTX1RVABCDEF
                    location_id: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                    team_member_id: null
                    action: redeem
                    value: 18
                    amount_minor: null
                    eligible_spend_minor: null
                    saving_minor: null
                    currency: EUR
                    balance_after:
                      points: 106
                    tier_at_earning: silver
                    applied_multiplier: 1.5
                    source: api
                    external_id: null
                    reversed: false
                    reverses:
                      id: txn_01HW9R5Q3AB7N8MTX1RVTXNDEF
                      type: points.earn
                    reversed_by: null
                    reversed_at: null
                    products: []
                    metadata:
                      reversal_reason: wrong amount entered at till
                    created_at: 1750695000
                    occurred_at: 1750695000
          headers: {}
        '400':
          description: Malformed query parameter, invalid filter value, or invalid header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '401':
          description: Missing, invalid, or revoked API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '403':
          description: API key lacks the required scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Transaction not found in your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '429':
          description: Per-minute or per-day rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      deprecated: false
      x-codeSamples:
        - lang: curl
          label: cURL
          source: >-
            curl
            https://api.vexioncards.one/v1/transactions/txn_01HW9R5Q3AB7N8MTX1RVTXNDEF
            \
              -H "Authorization: Bearer $VXN_API_KEY"
        - lang: javascript
          label: Node.js
          source: |-
            const txn = await (await fetch(
              'https://api.vexioncards.one/v1/transactions/txn_01HW9R5Q3AB7N8MTX1RVTXNDEF',
              { headers: { Authorization: `Bearer ${process.env.VXN_API_KEY}` } }
            )).json();
        - lang: python
          label: Python
          source: |-
            import os, requests
            txn = requests.get(
                "https://api.vexioncards.one/v1/transactions/txn_01HW9R5Q3AB7N8MTX1RVTXNDEF",
                headers={"Authorization": f"Bearer {os.environ['VXN_API_KEY']}"},
            ).json()
components:
  schemas:
    Transaction:
      type: object
      description: >-
        One immutable ledger entry. Transactions are the source of truth for
        every balance - the server computes `balance_after`, `type`, and `unit`;
        clients only ever describe the real-world action.


        **Reversal model:** mistakes are never edited - post a reversal (POST
        /v1/transactions/{id}/reverse). The reversal is itself a transaction
        (`kind: "reversal"`) whose effect exactly negates the original; both
        rows stay in the ledger forever. On a reversal, `action` describes what
        is being undone (the original action), not a new customer action -
        detect reversals via `kind`/`type`, never by parsing `action`.
      required:
        - id
        - object
        - kind
        - type
        - unit
        - card_id
        - card_type
        - customer_id
        - action
        - value
        - currency
        - source
        - reversed
        - created_at
        - occurred_at
      properties:
        id:
          type: string
          pattern: ^txn_[A-Za-z0-9]+$
          description: Opaque ledger identifier, `txn_` prefix. Stable forever.
          example: txn_01HW9R5Q3AB7N8MTX1RVTXNDEF
        object:
          type: string
          const: transaction
          description: Always `"transaction"`.
        kind:
          $ref: '#/components/schemas/TransactionKind'
        type:
          $ref: '#/components/schemas/TransactionType'
        unit:
          $ref: '#/components/schemas/TransactionUnit'
        card_id:
          type: string
          pattern: ^crd_[A-Za-z0-9]+$
          description: >-
            The card template this transaction targets. One transaction touches
            one card.
          example: crd_01HW9R4P2ZK7N8MTX1RVCARD01
        card_type:
          type:
            - string
            - 'null'
          enum:
            - stamp
            - reward
            - discount
            - coupon
            - membership
            - gift_card
            - custom
            - null
          description: >-
            Server-set, denormalized from the card. Cashback is NOT a separate
            type - cashback transactions target a `reward` card in cashback
            redemption mode. POST /v1/transactions accepts stamp, reward,
            discount and coupon cards; every other card type returns 422
            `card_type_not_writable`. NULL when the card this transaction
            belongs to has been HARD-deleted: card_type is denormalized from the
            Card document and nothing on the transaction row preserves it, so it
            is unrecoverable once the card is gone. `card_id` is still returned
            for audit, but GET /v1/cards/{id} for it will 404. Every other field
            on the row stays valid.
          example: reward
        customer_id:
          type: string
          pattern: ^cus_[A-Za-z0-9]+$
          description: The customer this entry credits or debits.
          example: cus_01HW9R4P2ZK7N8MTX1RVABCDEF
        location_id:
          type:
            - string
            - 'null'
          pattern: ^loc_[A-Za-z0-9]+$
          description: Location attribution. Optional.
          example: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
        team_member_id:
          type:
            - string
            - 'null'
          pattern: ^tm_[A-Za-z0-9]+$
          description: >-
            The team member (scanner staff) who recorded this entry. **Populated
            only on `source: "manual"` transactions** - the cashier scanner runs
            under a signed-in team member. Null everywhere else: `api` entries
            are attributed to the API key, `pos` entries to the till (the POS
            does not identify the operating person), `online`/`system` have no
            human operator.
          example: null
        action:
          $ref: '#/components/schemas/TransactionAction'
        value:
          type: number
          description: >-
            The units the action carried, paired with `unit`: stamps
            added/redeemed, points granted/consumed, points on a cashback card
            too (cashback is a presentation mode over the same points ledger -
            the money view is on `saving_minor` and
            `balance_after.cashback_minor`, never here), or gift-card minor
            units to load/spend. Whole for `source: api` rows. Rows from the
            staff scanner (`manual`) and from a POS (`pos`) can carry up to 4
            decimals on any Reward card, flat or tiered: for example 10 EUR at
            0.5 points per EUR on a 1.5x level yields exactly 7.5 on a scanner
            row. Treat this as a decimal.
          example: 18
        amount_minor:
          type:
            - integer
            - 'null'
          description: >-
            The bill total in minor units (cents) that drove the action.
            Required for `award` on spend-driven cards (reward-by-spend,
            cashback, discount); optional otherwise.
          example: 1250
        eligible_spend_minor:
          type:
            - integer
            - 'null'
          description: >-
            Only set on `type: "employee_discount.apply"` and on its reversal
            (`reverses.type === "employee_discount.apply"`) - a reversal's own
            `type` is always `"reversal"`, never the original type. Sum of
            loyalty-enabled line items (with VAT) so reversal math reconciles to
            the exact budget increment applied. Never accepted on input. It is
            null on `source: api` rows.
          example: null
        saving_minor:
          type:
            - integer
            - 'null'
          description: >-
            Server-computed monetary benefit the customer received on this bill,
            in minor units - the discount granted (`discount.apply`,
            `employee_discount.apply`), the credit used (`cashback.redeem`) or a
            referral bonus row. Null on earns and on every row with no money
            value, including `points.redeem` and `coupon.redeem`. Sum this per
            period for 'total value given' reporting. Never accepted on input.
          example: null
        currency:
          type: string
          minLength: 3
          maxLength: 3
          description: ISO 4217. Defaults to the organization's default currency.
          example: EUR
        balance_after:
          $ref: '#/components/schemas/BalanceAfter'
        tier_at_earning:
          type:
            - string
            - 'null'
          description: >-
            For Reward cards with levels: the name of the level (its display
            name, for example `Silver`) active when this entry was recorded, not
            a slug. Null on discount rows and on rows from some POS channels,
            where `applied_multiplier` is still set. Round-trips verbatim onto
            the reversal row so refund math uses the ORIGINAL tier, not the
            customer's current one.
          example: silver
        applied_multiplier:
          type:
            - number
            - 'null'
          description: >-
            For tiered reward cards: the earn multiplier in effect. Round-trips
            verbatim to the reversal row.
          example: 1.5
        source:
          $ref: '#/components/schemas/TransactionSource'
        external_id:
          type:
            - string
            - 'null'
          maxLength: 255
          description: >-
            Your correlation reference (order id, receipt number). Indexed for
            list filtering. (Public name for the platform's internal receipt
            reference.)
          example: pos-receipt-abc-123
        reversed:
          type: boolean
          description: True once a reversal is posted against this entry.
          example: false
        reverses:
          type:
            - object
            - 'null'
          description: >-
            On reversal rows only: `{id, type}` of the original - lets receivers
            branch on the reversed type without a second GET. Null on originals.
          properties:
            id:
              type: string
              description: The original `txn_` id.
            type:
              $ref: '#/components/schemas/TransactionType'
          example: null
        reversed_by:
          type:
            - string
            - 'null'
          description: >-
            The `txn_` of the reversal that voids this row. Null while
            un-reversed.
          example: null
        reversed_at:
          type:
            - integer
            - 'null'
          description: Unix seconds the reversal was posted.
          example: null
        products:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/TransactionProduct'
          description: >-
            Optional line items. Stored verbatim, emitted on webhooks, never
            interpreted by the loyalty engine. Use `sku` for product
            correlation.
          example:
            - name: Espresso doppio
              sku: ESP-D
              quantity: 1
              amount_minor: 1250
              currency: EUR
              category: drinks
        metadata:
          type: object
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 500
          description: >-
            Your free-form key/value bag. NOT renderable in pass/notification
            templates (unlike `customer.custom`). Shape IS enforced (422
            `validation_failed`): max 50 keys, key must match
            `^[a-zA-Z0-9_]{1,40}$`, values must be strings of at most 500 chars.
            The platform never interprets the contents. The only server-set key
            is `metadata.reversal_reason` on reversal rows.
          example:
            till_id: till-3
          maxProperties: 50
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,40}$
        created_at:
          type: integer
          description: Unix seconds, server-assigned.
          example: 1750691210
        occurred_at:
          type: integer
          description: >-
            Unix seconds when the event happened at its origin (till time for
            POS entries, request time for API entries). Usually equals
            `created_at`; differs when a POS webhook or batch import arrives
            late. Range filters (`created[gte|lte]`) and sort use `created_at` -
            use `occurred_at` for business-day reconciliation.
          example: 1750691210
    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.
    TransactionKind:
      type: string
      enum:
        - normal
        - reversal
      description: >-
        Server-set. `normal` = a regular ledger entry. `reversal` = a
        compensating row created by POST .../reverse, carrying `reverses: {id,
        type}`. Tombstones exist internally but never surface in the API.
      example: normal
    TransactionType:
      type: string
      enum:
        - card.issue
        - card.link
        - card.unlink
        - cashback.earn
        - cashback.redeem
        - coupon.issue
        - coupon.redeem
        - discount.apply
        - discount.tier_change
        - employee_discount.apply
        - gift_card.create
        - gift_card.refund
        - gift_card.spend
        - gift_card.topup
        - import
        - manual_adjust
        - membership.activate
        - membership.expire
        - membership.renew
        - membership.use
        - points.earn
        - points.manual_adjust
        - points.redeem
        - purchase
        - reversal
        - stamp.add
        - stamp.bonus
        - stamp.redeem
        - tier.change
      description: >-
        Server-set stable public classification (29 values). Partners building
        dashboards or per-event handlers should switch on `type`, never parse
        `action`.


        **You never send `type`** - the server derives it from `action` × the
        card's type (see the mapping table on POST /v1/transactions).


        | Family | Types |

        |---|---|

        | Generic | `purchase` (bill without loyalty action), `manual_adjust`,
        `reversal` (original type under `reverses.type`) |

        | Stamp | `stamp.add`, `stamp.redeem`, `stamp.bonus` |

        | Points | `points.earn`, `points.redeem`, `points.manual_adjust`* |

        | Cashback | `cashback.earn`, `cashback.redeem` |

        | Discount | `discount.apply`, `discount.tier_change`*,
        `employee_discount.apply` (carries `eligible_spend_minor`) |

        | Coupon | `coupon.issue`, `coupon.redeem` |

        | Gift card | `gift_card.create`, `gift_card.topup`, `gift_card.spend`,
        `gift_card.refund` |

        | Membership | `membership.activate`, `membership.use`,
        `membership.renew`, `membership.expire`* |

        | Lifecycle* | `tier.change`, `card.issue`, `card.link`, `card.unlink`,
        `import` |


        \* = **server-emitted only** - can never be produced via POST. Where one
        of them appears at all, it is in list results and webhooks; the seven
        types named below never appear.


        Namespace note: transaction types use verb stems (`points.earn`);
        webhook event names use past participles (`transaction.created`).
        Subscribe to the webhook name, branch on the inner `type`.


        **Declared but NOT emitted by v1 today.** These seven are reserved in
        the enum for forward compatibility and no current code path produces
        them, so a `switch` branch on any of them is dead code until this note
        is removed: `card.issue`, `card.link`, `card.unlink`, `coupon.issue`,
        `points.manual_adjust`, `stamp.bonus`, `tier.change`.


        `type` is DERIVED per row from the underlying event and its deltas,
        never stored, so the set of producible values is fixed by the classifier
        rather than by data. `coupon.issue` is structurally impossible rather
        than merely unimplemented: coupon issuance is not a ledger row, so every
        coupon transaction is a redemption.
      example: points.earn
    TransactionUnit:
      type: string
      enum:
        - stamps
        - points
        - gift_card_minor
        - membership_uses
        - none
      description: >-
        Server-set. Names what the integer in `value` carries, so partners never
        guess: `stamps`, `points` (cashback rides the same points ledger as any
        other Reward card - see the Transaction `value` description; the money
        view lives on `saving_minor` and `balance_after.cashback_minor`, never
        on `unit`/`value`), `gift_card_minor` (cents), `membership_uses`, or
        `none` (for example `purchase`, `discount.apply`,
        `employee_discount.apply`, `coupon.redeem`, `discount.tier_change` and
        `import` rows).
      example: points
    TransactionAction:
      type: string
      enum:
        - award
        - redeem
      description: >-
        `award` adds stamps or points, `redeem` uses them up. On a reversal
        (`kind: "reversal"`), `action` describes what is being undone (the
        original action), not a new customer action; detect reversals via
        `kind`/`type`, never by parsing `action`.
    BalanceAfter:
      type:
        - object
        - 'null'
      description: Server-authoritative balance snapshot. Shape varies by card_type.
      properties:
        stamps_collected:
          type: integer
          description: STAMP cards only - current stamp count after this entry.
        points:
          type: number
          description: >-
            REWARD cards only - point balance after this entry. Can carry up to
            4 decimals after `manual` or `pos` entries, as for
            Transaction.value. Treat it as a decimal.
        remaining_amount_minor:
          type: integer
          description: GIFT cards only - remaining balance in minor units.
        cashback_minor:
          type: integer
          description: >-
            CASHBACK-mode reward cards only - cashback balance in minor units.
            Computed with the card's current point value, so older rows change
            if the value changes.
      example:
        points: 124
    TransactionSource:
      type: string
      description: >-
        Where the ledger entry originated. **Server-stamped - never accepted on
        input.** Every transaction you create via `POST /v1/transactions` is
        stamped `"api"`, no exceptions.


        Values you will see on reads:


        - `api` - created through the Public API by you or your developer

        - `pos` - pushed by a connected POS bridge after a real-world bill (the
        specific POS vendor is never surfaced)

        - `manual` - recorded by staff through the VEXiON cashier scanner. A
        scanner undo appears as an opposite entry, not as a reversal

        - `online` - originated from a first-class online checkout integration

        - `system` - generated by VEXiON itself (tier demotion, expiry sweeps,
        referral payouts, transform reconciliation, import audit rows)


        Treat this as an **open set** - new origin values may be added without a
        version bump. Code defensively: branch on the values you know, pass
        unknown ones through.


        Entries with any source other than `api` are **read-only through this
        API**: you can list and retrieve them, but you cannot create or reverse
        them (corrections happen in their originating channel - POS cancellation
        flow, scanner undo, or support).
      example: api
    TransactionProduct:
      type: object
      description: >-
        A line item on the bill. Stored verbatim and emitted in the
        `transaction.created` webhook - the loyalty engine does NOT interpret
        line items (rules act on the aggregate `amount_minor`); they exist for
        per-product reporting and basket analytics.


        **Product correlation, in resolution order:** (1) an explicit
        `product_id` you supply, validated against the transaction's location
        catalog; (2) `sku` matched against the catalog; (3) POS `pos_ref`
        matching on POS-originated entries. Unresolved lines stay free-form
        (`product_id: null`) and back-link when the catalog entry appears.


        Up to 50 line items per transaction. Line totals are NOT required to sum
        to the transaction's `amount_minor` (taxes, service fees, rounding are
        accepted silently).
      required:
        - name
      properties:
        product_id:
          type:
            - string
            - 'null'
          pattern: ^prd_[A-Za-z0-9]+$
          description: >-
            Reference into the location's product catalog. Optional on input
            (must belong to the transaction's location - `422 product_not_found`
            otherwise); on reads it is resolver-stamped from `sku` / POS mapping
            when you omit it. Null when unresolved.
          example: prd_01HW9R7T2CD8N8MTX1RVPRDFW1
        name:
          type: string
          minLength: 1
          maxLength: 200
          description: Display name of the line item. The only required field.
          example: Flat White
        sku:
          type: string
          minLength: 1
          maxLength: 64
          description: >-
            Your stable product identifier - the correlation key for mapping
            this line to your product catalog. Strongly recommended on every
            line.
          example: FW-12
        quantity:
          type: integer
          minimum: 1
          default: 1
          description: >-
            Units purchased. Capped at 100,000 units per line item to prevent
            overflow abuse.
          example: 1
          maximum: 100000
        amount_minor:
          type: integer
          description: Line total in minor units (cents). Maximum 100,000,000,000.
          example: 350
          minimum: 0
          maximum: 100000000000
        currency:
          type: string
          minLength: 3
          maxLength: 3
          description: ISO 4217. Defaults to the transaction's currency.
          example: EUR
        category:
          type: string
          minLength: 1
          maxLength: 100
          description: Free-form category for basket analytics.
          example: drinks
        metadata:
          type: object
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 500
          description: >-
            Free-form key/value bag, not renderable in templates. Shape IS
            enforced (422 `validation_failed`): max 50 keys, key must match
            `^[a-zA-Z0-9_]{1,40}$`, values must be strings of at most 500 chars.
            The platform never interprets the contents.
          example:
            pos_line_id: e9dbedb5
          maxProperties: 50
          propertyNames:
            pattern: ^[a-zA-Z0-9_]{1,40}$
      example:
        name: Flat White
        sku: FW-12
        quantity: 1
        amount_minor: 350
        currency: EUR
        category: drinks
  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.