> ## Documentation Index
> Fetch the complete documentation index at: https://docs.feddi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Attach an identity to an open session (multi-credential)

> Attach a customer identity to an open checkout session, upgrading the session from `OPEN` to `IDENTIFIED` and merging the session's `anonymous_session_id` into the resolved customer (a one-way, idempotent merge). This is the session-anchored form of the canonical `POST /v1/partner/identify`. It dispatches on a multi-credential discriminated union (`phone`, `card_fingerprint`, `short_code`, `provider_customer_id`, or dynamic QR) and returns a `CustomerContext`. **It never returns a debit token or payment token**: identity resolution and money movement are strictly separated. To charge, the partner calls the session's payment sub-resource with a fresh dynamic credential. The `card_fingerprint` link is probationary (reversible) until the phone is verified, so a masked-PAN collision never auto-acts on money. Identify never releases a `LOCKED` promotional grant on its own: promo release stays gated on a verified identity plus the claim flow. Skipping identification is not this endpoint; a skip is an explicit, reason-coded, counted event on the session, never a silent default. The returned `CustomerContext` carries `wallet_program_id` and `wallet_id` as first-class fields so a multi-program customer is never ambiguous. An ambiguous identity returns `WALLET_PROGRAM_AMBIGUOUS` listing the candidates, and an unsupported credential returns `CREDENTIAL_TYPE_UNSUPPORTED` listing the supported set (read `GET /capabilities` first). Cross-enterprise identity resolution returns `NOT_FOUND` (no PII leak). Idempotent on the `Idempotency-Key` header: a retry replays the original `CustomerContext`.



## OpenAPI

````yaml /api-reference/openapi.yaml post /checkout/sessions/{id}/identify
openapi: 3.1.0
info:
  title: Feddi Partner API
  version: '2026-06-01'
  description: >-
    Operate a Feddi closed-loop wallet and loyalty program at the point of sale:
    identify customers, read balances, accept wallet payments, run top-ups, and
    reconcile transactions.


    All amounts are integer minor units with an explicit ISO-4217 currency.
    Balances are merchant-held and closed-loop.


    Every response uses a typed envelope (`ok` / `data` / `error` / `meta`) with
    string error codes (never a bare HTTP number) and an idempotency-replay
    flag. Authenticate with the `x-api-key` header; exchange it for a
    short-lived POS terminal JWT for hot-path calls.


    Self-validate against this document (`GET /openapi`) and read `GET
    /capabilities` for the credential types, currencies, and features enabled
    for your integration before assuming any enum.


    Operations are tagged `x-feddi-availability: ga` (stable) or `beta`
    (callable, contract may still change additively); confirm what is enabled
    for your credentials via `GET /capabilities`.


    Sandbox access is granted per partner agreement; request credentials from
    your Feddi contact.
servers:
  - url: https://api.feddi.io/v1/partner
    description: production
  - url: https://api.dev.feddi.io/v1/partner
    description: dev
security:
  - ApiKeyAuth: []
tags:
  - name: platform
    description: >-
      Platform cross-cutting: health, capabilities, openapi self-serve, merchant
      provisioning, settlement, reconciliation.
  - name: checkout_session
    description: >-
      Checkout sessions: every interaction opens a session, then identity and
      basket attach to it, and payment, top-up, and offers run against it.
  - name: auth
    description: >-
      Partner authentication + onboarding: API key lifecycle, POS terminal JWT
      exchange, terminal heartbeat.
  - name: customers
    description: >-
      Customer lookup + identification: resolve identity, cashier-panel
      summaries, preferences, GDPR export/erase.
  - name: enrollment
    description: >-
      Enrollment + signup: OTP enroll, cashback claims, identity/consent,
      customer correction + merge.
  - name: payments
    description: >-
      Payments + redemption: debit wallet (promo-first), balance-check, void,
      refund, QR mint.
  - name: topup
    description: >-
      Wallet top-up: 2-step prepare/confirm, reload-bonus grants, SKU top-up,
      settlement + reconciliation.
  - name: incentives
    description: >-
      Incentives + offers: offer feeds, apply/redeem/release locks, proposals,
      budget envelopes, points, grant clawback.
  - name: transactions
    description: >-
      Transactions + receipts: transaction detail, void, receipts, disputes,
      settlements, reconciliation, exports.
  - name: webhooks
    description: >-
      Webhooks + events: subscriptions, delivery history + retry, event catalog,
      polling fallback.
