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

# Resolve a customer credential to a CustomerContext

> The canonical identity-resolution resource. Accepts one of five credential types (discriminated by `credential_type`: `phone` | `card_fingerprint` | `short_code` | `provider_customer_id` | `qr`) and resolves the customer to a `CustomerContext` carrying their wallet state, promo balance, wallet program binding, and an `identity_trace_id` for downstream correlation. Returns one of three resolution states: `registered` (fully enrolled, balance readable), `pending_proof` (enrolled but not yet identity-verified, balance visible but spend gated by the POS), or `not_found` (no wallet identity for this credential under this enterprise). A credential that matches a customer enrolled in multiple active programs returns `WALLET_PROGRAM_AMBIGUOUS` (HTTP 422); the error body lists the candidates so the caller can present a disambiguator. **This endpoint never returns a debit token, an authorization token, or any artifact that permits a ledger mutation.** The session-anchored variant, which writes the resolved identity into an open checkout session, is `POST /checkout/sessions/{id}/identify`. Use this standalone endpoint for balance-check panels, cashier lookup screens, and POS-display use cases where there is no active checkout session. Rate-limited per integration to prevent enumeration attacks; a `429` response carries `Retry-After` and `X-RateLimit-*` headers.



## OpenAPI

````yaml /api-reference/openapi.yaml post /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:
  /identify:
    post:
      tags:
        - platform
      summary: Resolve a customer credential to a CustomerContext
      description: >-
        The canonical identity-resolution resource. Accepts one of five
        credential types (discriminated by `credential_type`: `phone` |
        `card_fingerprint` | `short_code` | `provider_customer_id` | `qr`) and
        resolves the customer to a `CustomerContext` carrying their wallet
        state, promo balance, wallet program binding, and an `identity_trace_id`
        for downstream correlation. Returns one of three resolution states:
        `registered` (fully enrolled, balance readable), `pending_proof`
        (enrolled but not yet identity-verified, balance visible but spend gated
        by the POS), or `not_found` (no wallet identity for this credential
        under this enterprise). A credential that matches a customer enrolled in
        multiple active programs returns `WALLET_PROGRAM_AMBIGUOUS` (HTTP 422);
        the error body lists the candidates so the caller can present a
        disambiguator. **This endpoint never returns a debit token, an
        authorization token, or any artifact that permits a ledger mutation.**
        The session-anchored variant, which writes the resolved identity into an
        open checkout session, is `POST /checkout/sessions/{id}/identify`. Use
        this standalone endpoint for balance-check panels, cashier lookup
        screens, and POS-display use cases where there is no active checkout
        session. Rate-limited per integration to prevent enumeration attacks; a
        `429` response carries `Retry-After` and `X-RateLimit-*` headers.
      operationId: identify
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyOptional'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IdentifyRequest'
            example:
              meta:
                partner_request_id: 7f3a1e20-1b2c-4d5e-8f90-a1b2c3d4e5f6
                occurred_at: '2026-06-05T10:00:00Z'
                sent_at: '2026-06-05T10:00:01Z'
                api_version: '2026-06-01'
              context:
                merchant_id: 11111111-1111-1111-1111-111111111111
                branch_id: 22222222-2222-2222-2222-222222222222
                terminal_id: POS-360-0001
                cashier_id: cashier-7
                partner_session_id: null
                feddi_session_id: null
              credential:
                credential_type: phone
                phone: '+97433001122'
      responses:
        '200':
          description: >-
            Customer resolved. Inspect `data.resolution_state` for the
            resolution outcome: `registered`, `pending_proof`, or `not_found`.
            The `not_found` state is returned as HTTP 200 with a null balance
            (not 404) so polling UIs can distinguish 'no wallet' from a server
            error.
          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-aabbccdd-1234
                  wallet_program_id: wp-55667788-0001
                  wallet_id: wlt-99001122-ffff
                  identity_trace_id: idt-trace-xyzabc
                  display_name: Ahmed Al-Rashid
                  badge: Gold
                  balance:
                    actual_minor: 15000
                    promo_available_minor: 500
                    promo_locked_minor: 1200
                    pending_topups_minor: 0
                    currency: QAR
                    promo_grants:
                      - source: CASHBACK
                        state: RELEASED
                        remaining_minor: 500
                        expires_at: '2026-09-05T00:00:00Z'
                      - source: RELOAD_BONUS
                        state: LOCKED
                        remaining_minor: 1200
                        expires_at: '2026-09-10T00:00:00Z'
                error: null
                meta:
                  request_id: req_b2c3d4
                  idempotency_replayed: false
                  api_version: '2026-06-01'
                  data_completeness_score: 88
                  decision_trace_id: null
        '400':
          description: >-
            `VALIDATION_ERROR` (HTTP 400), malformed body, unrecognized
            `credential_type`, or missing required credential field.
          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 or invalid `x-api-key` / POS
            terminal JWT.
          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
            merchant scope (cross-tenant guard).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '422':
          description: >-
            `WALLET_PROGRAM_AMBIGUOUS` (HTTP 422), the credential matches a
            customer enrolled in multiple active wallet programs under this
            enterprise. The `error.details.candidates` array lists
            `wallet_program_id` values so the caller can present a disambiguator
            to the customer or cashier.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
        '429':
          description: >-
            `RATE_LIMITED` (HTTP 429), per-integration identify quota exceeded.
            `Retry-After` and `X-RateLimit-*` headers are present.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      error:
                        $ref: '#/components/schemas/Error'
      security:
        - ApiKeyAuth: []
        - PosTerminalJWT: []
