Platform & Discovery
Probe liveness, read what your integration can actually do, and self-serve the contract. The platform tag is where you learn the surface before you build against it.
Overview
The platform tag answers three questions before you write a single transactional call: is Feddi up, what is this integration allowed to do, and what does the contract say. Two of those answers come from endpoints that are live and safe to call today (GET /health and GET /capabilities), and the contract itself is self-served from GET /openapi.
The load-bearing rule: read GET /capabilities before you assume any enum, currency, or feature flag. It is tenant-scoped and authoritative. The narrative on this site can drift; /capabilities and /openapi cannot, because they are served live from the running API.
This page also covers standalone identity resolution (POST /identify, Beta), and three Planned operations for platform-to-tenant provisioning and settlement reconciliation that are shape-frozen but not yet callable.
Operations
| Operation | Method + path | Auth | Availability |
|---|---|---|---|
getHealth | GET /v1/partner/health | None | GA |
getCapabilities | GET /v1/partner/capabilities | x-api-key | GA |
getOpenApiSpec | GET /v1/partner/openapi | None | GA |
identify | POST /v1/partner/identify | x-api-key or terminal JWT | Beta |
provisionMerchant | POST /v1/partner/merchants | Platform-scoped x-api-key | Planned |
submitSettlementReport | POST /v1/partner/settlement/report | x-api-key | Planned (phase 2) |
submitReconciliation | POST /v1/partner/reconciliation | x-api-key | Planned (phase 2) |
The three Planned operations are designed and reserved, not implementation-verified. Wire them, but treat them as not-callable until they flip. The authoritative list of mounted routes is always GET /capabilities and GET /openapi, never this table alone. See Availability & Maturity.
Health versus capabilities: a deliberate split
These two endpoints look similar and are not. The split is intentional and you should use each for its own job.
GET /health is liveness
Unauthenticated, stateless, no database read, no tenant resolution. Mounted before the auth middleware. Carries zero tenant data. Use it for load-balancer probes and POS terminals gating feature availability without holding a key.
GET /capabilities is read-before-you-assume
Authenticated and tenant-scoped. Returns the supported currencies, credential types, feature flags, and rate limits for the integration your key authenticated as. Read it before you build against any enum.
The reason they are separate: a liveness probe must answer instantly under load and must never leak which merchant is behind a key, so it does no DB work and carries no tenant fields. A capability descriptor must be tenant-accurate, so it requires a key and a tenant resolution. Combining them would either slow the probe or leak tenant data. They stay split.
GET /health
Check that the platform is reachable. The response is { ok: true, status: "up", degraded_subsystems: [] } when everything is nominal.
A non-empty degraded_subsystems array means a partial outage. The POS should surface a warning, but may continue for operations that do not depend on the impaired subsystem.
curl https://api.feddi.io/v1/partner/health
{
"ok": true,
"status": "up",
"degraded_subsystems": []
}
up when all subsystems are nominal. degraded when one or more subsystems are impaired but the platform is still serving.
Named subsystems currently impaired (for example ["promo_engine", "webhook_fanout"]). Empty when status is up.
GET /health returns a bare liveness object, not the standard response envelope. It is mounted before auth precisely so it can stay this lean. Every other operation on this page returns the { ok, data, error, meta } envelope.
GET /capabilities
Read what your integration can actually transact in. This is the runtime source of truth for currencies, credential types, feature flags, and rate limits, scoped to the integration your x-api-key resolves to.
An integrating agent (a POS adapter, an SDK, an automation) must call this before assuming any enum. Empty collections mean a feature is not yet provisioned for this integration, not that it is unsupported globally. The descriptor grows additively as endpoint bites land; it never over-claims.
curl https://api.feddi.io/v1/partner/capabilities \
-H "x-api-key: fddi_live_xxxxxxxxxxxxxxxx"
{
"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
}
}
Your integration's API key (format fddi_…). The descriptor returned is scoped to the integration this key authenticates as.
ISO 4217 codes this integration can transact in, derived from active wallet program assignments. Empty means none provisioned yet.
Credential types the integration's wallet programs accept for identity resolution. 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, alongside the openapi schema, is the authoritative enabled set.
Named feature flags keyed by operation slug. A missing key or false means the feature is not available for this integration.
Per-operation limits (per_minute integer) that match the X-RateLimit-* headers enforced on each endpoint.
Cache the descriptor, honor its Cache-Control header, and re-fetch when you hit a CREDENTIAL_TYPE_UNSUPPORTED or CURRENCY_NOT_SUPPORTED typed error. Those errors mean your cached view of the integration has gone stale.
GET /openapi
Fetch the CI-validated OpenAPI 3.1 contract for every mounted /v1/partner/* route. This is the self-description anchor: validate your integration against this document, not against generated TypeScript types.
The spec is CI-validated, so every mounted route has an entry or the build fails on drift. It is unauthenticated so tooling (Postman, Swagger UI, custom SDK generators) can bootstrap without a key.
curl https://api.feddi.io/v1/partner/openapi
The response is a valid OpenAPI 3.1 JSON document. Pair it with GET /capabilities when you build: /openapi tells you what shapes exist, /capabilities tells you which of them your integration can call right now.
POST /identify (Beta)
Resolve a customer credential to a CustomerContext without opening a checkout session. Use this for balance-check panels, cashier lookup screens, and any POS-display surface where there is no active session.
identify accepts a credential discriminated by credential_type. 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, so read GET /capabilities and the openapi schema for the authoritative enabled set. It returns the customer's wallet state, both money classes, the wallet program binding, and an identity_trace_id for downstream correlation.
It resolves to one of four states in data.resolution_state:
registered
Fully enrolled. Balance is readable. Spend is permitted through the normal payment flow.
pending_proof
Enrolled but not yet identity-verified. Balance is visible, but spend is gated by the POS until the customer proves their phone. See Pending Proof.
not_found
No wallet user for this credential under this enterprise. Returned as HTTP 200 with a null balance, not 404, so polling UIs can distinguish "no wallet" from a server error.
wallet_program_ambiguous
The credential matches a customer enrolled in multiple active programs. Returned as a typed WALLET_PROGRAM_AMBIGUOUS error (HTTP 422) whose error.details.candidates lists the candidate wallet_program_id values so you can present a disambiguator.
curl -X POST https://api.feddi.io/v1/partner/identify \
-H "x-api-key: fddi_live_xxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: 7f3a1e20-1b2c-4d5e-8f90-a1b2c3d4e5f6" \
-H "Content-Type: application/json" \
-d '{
"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"
}
}'
{
"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
}
}
The balance object exposes both money classes distinctly: actual_minor is real money, promo_available_minor is released and spendable, promo_locked_minor is gated on signup or first top-up. Never conflate them. Read Money: Actual vs Promotional for the full model.
POST /identify is the standalone, sessionless variant. The session-anchored form, which writes the resolved identity into an open checkout session, is POST /checkout/sessions/{id}/identify. Both are documented under Customers & Identity.
identify is rate-limited per integration to prevent enumeration. A 429 response carries Retry-After and X-RateLimit-* headers; back off and retry.
Planned: platform-to-tenant and settlement
Three operations on this tag are Planned. Their shapes are frozen in the contract so POS adapters can plan their integration, but they are not mounted yet. Confirm against GET /capabilities before building against them.
POST /merchants (Planned)
Lets a POS platform integration that holds a platform-scoped API key provision a new per-merchant Feddi tenant and mint a scoped credential for it. This is the addmerchant handshake.
One platform integration maps to many Feddi merchant tenants. The platform presents the new retailer's details; Feddi resolves or creates the enterprise tenant and returns a merchant_context carrying the merchant_id and a scoped api_key for subsequent merchant-scoped calls.
Platform-keyed only. A merchant-scoped key calling this endpoint returns FORBIDDEN (HTTP 403). The provisioned merchant is isolated: a customer's closed-loop balance stays per-merchant, which preserves the closed-loop exemption (no cross-merchant spend). The Sadad pilot wires this handshake in Phase 2.
POST /settlement/report (Planned, phase 2)
Ingests a provider-side settlement report (a Sadad daily settlement file, an Odoo payment journal export) and queues it for reconciliation against Feddi's ledger. The report is stored verbatim and processed asynchronously.
The response confirms ingestion, not reconciliation completion: HTTP 202 with { settlement_report_id, status: "queued" }. Listen for the settlement.reconciled webhook event or poll /{integrationId}/transactions for the resolved state.
POST /reconciliation (Planned, phase 2)
Accepts a partner-side daily reconciliation summary (transaction count, total value, discrepancy flags) for Feddi to cross-check against its ledger. The run is asynchronous; the response acknowledges with a reconciliation_run_id. Discrepancies surface via the reconciliation.completed webhook event. Idempotent on the Idempotency-Key header.
Settlement and reconciliation are a phase-2 pair. They are reporting surfaces, never custody surfaces: Feddi never holds money. See The Decisioning Model for the closed-loop money model.
Where to go next
Availability & Maturity
What GA, Beta, Planned, and Preview mean, and why the live spec is the only authoritative route list.
Customers & Identity
The session-anchored identify, lookup, and the four-state resolution in depth.
Authentication
Platform key versus terminal JWT, and how the tenant boundary is enforced.
Idempotency & Errors
The Idempotency-Key header and the typed error codes you handle on every mutating call.