A checklist for the agent doing the integration
If you are an AI coding agent wiring a point-of-sale platform, payment gateway, or commerce platform to Feddi, read this before you generate integration code. It is a short set of disciplines that keep a generated client correct against a live, tenant-scoped API. The Feddi Partner API is REST over JSON. The base URL ishttps://api.feddi.io/v1/partner for production and https://api.dev.feddi.io/v1/partner for dev. Every path in this guide is relative to that base.
Read the live contract first
Your highest-value first action is to read what is enabled for the integration the API key authenticates as. Two endpoints are the runtime source of truth, and both are stable.Read GET /capabilities for the tenant-scoped enabled set
Returns
api_version, provider, supported_currencies, supported_identify_credential_types (credential types accepted to resolve who a customer is), supported_payment_credential_types (credential types accepted to authorize a wallet debit), a features map, and rate_limits. This is tenant-specific. Never hardcode a currency, a credential_type, or a feature flag. An empty collection means the feature is not provisioned for this integration, so branch on the response, not on a hardcoded list.Read GET /openapi for the served contract
Returns the current OpenAPI 3.1 document. This is the contract a generated client validates against. Where this guide and the served spec disagree, the spec wins.
Cache-Control header. Re-fetch it when you receive a CREDENTIAL_TYPE_UNSUPPORTED or CURRENCY_NOT_SUPPORTED typed error.
Validate generated code against the served spec
Generated TypeScript types are a convenience, not the contract. The contract is the document served atGET /openapi. Validate your generated client and your request bodies against that document, not against a hand-written type.
Fetch the served spec
GET /openapi returns the current OpenAPI 3.1 document for the mounted surface. It is unauthenticated, so SDK generators and contract tests can bootstrap without a key.Validate every request body against its schema
The envelope, the credential discriminated unions, and the money fields all have schemas. Validate against them. Money is always a minor-units integer paired with an explicit ISO-4217
currency (for example amount_minor: 3500, currency: QAR), never a float.Reconcile drift toward the spec
If your types and the served document disagree, regenerate from the served document. The spec is the source of truth.
Authenticate, then exchange for a terminal token
You authenticate with thex-api-key header. For hot-path calls (identity and money), exchange the API key for a short-lived POS terminal JWT and present it as a bearer token.
Exchange the API key for a terminal JWT
POST /auth/token with your x-api-key returns a bearer JWT scoped to the terminal (token_type: Bearer, expires_in: 600). The token carries scope claims derived from the key. It is never a debit token and never carries customer PII.Refresh before the TTL expires
The JWT has a 600-second TTL. Call
GET /auth/token/validate to read remaining_seconds and refresh before starting a checkout, so you avoid a mid-transaction 401. A malformed or expired token returns HTTP 200 with valid: false, not a 401: it is a freshness check, not an auth gate.Sandbox access is granted per partner agreement. Request sandbox credentials from your Feddi contact. Sandbox keys run against mock providers and never touch production money or PII, so you can exercise every money path while you build.
Send the canonical envelope on every mutating call
Every mutating call carries ameta block and a context block. Every response is the envelope ok, data, error, meta. Errors are typed string codes in error.code, never a bare HTTP number. Idempotency is the Idempotency-Key HTTP header, and only that.
required
A partner-generated UUID, one per logical mutating operation (24h TTL). Same key plus the same payload replays the original byte-identical response with
meta.idempotency_replayed: true. Same key plus a different payload returns IDEMPOTENCY_KEY_REUSED. meta.partner_request_id is your correlation id, not the deduplication basis.required
The resolved Feddi tenant. Key-enforced: a
merchant_id outside the credential’s authority is rejected, never honored. It is not a free-text trust field.required
Your own correlation id, echoed in logs and responses. Not the idempotency basis.
An opaque integer Feddi may use internally. No action required.
An opaque correlation id. Persist it and echo it where a call accepts one.
Handle every typed error code
Branch onerror.code, the typed string enum, never the HTTP status. Some codes echo the valid set in error.details so you can present or reconcile the accepted values.
error.code enum your client must handle:
INSUFFICIENT_FUNDS(HTTP 402): the wallet balance is less thanamount_minor. The credential is not consumed, so a retry is valid after a top-up. Route this to recovery, never a dead end.WALLET_PROGRAM_AMBIGUOUS(HTTP 422): the identity matches more than one active wallet program.details.candidateslists thewallet_program_idvalues so you can present a disambiguator.CREDENTIAL_TYPE_UNSUPPORTED(HTTP 422): the credential is not enabled for this integration.details.supportedlists the accepted set. Re-readGET /capabilities.REQUIRES_DYNAMIC_CREDENTIAL: a money call needs a fresh dynamic credential (OTP or QR), not a static identifier.IDEMPOTENCY_KEY_REUSED(HTTP 422): the sameIdempotency-Keywas presented with a different payload.CURRENCY_NOT_SUPPORTED: the currency is not provisioned for this integration.detailslists the supported ISO-4217 codes.INVALID_API_KEY(HTTP 401),FORBIDDEN(HTTP 403),NOT_FOUND(HTTP 404),VALIDATION_ERROR(HTTP 400),RATE_LIMITED(HTTP 429),CONFLICT,INTERNAL_SERVER_ERROR.
Identity and money are separate
An identity call resolves a credential to a customer context and returns balance state. It never returns a debit token, an authorization token, or any artifact that permits a ledger mutation. To charge a wallet, you make a separate payment call with a fresh dynamic credential.Identify the customer
POST /identify resolves a credential and returns a resolution_state of registered, pending_proof, or not_found. The not_found state is HTTP 200 with a null balance, not a 404, so a polling UI can tell ‘no wallet’ from a server error. Read GET /capabilities for the enabled supported_identify_credential_types before you send one.Charge the wallet
POST /payments debits the wallet for amount_minor in currency, promo balance first, then actual balance. The result carries debited_promo_minor and debited_actual_minor so the two are always reconcilable, plus the full balance_after.Where to go next
Make your first payment
The callable identify, pay, recover loop worked end to end.
Authentication
The API-key lifecycle and the terminal JWT exchange.
Conventions
The response envelope, money-as-minor-integers, idempotency, versioning, and pagination.
Errors
Every typed code, its HTTP status, and its recovery action.