Resolve a credential to a CustomerContext
You send the credential the point of sale has. Feddi resolves it to aCustomerContext: the customer’s wallet state, program binding, and balance breakdown. An identity call never returns a debit token, an authorization token, or any artifact that permits a ledger mutation. Identity and money are strictly separated.
That separation is the load-bearing rule of this page. Resolving who a customer is tells you nothing you can spend. To move money, you call a payment endpoint with its own credential. See Payments and redemption.
The sequence below shows a presented credential resolving to a CustomerContext. The credential is an opaque input and the resolution is an opaque output; the POS sends one and reads the other.
The canonical resolver is POST /v1/partner/identify. Inside a checkout flow you use the session-anchored form, POST /v1/partner/checkout/sessions/{id}/identify, which also merges the anonymous session into the resolved customer.
A customer’s balance is scoped per merchant. The same phone at two merchants resolves to two distinct balances.
Credential types
The point of sale always has one signal. Feddi resolves any supported credential type to the sameCustomerContext. Each call sends exactly one credential, keyed by credential_type.
The credential_type enum is phone, provider_customer_id, card_fingerprint, short_code, and qr. The set enabled for your integration is capabilities-driven, never a fixed list. Read it from GET /capabilities under supported_identify_credential_types before you assume a type is callable.
E.164 phone number. Feddi-native. OTP and proof flows attach to the identified phone.
The POS platform’s own customer id.
Tokenised card fingerprint from a card-tap or NFC read, never the full PAN. The link is probationary until the phone is verified. A masked-PAN collision never auto-acts on money.
A 4-8 alphanumeric code the customer displays from the wallet app. Already verified by Feddi.
Raw QR payload scanned from the wallet app. Single-use.
The discriminated-union shape
credential_type is the discriminator. You send one credential block per call, and Feddi reads the discriminator to pick the validator. New credential types are added additively: a new type appears in GET /capabilities without any route change, and your existing /identify integration keeps working unchanged. This is why you read capabilities rather than hardcode the list.
Resolve a customer
Read capabilities once at startup
Call
GET /v1/partner/capabilities. It returns supported_identify_credential_types, supported_payment_credential_types, supported_currencies, features, rate_limits, and api_version. Identity resolution reads supported_identify_credential_types. Cache the result, keyed on api_version, and re-fetch when the api_version in a response meta changes.Send the credential you have
Call
POST /v1/partner/identify with meta, context, and one credential block. The standalone resolver fits balance-check panels, cashier lookup screens, and POS displays where there is no active checkout session.Branch on resolution_state
Inspect
data.resolution_state. Handle registered, pending_proof, and not_found explicitly. not_found is returned as HTTP 200, not 404, so a polling UI can tell ‘no wallet’ from a server error.Re-fetch capabilities on CREDENTIAL_TYPE_UNSUPPORTED
A
CREDENTIAL_TYPE_UNSUPPORTED error means your cached set is stale. Re-read GET /capabilities and route to a supported type./identify request body carries three required blocks: meta, context, and credential.
required
Request metadata. Carries
partner_request_id (a correlation id, not the idempotency basis) and api_version. Optional occurred_at and sent_at timestamps. Send both timestamps when you have them.required
Request context.
merchant_id is required and key-enforced to a merchant your credential is authorized for, never a free-text trust field. Optional branch_id, terminal_id, cashier_id, partner_session_id, feddi_session_id.required
Exactly one credential block, keyed by
credential_type. See the credential types above.Idempotency-Key header is optional on this call: identify is a side-effect-free resolution. Sending it is allowed, and a retry with the same key and the same payload replays the original response.
The CustomerContext response
A successful resolution returns aCustomerContext. The shape is the same from /identify and from the session-anchored identify. resolution_state and identity_trace_id are always present. The customer fields and balance are null when resolution_state is not_found.
required
One of
registered (fully enrolled, balance readable and spendable), pending_proof (enrolled, balance visible, spend may be gated by the POS), or not_found (no wallet user for this credential under this enterprise, returned as HTTP 200).The customer’s POS-facing identity id. Null when
resolution_state is not_found.First-class program binding. Null when
not_found. Ambiguity raises HTTP 422 WALLET_PROGRAM_AMBIGUOUS instead of returning this field.The ledger id. Null when
not_found, or when the customer’s wallet has not yet been created (no credits yet).Customer display name. Null when unknown.
Current loyalty tier label, for example
Gold or Silver. Null when the program has no tiering.The two-class balance breakdown:
actual_minor, promo_available_minor, promo_locked_minor, pending_topups_minor, currency, and a promo_grants array. Null when not_found. See Money: actual vs promotional.required
An opaque correlation id for this resolution. Persist it and echo it where a call accepts one.
There is no debit token anywhere in this response. Identity tells you who the customer is and what they hold. It does not authorize spending any of it.
Identify within a checkout session
The session-anchored form attaches an identity to an open checkout session, upgrading it fromOPEN to IDENTIFIED, and merges the session’s anonymous activity into the resolved customer. Use POST /v1/partner/checkout/sessions/{id}/identify. It returns the same CustomerContext.
The request body differs from the standalone resolver: the credential block is named customer, and an optional top-level wallet_program_id disambiguates a multi-program match.
required
Request context.
feddi_session_id is redundant with the path {id} and must match it when both are present.required
Exactly one credential block, keyed by
credential_type. Same credential types as the standalone resolver.required
Disambiguates when the identity resolves to multiple wallet programs. Omit it to receive
WALLET_PROGRAM_AMBIGUOUS with the candidate list.One-way
The anonymous session folds into the customer. The customer is never demoted back to anonymous.
Idempotent
Re-running the merge with the same inputs produces the same result. The call is idempotent on the
Idempotency-Key header: a retry replays the original CustomerContext.Never releases a LOCKED promo grant
The merge moves session activity onto the customer. It does not unlock promotional credit. Release happens only on verified phone proof. See PENDING_PROOF and promo release.
Identity errors
Each error is a string code inerror.code, never a bare HTTP number. The typed errors specific to credential resolution echo the valid set so you can recover without a second round trip.
HTTP
422. The credential type is not enabled for this integration. error.details.supported lists the supported set. Recovery: re-read GET /capabilities and use a supported type.HTTP
422. The credential matches a customer enrolled in more than one active wallet program under this enterprise, and you did not specify which. error.details.candidates lists the candidate wallet_program_id values. Recovery: present a disambiguator and re-call with wallet_program_id set.HTTP
400. Malformed body, unrecognized credential_type, or a missing required credential field. error.details carries the field errors.HTTP
401. Missing or invalid x-api-key or POS terminal JWT.HTTP
403. The key is valid but not authorized for the merchant or branch scope of the call.HTTP
429. The per-integration identify quota is exceeded. The response carries Retry-After and X-RateLimit-* headers.The enabled set is tenant-specific
Not every credential type is enabled for every integration. The authoritative, per-tenant list lives inGET /v1/partner/capabilities under supported_identify_credential_types. GET /v1/partner/openapi is the second runtime source of truth: the CI-validated spec of every mounted route. When you need to know what exists right now, capabilities plus openapi answer it, not this page. Never assume an endpoint is callable because this page names it.
The session-anchored identify shares the contract and credential model of the standalone resolver. Confirm it is enabled for your integration via GET /capabilities before you build against it.
Where to go next
PENDING_PROOF and promo release
Why a card link is probationary and how locked promo releases only on verified phone proof.
Customers and identification
The cashier-panel lookup, the four-state model, and the projection fields.
Idempotency and errors
The response envelope, the idempotency rule, and the full typed-error model.
Money: actual vs promotional
The two-class balance that a CustomerContext returns.