Quickstart
Make your first authenticated call to the Feddi Partner API in a few minutes: probe health, read capabilities, exchange your key for a terminal JWT, and identify a customer.
What you build here
You make your first authenticated calls against the Feddi Partner API. By the end you have probed the platform with no key, read what your integration can transact in, exchanged your x-api-key for a terminal JWT, and resolved a customer credential to a profile.
This is the on-ramp, not the full loop. For the complete open → identify → basket → offers → pay → close flow, go to The Golden Path.
Base URL is https://api.feddi.io for production and https://sandbox.api.feddi.io for sandbox.
Every path lives under /v1/partner. Build and self-test in sandbox first: sandbox rows are flagged
and never touch production money or PII.
What you need first
You need one of two API credentials before step 3.
A provisioned platform key
Your platform-scoped x-api-key in the format fddi_.... This is the root credential. The tenant
boundary is enforced on it: context.merchant_id must be a merchant the key is authorized for.
A sandbox key
POST /v1/partner/auth/keys/sandbox provisions sandbox credentials machine-to-machine. This
operation is Planned (not yet callable). Until it flips, use your provisioned key against the
sandbox base URL.
The raw_key from key provisioning is shown once. Store it. A lost key is regenerated, never
recovered. See Authentication for the full key lifecycle.
The five steps
This sequence mixes maturities. Health and capabilities are GA. The terminal JWT exchange and identify are Beta (callable today, shapes may still tighten). Sandbox key provisioning is Planned. Treat GET /v1/partner/capabilities and GET /v1/partner/openapi as the runtime source of truth for what is actually mounted.
Get a sandbox key (or use your provisioned key)
POST /v1/partner/auth/keys/sandbox returns sandbox credentials. This is Planned, so until it
is mounted, use your provisioned platform x-api-key (fddi_...) against the sandbox base URL.
Probe health, no key required
GET /v1/partner/health is GA and unauthenticated. It returns immediately with no DB read or
tenant resolution, so a POS terminal can gate feature availability without holding a key.
Read your capabilities
GET /v1/partner/capabilities is GA. Send your x-api-key. It returns the supported
currencies, credential types, and feature flags for your integration. Read this before assuming
any enum is enabled.
Exchange your key for a terminal JWT
POST /v1/partner/auth/token is Beta. It exchanges your x-api-key for a short-lived
(600s TTL) terminal-scoped bearer token. Refresh it before any money call.
Make a first identify call
POST /v1/partner/identify is Beta. Resolve a customer credential (phone, card fingerprint,
short code, or QR) to a profile carrying wallet state and promo balance. Identify never returns a
debit token or any artifact that permits a ledger mutation.
Step 2: Probe health (GA, no key)
Call health first. A 200 with status: "up" and an empty degraded_subsystems array means the platform is nominal. A non-empty array signals a partial outage: surface a warning at the POS, but you may continue for non-degraded operations.
curl https://sandbox.api.feddi.io/v1/partner/health
{
"ok": true,
"status": "up",
"degraded_subsystems": []
}
Health carries no tenant data by design. It is mounted before the auth middleware, so it is the cheapest possible liveness probe.
Step 3: Read capabilities (GA, with x-api-key)
Capabilities is the authoritative descriptor of what your integration can transact in. An integrating agent (POS adapter, SDK, or automation) reads this before assuming any currency, credential type, or feature flag exists.
Your platform-scoped API key (fddi_...). The integration is resolved from this key.
curl https://sandbox.api.feddi.io/v1/partner/capabilities \
-H "x-api-key: fddi_your_key_here"
{
"ok": true,
"data": {
"api_version": "2026-06-01",
"integration_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"provider": "ODOO",
"supported_currencies": ["QAR", "SAR"],
"supported_credential_types": ["phone", "card_fingerprint", "short_code"],
"features": { "identify": true, "checkout": true, "topup": false, "promo_grants": false },
"rate_limits": { "identify": { "per_minute": 120 }, "payments": { "per_minute": 60 } }
},
"error": null,
"meta": {
"request_id": "req_a1b2c3",
"idempotency_replayed": false,
"api_version": "2026-06-01",
"data_completeness_score": 100
}
}
Key fields to read off the response:
The currencies your integration's active wallet programs transact in. An empty array means no program is provisioned yet, not that currencies are unsupported globally.
The customer credential types this integration accepts. The core types are phone,
card_fingerprint, and short_code; additional types (provider_customer_id, and qr in
QR-capable contexts) exist under the same discriminated-union pattern. This array is the
authoritative enabled set, alongside the openapi schema. Sending an unsupported type returns
CREDENTIAL_TYPE_UNSUPPORTED with the supported set in the error details.
Named feature flags keyed by operation. The descriptor grows additively as endpoint bites land; it never over-claims.
Cache this response, honor its Cache-Control header, and re-fetch on a
CREDENTIAL_TYPE_UNSUPPORTED or CURRENCY_NOT_SUPPORTED typed error. See
Availability & Maturity for the full runtime-discovery model.
Step 4: Exchange your key for a terminal JWT (Beta)
Per-terminal money and session calls use a POS terminal JWT, not the raw x-api-key. You exchange the key for a JWT, then send Authorization: Bearer <jwt> on session, payment, and identify calls.
The JWT is short-lived: TTL 600 seconds. It carries scope claims (integration_id, enterprise_id, brand_id, branch_id) derived from the key plus a server-validated cashier_id. Refresh it before any money call.
The exchange takes the canonical envelope. Send meta and context, and an Idempotency-Key header (a retried exchange within the window replays the same JWT).
A partner-generated UUID. A retried exchange within the window replays the same JWT.
curl -X POST https://sandbox.api.feddi.io/v1/partner/auth/token \
-H "x-api-key: fddi_your_key_here" \
-H "Idempotency-Key: 6f1d2c3a-0000-4abc-9def-000000000001" \
-H "Content-Type: application/json" \
-d '{
"meta": { "partner_request_id": "aa07...", "occurred_at": "2026-06-05T09:00:00Z", "sent_at": "2026-06-05T09:00: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", "cashier_id": "cashier-42" },
"cashier_id": "cashier-42"
}'
{
"ok": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...signature",
"token_type": "Bearer",
"expires_in": 600,
"expires_at": "2026-06-05T09:10:00Z",
"scope": {
"integration_id": "33333333-3333-3333-3333-333333333333",
"enterprise_id": "11111111-1111-1111-1111-111111111111",
"brand_id": "44444444-4444-4444-4444-444444444444",
"branch_id": "22222222-2222-2222-2222-222222222222",
"cashier_id": "cashier-42"
},
"sandbox": true
},
"error": null,
"meta": { "request_id": "req_tk1", "idempotency_replayed": false, "api_version": "2026-06-01" }
}
The terminal JWT expired (600s TTL). Re-exchange your x-api-key at POST /v1/partner/auth/token.
A revoked or expired key, or a key whose integration is not ACTIVE, also returns INVALID_API_KEY.
Errors are typed string codes in error.code, never a bare HTTP number. See
Idempotency & Errors.
Step 5: Make a first identify call (Beta)
Identify resolves one customer credential to a profile. Use the terminal JWT you minted in step 4 (the standalone endpoint also accepts x-api-key for balance-check panels and cashier lookup screens where there is no active session).
The call carries the canonical envelope and an Idempotency-Key header. The data.resolution_state field returns one of three states: registered (balance readable and spendable), pending_proof (enrolled but not yet identity-verified, balance visible, spend gated by the POS), or not_found (no wallet user for this credential, returned as HTTP 200 with a null balance so polling UIs distinguish it from a server error). A credential that matches a customer enrolled in multiple active programs is not a resolution state: it returns the typed WALLET_PROGRAM_AMBIGUOUS error (HTTP 422) with the candidate wallet_program_id values in error.details.candidates.
curl -X POST https://sandbox.api.feddi.io/v1/partner/identify \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...signature" \
-H "Idempotency-Key: 6f1d2c3a-0000-4abc-9def-000000000002" \
-H "Content-Type: application/json" \
-d '{
"meta": { "partner_request_id": "bb08...", "occurred_at": "2026-06-05T09:01:00Z", "sent_at": "2026-06-05T09:01: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", "cashier_id": "cashier-42" },
"credential": { "credential_type": "phone", "phone": "+97433001122" }
}'
{
"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" }
},
"error": null,
"meta": { "request_id": "req_id1", "idempotency_replayed": false, "api_version": "2026-06-01", "data_completeness_score": 88, "decision_trace_id": null }
}
Identify never returns a debit token, an authorization token, or any artifact that permits a ledger mutation. It reads identity and balance only. A spend happens on a separate, idempotent payment call. See The Decisioning Model.
The credential types follow a discriminated-union pattern on credential_type. The core types are phone, card fingerprint, and short code; additional types exist in the contract under the same pattern. Read Identity & Credentials for the full set, and confirm what your integration accepts via GET /capabilities.
You are now authenticated
You have probed the platform, read your capabilities, minted a terminal JWT, and resolved a customer. The next move is the full session loop: open a checkout_session, attach the basket, resolve offers, pay promo-first, and close with a receipt of record.
Where to go next
The Golden Path
Open → identify → basket → offers → pay → close, worked end to end.
The Decisioning Model
Why every interaction opens a session and the wallet is one actuator.
Authentication
The two API credentials, the key lifecycle, and JWT scope claims.
Availability & Maturity
How GA, Beta, Planned, and Preview map to what you can call today.