paths:
  /checkout/sessions/{id}/identify:
    post:
      tags:
        - checkout_session
      summary: Attach an identity to an open session (multi-credential)
      description: >-
        Attach a customer identity to an open checkout session, upgrading the
        session from `OPEN` to `IDENTIFIED` and merging the session's
        `anonymous_session_id` into the resolved customer (a one-way, idempotent
        merge). This is the session-anchored form of the canonical `POST
        /v1/partner/identify`. It dispatches on a multi-credential discriminated
        union (`phone`, `card_fingerprint`, `short_code`,
        `provider_customer_id`, or dynamic QR) and returns a `CustomerContext`.
        **It never returns a debit token or payment token**: identity resolution
        and money movement are strictly separated. To charge, the partner calls
        the session's payment sub-resource with a fresh dynamic credential. The
        `card_fingerprint` link is probationary (reversible) until the phone is
        verified, so a masked-PAN collision never auto-acts on money. Identify
        never releases a `LOCKED` promotional grant on its own: promo release
        stays gated on a verified identity plus the claim flow. Skipping
        identification is not this endpoint; a skip is an explicit,
        reason-coded, counted event on the session, never a silent default. The
        returned `CustomerContext` carries `wallet_program_id` and `wallet_id`
        as first-class fields so a multi-program customer is never ambiguous. An
        ambiguous identity returns `WALLET_PROGRAM_AMBIGUOUS` listing the
        candidates, and an unsupported credential returns
        `CREDENTIAL_TYPE_UNSUPPORTED` listing the supported set (read `GET
        /capabilities` first). Cross-enterprise identity resolution returns
        `NOT_FOUND` (no PII leak). Idempotent on the `Idempotency-Key` header: a
        retry replays the original `CustomerContext`.
      operationId: checkoutSessionIdentify
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - name: id
          in: path
          required: true
          description: >-
            The checkout_session id to attach identity to. Must be OPEN or
            IDENTIFIED.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionIdentifyRequest'
            example:
              meta:
                partner_request_id: 9a01b2c3-0c4a-4f9b-9d1e-1a2b3c4d5e6f
                occurred_at: '2026-06-05T09:02:00Z'
                sent_at: '2026-06-05T09:02:00Z'
                api_version: '2026-06-01'
              context:
                merchant_id: 11111111-1111-1111-1111-111111111111
                branch_id: 22222222-2222-2222-2222-222222222222
                terminal_id: POS-360-0007
                feddi_session_id: 33333333-3333-3333-3333-333333333333
              customer:
                credential_type: phone
                phone: '+97433001122'
              wallet_program_id: null
      responses:
        '200':
          description: >-
            Identity resolved + attached; session is now IDENTIFIED. Returns the
            `CustomerContext` (NEVER a debit token).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/CustomerContext'
              example:
                ok: true
                data:
                  resolution_state: registered
                  wallet_user_id: wu_5512
                  wallet_program_id: wp_77
                  wallet_id: wal_5512
                  identity_trace_id: itr_aa01
                  display_name: Ahmed Al-Rashid
                  badge: Gold
                  balance:
                    actual_minor: 12500
                    promo_available_minor: 500
                    promo_locked_minor: 0
                    pending_topups_minor: 0
                    currency: QAR
                    promo_grants: []
                error: null
                meta:
                  request_id: req_cs_id1
                  idempotency_replayed: false
                  api_version: '2026-06-01'
                  data_completeness_score: 82
                  decision_trace_id: null
        '400':
          description: >-
            `VALIDATION_ERROR` (HTTP 400), malformed body, missing credential
            value for the declared `credential_type`, a domain rule violated, or
            the session is in a non-identifiable state (`CLOSED`/`ABANDONED`;
            the full lifecycle is the `CheckoutSession.status` enum, only an
            `OPEN` session can be identified). `details` carries the field
            errors.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '401':
          description: >-
            `INVALID_API_KEY` (HTTP 401), missing/invalid `x-api-key` or POS
            terminal JWT (expired, malformed, or wrong audience).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '403':
          description: >-
            `FORBIDDEN` (HTTP 403), the key is valid but not authorized for this
            session's merchant/branch scope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '404':
          description: >-
            `NOT_FOUND`, the session does not exist / belongs to another
            integration, OR the identity does not resolve to a customer in this
            enterprise (cross-enterprise lookups resolve here to avoid a PII
            leak; the cashier still gets a register-CTA via the `not_found`
            customer_state, not an error).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '422':
          description: >-
            `CREDENTIAL_TYPE_UNSUPPORTED` (the credential is not enabled for
            this integration; details list the supported set),
            `WALLET_PROGRAM_AMBIGUOUS` (identity resolves to multiple programs;
            details list the candidate `wallet_program_id`s), or
            `IDEMPOTENCY_KEY_REUSED` (same key, different payload). A session in
            a non-identifiable state (`CLOSED`/`ABANDONED`) is a `400`
            `VALIDATION_ERROR`, see that response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
              example:
                ok: false
                data: null
                error:
                  code: CREDENTIAL_TYPE_UNSUPPORTED
                  message: >-
                    Credential type 'card_fingerprint' is not enabled for this
                    integration.
                  details:
                    requested: card_fingerprint
                    supported:
                      - phone
                      - provider_customer_id
                      - short_code
                meta:
                  request_id: req_cs_id_unsup
                  idempotency_replayed: false
                  api_version: '2026-06-01'
      security:
        - PosTerminalJWT: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Partner-generated UUID; the ONE dedup key for all mutating endpoints.
        24h TTL. Same key + same payload -> byte-identical replay; same key +
        different payload -> IDEMPOTENCY_KEY_REUSED (422).
      schema:
        type: string
        format: uuid
  schemas:
    SessionIdentifyRequest:
      type: object
      description: >-
        Body for POST /checkout/sessions/`{id}`/identify. Resolves a customer
        via the multi-credential discriminated union and attaches them to the
        session. Returns a CustomerContext, NEVER a debit token.
      required:
        - context
        - customer
      properties:
        meta:
          $ref: '#/components/schemas/RequestMeta'
        context:
          $ref: '#/components/schemas/RequestContext'
        customer:
          $ref: '#/components/schemas/CustomerCredential'
        wallet_program_id:
          type:
            - string
            - 'null'
          description: >-
            Disambiguate when the identity resolves to multiple wallet programs
            (else WALLET_PROGRAM_AMBIGUOUS).
    ResponseEnvelope:
      type: object
      description: >-
        The standard response envelope that every enveloped endpoint serializes
        through. `ok` is a boolean discriminator: when `ok` is `true`, the typed
        result is carried in `data`; when `ok` is `false`, a typed error object
        is returned instead. The `meta` object is uniform across the entire API
        surface.
      required:
        - ok
        - meta
      properties:
        ok:
          type: boolean
        data:
          type:
            - object
            - 'null'
        error:
          oneOf:
            - $ref: '#/components/schemas/Error'
            - type: 'null'
        meta:
          $ref: '#/components/schemas/Meta'
    CustomerContext:
      type: object
      description: >-
        The identity-resolution result. Returned by `POST /identify` and the
        session-anchored `POST /checkout/sessions/{id}/identify`. This object
        **never contains a debit token, authorization token, or any artifact
        that permits a ledger mutation**: identity resolution is read-only and
        cannot, by itself, move funds. The `resolution_state` field carries the
        resolution outcome (`registered`, `pending_proof`, or `not_found`); an
        ambiguous match returns `WALLET_PROGRAM_AMBIGUOUS` (HTTP 422). The
        `balance` field is `null` when `resolution_state` is `not_found`.
      required:
        - resolution_state
        - identity_trace_id
      properties:
        resolution_state:
          type: string
          enum:
            - registered
            - pending_proof
            - not_found
          description: >-
            `registered`, fully enrolled, balance readable and spendable.
            `pending_proof`, enrolled but not yet identity-verified; balance
            visible, spend may be gated by the POS. `not_found`, no wallet user
            found for this credential under this enterprise (HTTP 200 to
            distinguish from server error).
        wallet_user_id:
          type:
            - string
            - 'null'
          description: >-
            The Feddi `wallet_user_id` (POS-facing identity row). Null when
            `resolution_state` is `not_found`.
          example: wu-aabbccdd-1234
        wallet_program_id:
          type:
            - string
            - 'null'
          description: >-
            First-class `wallet_program_id` binding. Null when
            `resolution_state` is `not_found`. Ambiguity raises HTTP 422
            `WALLET_PROGRAM_AMBIGUOUS` instead of returning this field.
          example: wp-55667788-0001
        wallet_id:
          type:
            - string
            - 'null'
          description: >-
            The canonical `wallet_id` (ledger row). Null when `resolution_state`
            is `not_found` or the customer's wallet has not yet been created (no
            credits yet).
          example: wlt-99001122-ffff
        identity_trace_id:
          type: string
          description: >-
            Opaque trace ID for this identity resolution event. Use for
            correlating downstream audit logs, `decision_trace_id` fields.
          example: idt-trace-xyzabc
        display_name:
          type:
            - string
            - 'null'
          description: >-
            Customer display name (from the customer's identity record). Null
            when unknown.
          example: Ahmed Al-Rashid
        badge:
          type:
            - string
            - 'null'
          description: >-
            Current loyalty tier badge label (e.g. `'Gold'`, `'Silver'`). Null
            when the program has no tiering.
          example: Gold
        balance:
          anyOf:
            - allOf:
                - $ref: '#/components/schemas/BalanceSummary'
              description: >-
                Wallet balance breakdown. Null when `resolution_state` is
                `not_found`.
            - type: 'null'
    Error:
      type: object
      description: >-
        The typed error object returned with every non-2xx response. `code` is a
        string enum (for example `WALLET_PROGRAM_AMBIGUOUS`,
        `CREDENTIAL_TYPE_UNSUPPORTED`, `INSUFFICIENT_FUNDS`,
        `IDEMPOTENCY_KEY_REUSED`, `CURRENCY_NOT_SUPPORTED`), **never a bare HTTP
        status number**. Inspect `code` for programmatic branching, not the HTTP
        status. Some codes echo the valid set in `details` so clients can
        present or reconcile the accepted values: for example
        `CREDENTIAL_TYPE_UNSUPPORTED` lists the supported credential types,
        `CURRENCY_NOT_SUPPORTED` lists the supported ISO-4217 currencies, and
        `WALLET_PROGRAM_AMBIGUOUS` lists the candidate `wallet_program_id`
        values that matched the request.
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - INSUFFICIENT_FUNDS
            - INVALID_API_KEY
            - CREDENTIAL_TYPE_UNSUPPORTED
            - CURRENCY_NOT_SUPPORTED
            - IDEMPOTENCY_KEY_REUSED
            - WALLET_PROGRAM_AMBIGUOUS
            - REQUIRES_DYNAMIC_CREDENTIAL
            - NOT_FOUND
            - FORBIDDEN
            - VALIDATION_ERROR
            - RATE_LIMITED
            - CONFLICT
            - INTERNAL_SERVER_ERROR
        message:
          type: string
        details:
          type: object
          additionalProperties: true
    RequestMeta:
      type: object
      description: >-
        Common request metadata shared across all Partner API domains.
        `partner_request_id` is a correlation identifier only: it appears in
        logs and responses for tracing but is **never used as the deduplication
        key**. Request deduplication is keyed exclusively on the
        `Idempotency-Key` header.
      required:
        - partner_request_id
        - api_version
      properties:
        partner_request_id:
          type: string
          format: uuid
          description: Partner's own correlation id. NOT the idempotency basis.
        occurred_at:
          type: string
          format: date-time
          description: >-
            When the event happened at the POS (RFC3339). Clock-drift signal vs
            sent_at.
        sent_at:
          type: string
          format: date-time
          description: When the partner sent the request (RFC3339).
        api_version:
          type: string
          description: The single API version field.
          example: '2026-06-01'
    RequestContext:
      type: object
      description: >-
        Canonical request context, shared across all partner domains.
        `merchant_id` is key-enforced to a merchant the calling credential is
        authorized for (a platform-scoped credential acts only on its own
        tenants), so it is **never a free-text trust field**: a `merchant_id`
        outside the credential's authority is rejected rather than honored.
      required:
        - merchant_id
      properties:
        merchant_id:
          type: string
          format: uuid
          description: Resolved Feddi tenant (enterprise).
        branch_id:
          type:
            - string
            - 'null'
          format: uuid
        terminal_id:
          type:
            - string
            - 'null'
        cashier_id:
          type:
            - string
            - 'null'
          description: Server-trusted only when signed into the terminal JWT.
        partner_session_id:
          type:
            - string
            - 'null'
          description: The partner's own order/session ref.
        feddi_session_id:
          type:
            - string
            - 'null'
          format: uuid
          description: >-
            The checkout_session this call anchors to. On session sub-resource
            calls it is redundant with the path `{id}` and must match it.
    CustomerCredential:
      type: object
      description: >-
        An identity credential used to resolve a customer, shared across all
        partner domains. The object is discriminated by `credential_type`, and
        the value field matching the declared `credential_type` is REQUIRED
        (enforced by per-type conditional schemas: a credential without its
        matching value field is invalid). The set of supported `credential_type`
        values is enumerated by `GET /capabilities`. New credential types are
        added additively: a new type appears in the `GET /capabilities` set
        without any route change. A `credential_type` that is not in the
        supported set returns `CREDENTIAL_TYPE_UNSUPPORTED`, and the error
        response lists the currently supported set.
      required:
        - credential_type
      properties:
        credential_type:
          type: string
          enum:
            - phone
            - provider_customer_id
            - card_fingerprint
            - short_code
            - qr
          description: >-
            Which credential identifies the customer. phone=Feddi-native;
            provider_customer_id=the POS provider's customer id;
            card_fingerprint=masked-PAN hash (probationary until
            phone-verified); short_code=wallet-minted code; qr=raw QR payload
            scanned from the wallet app. The enabled set is gated by GET
            /capabilities.
        phone:
          type:
            - string
            - 'null'
          description: E.164 phone (when credential_type=phone).
        provider_customer_id:
          type:
            - string
            - 'null'
          description: >-
            The POS provider's customer id (when
            credential_type=provider_customer_id).
        card_fingerprint:
          type:
            - string
            - 'null'
          description: >-
            Masked-PAN hash (when credential_type=card_fingerprint).
            PROBATIONARY identity, reversible until phone-verified; never
            auto-acts on money.
        short_code:
          type:
            - string
            - 'null'
          description: Wallet-minted short code (when credential_type=short_code).
        qr:
          type:
            - string
            - 'null'
          description: >-
            Raw QR payload scanned from the wallet app (when
            credential_type=qr).
      allOf:
        - if:
            required:
              - credential_type
            properties:
              credential_type:
                const: phone
          then:
            required:
              - phone
            properties:
              phone:
                type: string
        - if:
            required:
              - credential_type
            properties:
              credential_type:
                const: provider_customer_id
          then:
            required:
              - provider_customer_id
            properties:
              provider_customer_id:
                type: string
        - if:
            required:
              - credential_type
            properties:
              credential_type:
                const: card_fingerprint
          then:
            required:
              - card_fingerprint
            properties:
              card_fingerprint:
                type: string
        - if:
            required:
              - credential_type
            properties:
              credential_type:
                const: short_code
          then:
            required:
              - short_code
            properties:
              short_code:
                type: string
        - if:
            required:
              - credential_type
            properties:
              credential_type:
                const: qr
          then:
            required:
              - qr
            properties:
              qr:
                type: string
    Meta:
      type: object
      description: >-
        The uniform response metadata block returned on every response.
        `data_completeness_score` is computed per call and reports the
        completeness of the returned data. `decision_trace_id` is present on
        responses that carry decision or insight output and can be used to
        correlate the response with its reasoning. `capabilities` is an
        additive, response-level array of hints advertising features the caller
        may use, and may be extended over time without notice.
      required:
        - request_id
        - api_version
      properties:
        request_id:
          type: string
          description: Feddi-issued correlation id for this response.
        idempotency_replayed:
          type: boolean
          description: True when this response was replayed from the idempotency store.
        api_version:
          type: string
          description: The single API version field.
          example: '2026-06-01'
        data_completeness_score:
          type: integer
          minimum: 0
          maximum: 100
          description: >-
            0-100, computed per call (basket/identity/tax/category/consent
            presence).
        decision_trace_id:
          type:
            - string
            - 'null'
          description: Opaque trace id on intelligence-bearing responses; one per response.
        capabilities:
          type: object
          additionalProperties: true
          description: Additive response-level capability hints.
    BalanceSummary:
      type: object
      description: >-
        Wallet balance split into two distinct money classes: `actual` (real
        money funded by top-ups, a real merchant liability) and `promo`
        (promotional credits, cashback, reload bonus, signup bonus, a SEPARATE,
        lighter class that is contingent, expirable, and promo-first-debited).
        These MUST NOT be grouped under one liability bucket.
      required:
        - actual_minor
        - promo_available_minor
        - promo_locked_minor
        - pending_topups_minor
        - currency
        - promo_grants
      properties:
        actual_minor:
          type: integer
          minimum: 0
          description: >-
            Real money balance in minor currency units (funded by customer
            top-ups). A real merchant deferred-revenue liability.
        promo_available_minor:
          type: integer
          minimum: 0
          description: >-
            Released promotional credits available to spend, in minor units.
            Spent promo-first (FIFO by expiry) before `actual_minor`.
        promo_locked_minor:
          type: integer
          minimum: 0
          description: >-
            Locked promotional credits NOT yet spendable (activation condition,
            wallet signup or first top-up, not yet met). The acquisition
            flywheel: money waiting for the customer to enroll.
        pending_topups_minor:
          type: integer
          minimum: 0
          description: >-
            Sum of top-up value in-flight (prepared but not yet confirmed). Not
            spendable.
        currency:
          type: string
          description: ISO 4217 currency code for all minor-unit fields in this balance.
          example: QAR
        promo_grants:
          type: array
          description: >-
            Per-grant breakdown of promotional credits. Enables the POS to
            display '3 SAR cashback expiring June 30' etc. Sorted FIFO by
            `expires_at` (earliest first, the order they will be spent).
          items:
            type: object
            required:
              - source
              - state
              - remaining_minor
            properties:
              source:
                type: string
                enum:
                  - CASHBACK
                  - RELOAD_BONUS
                  - SKU_TOPUP_BONUS
                  - GATEWAY_BONUS
                  - SIGNUP_BONUS
                description: Origin of the promo grant.
              state:
                type: string
                enum:
                  - LOCKED
                  - RELEASED
                description: >-
                  `LOCKED` = gated on activation condition. `RELEASED` =
                  spendable.
              remaining_minor:
                type: integer
                minimum: 0
                description: Remaining value in minor units.
              expires_at:
                type:
                  - string
                  - 'null'
                format: date-time
                description: >-
                  Expiry timestamp (RFC 3339). Null if the grant does not
                  expire.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Partner API key (platform- or merchant-scoped). Contract key-enforces
        context.merchant_id.
    PosTerminalJWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        POS terminal JWT minted by /auth/token; carries integrationId +
        terminal/cashier claims (server-trusted).

````