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: |
# 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] }