Domain GuidesPlatform & Discovery

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

OperationMethod + pathAuthAvailability
getHealthGET /v1/partner/healthNoneGA
getCapabilitiesGET /v1/partner/capabilitiesx-api-keyGA
getOpenApiSpecGET /v1/partner/openapiNoneGA
identifyPOST /v1/partner/identifyx-api-key or terminal JWTBeta
provisionMerchantPOST /v1/partner/merchantsPlatform-scoped x-api-keyPlanned
submitSettlementReportPOST /v1/partner/settlement/reportx-api-keyPlanned (phase 2)
submitReconciliationPOST /v1/partner/reconciliationx-api-keyPlanned (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.

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
statusstring

up when all subsystems are nominal. degraded when one or more subsystems are impaired but the platform is still serving.

degraded_subsystemsarray

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"
header
x-api-keystring
Required

Your integration's API key (format fddi_…). The descriptor returned is scoped to the integration this key authenticates as.

supported_currenciesarray

ISO 4217 codes this integration can transact in, derived from active wallet program assignments. Empty means none provisioned yet.

supported_credential_typesarray

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.

featuresobject

Named feature flags keyed by operation slug. A missing key or false means the feature is not available for this integration.

rate_limitsobject

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.

This endpoint NEVER returns a debit token, an authorization token, or any artifact that permits a ledger mutation. It is read-only identity resolution. To pay or top up, attach identity to a checkout session and use the payments or topup flows.

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"
    }
  }'

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