components:
  parameters:
    IdempotencyKeyOptional:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        OPTIONAL on this operation, it is a side-effect-free read behind a POST
        (the body carries the query). Reads are naturally idempotent, so no
        dedup key is required for correctness. If you send the header anyway
        (e.g. uniform client middleware that stamps every POST), it is accepted
        and replays are reported via `meta.idempotency_replayed`. Mutating
        operations use the REQUIRED `IdempotencyKey` parameter instead.
      schema:
        type: string
        format: uuid
  schemas:
    IdentifyRequest:
      type: object
      description: >-
        Body for `POST /identify`. Carries exactly one credential (discriminated
        by `credential.credential_type`). No checkout session context is
        required, this is the standalone identify. For the session-anchored
        variant use `POST /checkout/sessions/{id}/identify`.
      required:
        - meta
        - context
        - credential
      properties:
        meta:
          $ref: '#/components/schemas/RequestMeta'
        context:
          $ref: '#/components/schemas/RequestContext'
        credential:
          $ref: '#/components/schemas/IdentifyCredential'
    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.
    IdentifyCredential:
      description: >-
        Discriminated-union credential. The `credential_type` field selects the
        variant. Exactly one credential field must be present alongside
        `credential_type`.
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - credential_type
            - phone
          properties:
            credential_type:
              type: string
              enum:
                - phone
            phone:
              type: string
              description: E.164 phone number (e.g. `'+97433001122'`).
              example: '+97433001122'
        - type: object
          additionalProperties: false
          required:
            - credential_type
            - card_fingerprint
          properties:
            credential_type:
              type: string
              enum:
                - card_fingerprint
            card_fingerprint:
              type: string
              description: >-
                Tokenised card fingerprint (from a card-tap / NFC read). Never
                the full PAN.
              example: fp_a1b2c3d4e5f6
        - type: object
          additionalProperties: false
          required:
            - credential_type
            - short_code
          properties:
            credential_type:
              type: string
              enum:
                - short_code
            short_code:
              type: string
              description: >-
                Customer-displayed short code (4-8 alphanumeric chars shown in
                the wallet app).
              example: F3A9
        - type: object
          additionalProperties: false
          required:
            - credential_type
            - qr
          properties:
            credential_type:
              type: string
              enum:
                - qr
            qr:
              type: string
              description: Raw QR payload scanned from the customer's wallet app.
              example: feddi://qr/v1/9f8e7d6c5b4a
        - type: object
          additionalProperties: false
          required:
            - credential_type
            - provider_customer_id
          properties:
            credential_type:
              type: string
              enum:
                - provider_customer_id
            provider_customer_id:
              type: string
              description: >-
                The POS provider's own customer id (when
                credential_type=provider_customer_id).
              example: cust_77f0a1
    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.
  headers:
    RetryAfter:
      description: >-
        Seconds to wait before retrying (RFC 9110 Retry-After). Present on `429`
        responses.
      schema:
        type: integer
    XRateLimitLimit:
      description: >-
        The per-minute request budget for this operation + integration (mirrors
        `Capabilities.rate_limits`).
      schema:
        type: integer
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    XRateLimitReset:
      description: Unix epoch seconds at which the current rate-limit window resets.
      schema:
        type: integer
  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).

````