openapi: 3.1.0 info: title: Retail Wallet Partner API version: 1.0.0-draft contact: name: Retail Wallet Integration Support email: support@retail-wallet.com url: https://retail-wallet.com description: |
⬇ Download OpenAPI spec (YAML)⬇ Download integration guide (DOCX)
# Introduction The Retail Wallet Partner API lets a partner application offer its users a virtual **Retail Wallet & Embargo Visa card** — issued, funded and rewarded entirely inside the partner app. Everything regulated and heavy lives behind this API: * **Card issuing & e-money** — cards are issued by our regulated issuer backend; customer funds are safeguarded. The partner needs no licence. * **KYC** — identity verification runs through our KYC-check system; the partner only embeds an SDK screen we hand it. * **Payments** — card top-ups and pay-by-bank run through our PSP integrations; card data never touches partner code, so the partner stays **outside PCI DSS scope**. * **Rewards engine** — cashback, instant discounts and time-slot offers are computed and settled by us; the partner receives ready-made webhook events. **The journey at a glance:** `onboard user → issue card → add to Google Wallet → top up / link card → user pays in store → events & rewards flow back → balances and history on screen` # Environments | Environment | Base URL | |---|---| | Sandbox | `https://sandbox.api.retail-wallet.com/v1` | | Production | `https://api.retail-wallet.com/v1` | The sandbox provides test users, test cards and simulated in-store purchases. # Authentication Server-to-server **OAuth 2.0 Client Credentials**. Exchange your `client_id` + `client_secret` at the token endpoint for a short-lived access token and send it as a Bearer token: ``` POST https://auth.retail-wallet.com/oauth/token grant_type=client_credentials&client_id=...&client_secret=... ``` ``` Authorization: Bearer ``` All calls are server-to-server over TLS 1.2+. Your mobile app never calls this API directly. # Idempotency All `POST` endpoints accept an `Idempotency-Key` header (any unique string, e.g. UUID). Retrying a request with the same key returns the original result and never creates a duplicate side effect — safe retries, no double charges. # Webhooks We deliver events to a single HTTPS endpoint you register with us. See the **Webhooks** section for every event type and payload. * Each delivery is signed: `X-RW-Signature: sha256=HMAC_SHA256(raw_body, webhook_secret)`. Verify the signature before trusting the payload. * Deliveries are retried with exponential backoff until your endpoint returns `2xx`. * Deduplicate by `event_id` — a delivery may arrive more than once. # Errors Errors use a single envelope: ```json { "error": { "code": "validation_failed", "message": "date_of_birth: user must be 18 or older", "request_id": "req_9f27c1" } } ``` | HTTP | code (examples) | Meaning | |---|---|---| | 400 | `validation_failed` | Malformed request or field validation error | | 401 | `unauthenticated` | Missing/expired token | | 403 | `forbidden` | Token lacks access to this resource | | 404 | `not_found` | Resource does not exist | | 409 | `duplicate` | Idempotency conflict / already exists | | 422 | `unprocessable` | Business rule rejection (e.g. top-up limit exceeded) | | 429 | `rate_limited` | Too many requests | | 500 | `internal_error` | Our side; safe to retry with the same Idempotency-Key | # Amounts All monetary amounts are **integer minor units** (`amount_minor`, pence) with an ISO 4217 `currency` (pilot: `GBP` only). `£12.50` → `1250`. servers: - url: https://sandbox.api.retail-wallet.com/v1 description: Sandbox - url: https://api.retail-wallet.com/v1 description: Production security: - oauth2: [partner.api] tags: - name: Users description: > Customer onboarding. Create a user, embed the KYC SDK screen with the token we return, and wait for the `user.status_changed` webhook to unlock card issuing. - name: Cards description: > Virtual card issuing and lifecycle. Cards are issued instantly and are born restricted to participating merchants. The pilot app displays last4 + design only. - name: Google Wallet description: > In-app push provisioning to Google Wallet. The app obtains device identifiers with the Google TapAndPay SDK, this API returns an opaque provisioning payload (OPC), and the app passes it to `pushTokenize`. Apple Pay follows after the Google Wallet launch. - name: Payment Methods description: > Linking (saving) a customer card for one-tap top-ups and post-pay. Card data is captured only inside the drop-in payment sheet and stored in the PSP's certified vault. - name: Top-ups description: > Funding the prepaid credit. Two methods — `card` and `pay_by_bank` — both confirmed by the user in a drop-in sheet in the app. Pilot limits: min £1, max £200 per top-up, £400 per month. - name: Wallet description: Balances and prepaid credit. - name: Transactions description: Unified activity feed — purchases, top-ups, cashback. - name: Merchants description: > Read API for the in-app storefront — participating merchants, cashback terms and time-slot offers. Offer management is performed by Retail Wallet during the pilot; a management API opens later without changes to this read API. x-tagGroups: - name: API tags: [Users, Cards, Google Wallet, Payment Methods, Top-ups, Wallet, Transactions, Merchants] paths: /users: post: tags: [Users] operationId: createUser summary: Create user (onboarding) description: | Registers a partner-app user with Retail Wallet and starts KYC. Call after the user accepts the Retail Wallet Terms & Conditions in the app. The response includes `kyc_sdk_token` — pass it to the KYC-check SDK screen in the app. When verification completes we send the `user.status_changed` webhook. parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserCreateRequest' responses: '201': description: User created, KYC pending. content: application/json: schema: $ref: '#/components/schemas/UserCreateResponse' '400': { $ref: '#/components/responses/BadRequest' } '409': { $ref: '#/components/responses/Duplicate' } /users/{user_id}: get: tags: [Users] operationId: getUser summary: Get user parameters: - $ref: '#/components/parameters/UserId' responses: '200': description: User. content: application/json: schema: $ref: '#/components/schemas/User' '404': { $ref: '#/components/responses/NotFound' } /cards: post: tags: [Cards] operationId: issueCard summary: Issue card description: | Issues a virtual card for an active user. Issuing is asynchronous but fast (seconds): the response returns `card_request_id`, and the `card.issued` webhook delivers the card. parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CardCreateRequest' responses: '202': description: Card issuing accepted. content: application/json: schema: type: object required: [card_request_id] properties: card_request_id: type: string examples: [cardreq_8Zk2w] '400': { $ref: '#/components/responses/BadRequest' } '422': { $ref: '#/components/responses/Unprocessable' } /cards/{card_id}: get: tags: [Cards] operationId: getCard summary: Get card parameters: - $ref: '#/components/parameters/CardId' responses: '200': description: Card. content: application/json: schema: $ref: '#/components/schemas/Card' '404': { $ref: '#/components/responses/NotFound' } /cards/{card_id}/freeze: post: tags: [Cards] operationId: freezeCard summary: Freeze card description: Temporarily blocks the card. Confirmed by a `card.status_changed` webhook. parameters: - $ref: '#/components/parameters/CardId' - $ref: '#/components/parameters/IdempotencyKey' responses: '200': description: Card frozen. content: application/json: schema: $ref: '#/components/schemas/Card' /cards/{card_id}/unfreeze: post: tags: [Cards] operationId: unfreezeCard summary: Unfreeze card parameters: - $ref: '#/components/parameters/CardId' - $ref: '#/components/parameters/IdempotencyKey' responses: '200': description: Card active again. content: application/json: schema: $ref: '#/components/schemas/Card' /cards/{card_id}/google-pay: post: tags: [Google Wallet] operationId: provisionGooglePay summary: Get Google Wallet provisioning payload description: | Returns the opaque provisioning payload (**OPC**) for adding this card to Google Wallet. Flow in the app: 1. Obtain `client_device_id` and `client_wallet_account_id` from the Google TapAndPay SDK. 2. Call this endpoint. 3. Pass `opc` to `pushTokenize()`. 4. We confirm activation with the `card.wallet_status` webhook — no polling needed. parameters: - $ref: '#/components/parameters/CardId' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GooglePayProvisionRequest' responses: '200': description: Provisioning payload. content: application/json: schema: type: object required: [opc] properties: opc: type: string description: Opaque payment card payload — pass unchanged to `pushTokenize()`. examples: [eyJraWQiOiIxIiwidHlwIj...] '422': { $ref: '#/components/responses/Unprocessable' } /payment-methods/setup: post: tags: [Payment Methods] operationId: setupPaymentMethod summary: Start card linking description: | Creates a linking session. Present the returned `setup_session` in the drop-in payment sheet; on success we send the `payment_method.added` webhook with `pm_id`, `brand`, `last4`. parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object required: [user_id] properties: user_id: type: string examples: [usr_7Hq1k] responses: '201': description: Linking session created. content: application/json: schema: type: object required: [setup_session] properties: setup_session: type: string description: One-time session token for the payment sheet. examples: [setup_sess_x91h2] /users/{user_id}/payment-methods: get: tags: [Payment Methods] operationId: listPaymentMethods summary: List linked cards parameters: - $ref: '#/components/parameters/UserId' responses: '200': description: Linked cards. content: application/json: schema: type: object properties: items: type: array items: $ref: '#/components/schemas/PaymentMethod' /payment-methods/{payment_method_id}: delete: tags: [Payment Methods] operationId: deletePaymentMethod summary: Unlink card parameters: - name: payment_method_id in: path required: true schema: { type: string } responses: '204': description: Unlinked. /topups: post: tags: [Top-ups] operationId: createTopup summary: Create top-up description: | Starts a top-up of the user's prepaid credit. * `method: card` — returns `payment_session` for the card payment sheet (new or saved card). * `method: pay_by_bank` — returns `bank_session` for the pay-by-bank sheet (bank selection → approval in the user's banking app). The outcome is delivered by `topup.succeeded` / `topup.failed` webhooks. **Pilot limits:** min £1 (`100`), max £200 (`20000`) per top-up, £400 (`40000`) per calendar month. parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TopupCreateRequest' responses: '201': description: Top-up created; confirm in the app sheet. content: application/json: schema: $ref: '#/components/schemas/TopupCreateResponse' '422': { $ref: '#/components/responses/Unprocessable' } /topups/{topup_id}: get: tags: [Top-ups] operationId: getTopup summary: Get top-up parameters: - name: topup_id in: path required: true schema: { type: string } responses: '200': description: Top-up. content: application/json: schema: $ref: '#/components/schemas/Topup' '404': { $ref: '#/components/responses/NotFound' } /users/{user_id}/wallet: get: tags: [Wallet] operationId: getWallet summary: Get balances description: | Four numbers in one call. Safe to cache 30–60 s — our webhooks tell you when to refresh. parameters: - $ref: '#/components/parameters/UserId' responses: '200': description: Wallet. content: application/json: schema: $ref: '#/components/schemas/Wallet' /users/{user_id}/transactions: get: tags: [Transactions] operationId: listTransactions summary: List transactions description: One merged feed — purchases, top-ups and cashback — newest first, cursor pagination. parameters: - $ref: '#/components/parameters/UserId' - name: cursor in: query schema: { type: string } description: Opaque cursor from `next_cursor` of the previous page. - name: type in: query schema: type: string enum: [purchase, topup, cashback, refund] - name: status in: query schema: type: string enum: [pending, completed, reversed] - name: limit in: query schema: { type: integer, default: 25, maximum: 100 } responses: '200': description: Transaction page. content: application/json: schema: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/Transaction' next_cursor: type: [string, 'null'] examples: [cur_9Ab3] /merchants: get: tags: [Merchants] operationId: listMerchants summary: List participating merchants description: Merchants accepting the card, with active cashback/discount terms — for the in-app storefront. responses: '200': description: Merchants. content: application/json: schema: type: object properties: items: type: array items: $ref: '#/components/schemas/Merchant' /merchants/{merchant_id}/discount-slots: get: tags: [Merchants] operationId: listDiscountSlots summary: List time-slot offers parameters: - name: merchant_id in: path required: true schema: { type: string } responses: '200': description: Active and upcoming slots. content: application/json: schema: type: object properties: items: type: array items: $ref: '#/components/schemas/DiscountSlot' webhooks: user.status_changed: post: tags: [Users] operationId: whUserStatusChanged summary: 'Webhook: user.status_changed' description: KYC passed (`active`) or the user was blocked. `active` unlocks card issuing. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: user_id: { type: string, examples: [usr_7Hq1k] } status: { type: string, enum: [active, blocked, kyc_rejected] } responses: '200': { description: Acknowledge with any 2xx. } card.issued: post: tags: [Cards] operationId: whCardIssued summary: 'Webhook: card.issued' description: The virtual card is issued and active. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: $ref: '#/components/schemas/Card' responses: '200': { description: Acknowledge with any 2xx. } card.status_changed: post: tags: [Cards] operationId: whCardStatusChanged summary: 'Webhook: card.status_changed' description: Card frozen / unfrozen / closed. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: card_id: { type: string } status: { type: string, enum: [active, frozen, closed] } responses: '200': { description: Acknowledge with any 2xx. } card.wallet_status: post: tags: [Google Wallet] operationId: whCardWalletStatus summary: 'Webhook: card.wallet_status' description: The card became active (or was suspended/removed) in Google Wallet. We track token status on our side — no polling needed. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: card_id: { type: string } wallet: { type: string, enum: [google, apple] } status: { type: string, enum: [active, suspended, removed] } responses: '200': { description: Acknowledge with any 2xx. } payment_method.added: post: tags: [Payment Methods] operationId: whPaymentMethodAdded summary: 'Webhook: payment_method.added' description: A customer card was linked successfully. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: $ref: '#/components/schemas/PaymentMethod' responses: '200': { description: Acknowledge with any 2xx. } topup.succeeded: post: tags: [Top-ups] operationId: whTopupSucceeded summary: 'Webhook: topup.succeeded' description: Top-up completed; prepaid credit updated. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: topup_id: { type: string, examples: [top_5f1Kc] } user_id: { type: string } amount_minor: { type: integer, examples: [2000] } currency: { type: string, examples: [GBP] } new_balance_minor: { type: integer, examples: [3550] } responses: '200': { description: Acknowledge with any 2xx. } topup.failed: post: tags: [Top-ups] operationId: whTopupFailed summary: 'Webhook: topup.failed' description: Top-up failed with a reason code. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: topup_id: { type: string } reason: type: string enum: [payment_declined, limit_exceeded, cancelled_by_user, expired] responses: '200': { description: Acknowledge with any 2xx. } transaction.authorized: post: tags: [Transactions] operationId: whTransactionAuthorized summary: 'Webhook: transaction.authorized' description: Real-time purchase authorisation — use for the instant push and stamp logic. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: tx_id: { type: string, examples: [tx_2Nc8d] } user_id: { type: string } card_id: { type: string } merchant_id: { type: string, examples: [mer_41Ka] } merchant_name: { type: string, examples: [Coffee X Soho] } amount_minor: { type: integer, examples: [450] } currency: { type: string, examples: [GBP] } responses: '200': { description: Acknowledge with any 2xx. } transaction.settled: post: tags: [Transactions] operationId: whTransactionSettled summary: 'Webhook: transaction.settled' description: Final cleared amount (T+1/2). Cashback accrual (if any) follows as `cashback.accrued`. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: tx_id: { type: string } final_amount_minor: { type: integer, examples: [450] } currency: { type: string, examples: [GBP] } responses: '200': { description: Acknowledge with any 2xx. } transaction.reversed: post: tags: [Transactions] operationId: whTransactionReversed summary: 'Webhook: transaction.reversed' description: Purchase reversed/refunded — revert stamps and cashback in the UI. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: tx_id: { type: string } responses: '200': { description: Acknowledge with any 2xx. } cashback.accrued: post: tags: [Merchants] operationId: whCashbackAccrued summary: 'Webhook: cashback.accrued' description: Cashback accrued for a settled purchase; status `pending` until the merchant-defined pending period ends. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: cashback_id: { type: string, examples: [cb_8Ry2t] } tx_id: { type: string } user_id: { type: string } amount_minor: { type: integer, examples: [45] } status: { type: string, enum: [pending] } confirm_expected_at: { type: string, format: date } responses: '200': { description: Acknowledge with any 2xx. } cashback.paid: post: tags: [Merchants] operationId: whCashbackPaid summary: 'Webhook: cashback.paid' description: Cashback confirmed and paid to the card balance automatically. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: cashback_id: { type: string } user_id: { type: string } amount_minor: { type: integer, examples: [45] } new_balance_minor: { type: integer, examples: [3595] } responses: '200': { description: Acknowledge with any 2xx. } cashback.expired: post: tags: [Merchants] operationId: whCashbackExpired summary: 'Webhook: cashback.expired' description: Accrued cashback expired unused (per merchant terms). requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: cashback_id: { type: string } amount_minor: { type: integer } responses: '200': { description: Acknowledge with any 2xx. } discount.applied: post: tags: [Merchants] operationId: whDiscountApplied summary: 'Webhook: discount.applied' description: | An instant or slot discount was applied to a purchase. Prepaid: the discount returns to the balance within seconds. Post-pay: the linked card is charged the already-discounted amount. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/EventEnvelope' - type: object properties: data: type: object properties: tx_id: { type: string } slot_id: { type: [string, 'null'] } charged_minor: { type: integer, examples: [8500] } discount_minor: { type: integer, examples: [1500] } responses: '200': { description: Acknowledge with any 2xx. } components: securitySchemes: oauth2: type: oauth2 description: OAuth 2.0 Client Credentials. Credentials are issued per environment during onboarding. flows: clientCredentials: tokenUrl: https://auth.retail-wallet.com/oauth/token scopes: partner.api: Full partner API access parameters: IdempotencyKey: name: Idempotency-Key in: header required: true description: Unique key (e.g. UUID). Retries with the same key return the original result. schema: type: string examples: [4f9e2c1a-7b3d-4e8f-9a21-6c5d8e7f0a1b] UserId: name: user_id in: path required: true schema: type: string description: Retail Wallet user ID (`usr_…`). CardId: name: card_id in: path required: true schema: type: string description: Card ID (`card_…`). responses: BadRequest: description: Validation error. content: application/json: schema: { $ref: '#/components/schemas/Error' } NotFound: description: Resource not found. content: application/json: schema: { $ref: '#/components/schemas/Error' } Duplicate: description: Duplicate (idempotency or unique constraint). content: application/json: schema: { $ref: '#/components/schemas/Error' } Unprocessable: description: Business rule rejection. content: application/json: schema: { $ref: '#/components/schemas/Error' } schemas: Address: type: object required: [street, city, postal_code, country] properties: street: { type: string, examples: [221B Baker Street] } city: { type: string, examples: [London] } postal_code: { type: string, examples: [NW1 6XE] } country: type: string description: ISO 3166-1 alpha-2. Pilot — `GB` only. examples: [GB] UserCreateRequest: type: object required: [external_user_id, first_name, last_name, date_of_birth, email, phone, residential_address] properties: external_user_id: type: string description: Partner's stable user ID — the key all future calls map to. examples: [emb_user_18442] first_name: { type: string, description: Legal first name as on ID document., examples: [Jane] } last_name: { type: string, description: Legal last name as on ID document., examples: [Doe] } date_of_birth: type: string format: date description: User must be 18+. examples: ['1994-05-14'] email: { type: string, format: email, examples: [jane@example.com] } phone: type: string description: E.164. Used for card notifications and 3-D Secure. examples: ['+447700900123'] residential_address: $ref: '#/components/schemas/Address' UserCreateResponse: type: object required: [user_id, status, kyc_sdk_token] properties: user_id: { type: string, examples: [usr_7Hq1k] } status: type: string enum: [kyc_pending] kyc_sdk_token: type: string description: One-time token for the KYC-check SDK screen in the app. examples: [kyc_tok_2mQ9x] User: type: object properties: user_id: { type: string, examples: [usr_7Hq1k] } external_user_id: { type: string, examples: [emb_user_18442] } status: type: string enum: [kyc_pending, active, blocked, kyc_rejected] created_at: { type: string, format: date-time } CardCreateRequest: type: object required: [user_id, program] properties: user_id: { type: string, examples: [usr_7Hq1k] } program: type: string enum: [network, merchant_locked] description: > `network` — accepted at all participating merchants; `merchant_locked` — single-merchant card (requires `merchant_ref`). merchant_ref: type: [string, 'null'] description: Required when `program = merchant_locked`. examples: [mer_41Ka] Card: type: object properties: card_id: { type: string, examples: [card_3Vb9s] } user_id: { type: string, examples: [usr_7Hq1k] } program: { type: string, enum: [network, merchant_locked] } last4: { type: string, examples: ['4321'] } design: type: string description: Card design code for rendering in the app. examples: [rw_embargo_default] status: { type: string, enum: [active, frozen, closed] } wallet_status: type: object description: Presence in phone wallets. properties: google: { type: string, enum: [not_added, active, suspended, removed] } apple: { type: string, enum: [not_available_yet] } created_at: { type: string, format: date-time } GooglePayProvisionRequest: type: object required: [client_device_id, client_wallet_account_id] properties: client_device_id: type: string description: From TapAndPay `getStableHardwareId()`. client_wallet_account_id: type: string description: From TapAndPay `getActiveWalletId()`. PaymentMethod: type: object properties: pm_id: { type: string, examples: [pm_6Tt3e] } user_id: { type: string } brand: { type: string, examples: [visa] } last4: { type: string, examples: ['4242'] } created_at: { type: string, format: date-time } TopupCreateRequest: type: object required: [user_id, amount_minor, currency, method] properties: user_id: { type: string, examples: [usr_7Hq1k] } amount_minor: type: integer description: 'Pence. Pilot: 100–20000 per top-up, 40000 per month.' examples: [2000] currency: { type: string, enum: [GBP] } method: type: string enum: [card, pay_by_bank] payment_method_id: type: [string, 'null'] description: Saved card for one-tap top-up (`method = card` only). TopupCreateResponse: type: object required: [topup_id] properties: topup_id: { type: string, examples: [top_5f1Kc] } payment_session: type: [string, 'null'] description: Present when `method = card` — pass to the card payment sheet. bank_session: type: [string, 'null'] description: Present when `method = pay_by_bank` — pass to the pay-by-bank sheet. Topup: type: object properties: topup_id: { type: string, examples: [top_5f1Kc] } user_id: { type: string } amount_minor: { type: integer, examples: [2000] } currency: { type: string, examples: [GBP] } method: { type: string, enum: [card, pay_by_bank] } status: { type: string, enum: [pending, succeeded, failed] } failure_reason: type: [string, 'null'] enum: [payment_declined, limit_exceeded, cancelled_by_user, expired, null] created_at: { type: string, format: date-time } Wallet: type: object properties: balance_minor: type: integer description: Current prepaid credit. examples: [3550] available_balance_minor: type: integer description: Minus amounts blocked by in-flight authorisations. examples: [3100] cashback_pending_minor: type: integer description: Accrued, still in the merchant-defined pending period. examples: [45] cashback_confirmed_minor: type: integer description: Confirmed; paid to the balance automatically. examples: [0] currency: { type: string, examples: [GBP] } Transaction: type: object properties: tx_id: { type: string, examples: [tx_2Nc8d] } type: { type: string, enum: [purchase, topup, cashback, refund] } status: { type: string, enum: [pending, completed, reversed] } date: { type: string, format: date-time } amount_minor: type: integer description: Negative for purchases, positive for credits. examples: [-450] currency: { type: string, examples: [GBP] } merchant_name: { type: [string, 'null'], examples: [Coffee X Soho] } merchant_id: { type: [string, 'null'], examples: [mer_41Ka] } mcc: { type: [string, 'null'], examples: ['5814'] } cashback_amount_minor: { type: [integer, 'null'], examples: [45] } cashback_status: { type: [string, 'null'], enum: [pending, confirmed, paid, expired, reversed, null] } CashbackTerms: type: object properties: rate_percent: { type: number, examples: [10] } pending_days: { type: integer, examples: [7] } expiry_days: { type: integer, examples: [90] } spend_scope: type: string enum: [earning_merchant_only, all_participating] description: Where the earned cashback can be spent. Merchant: type: object properties: merchant_id: { type: string, examples: [mer_41Ka] } name: { type: string, examples: [Coffee X] } category: { type: string, examples: [Coffee & Bakery] } cashback: oneOf: - $ref: '#/components/schemas/CashbackTerms' - type: 'null' instant_discount_percent: type: [number, 'null'] description: One reward per purchase — cashback or instant discount, never both. logo_url: { type: [string, 'null'], format: uri } DiscountSlot: type: object properties: slot_id: { type: string, examples: [slot_9Qe4v] } merchant_id: { type: string, examples: [mer_41Ka] } starts_at: { type: string, format: date-time, examples: ['2026-07-24T18:00:00Z'] } ends_at: { type: string, format: date-time, examples: ['2026-07-24T20:00:00Z'] } rate_percent: { type: number, examples: [20] } capacity: { type: integer, examples: [10] } remaining: { type: integer, examples: [7] } EventEnvelope: type: object required: [event_id, type, created_at, data] properties: event_id: type: string description: Deduplicate by this ID — a delivery may arrive more than once. examples: [evt_01J2ZK8] type: { type: string, examples: [transaction.authorized] } created_at: { type: string, format: date-time } data: type: object description: Event-specific payload. Error: type: object required: [error] properties: error: type: object required: [code, message] properties: code: { type: string, examples: [validation_failed] } message: { type: string, examples: ['date_of_birth: user must be 18 or older'] } request_id: { type: string, examples: [req_9f27c1] }