Collections
Collect money from your payers over open banking. You open a collection, Swappr returns a checkout_url, and your payer completes the payment inside their own bank. When it settles, the amount is credited to your wallet in that currency.
Available in GBP and EUR.
How it differs from a virtual account
A virtual account is a standing bank account you hand out once, and anything paid into it is yours because the account number identifies you. A collection is the opposite shape: it is opened per payment, for a specific amount, and it is Swappr’s record of that collection — not the account the money lands in — that routes the funds to your wallet.
Two things follow from that, and both are worth designing around:
- The
checkout_urlis the deliverable. Creating a collection does not move money; it produces a link. Surface it however suits you — redirect the payer, put it in an invoice, render it behind a button. - The destination wallet is fixed when the collection is opened. It is not re-derived at settlement, so the funds land where you expected even if your account configuration changes while the payer is paying.
Before you collect
- Enable the capability on your key. The API key must carry the collections capability — enable Create collections on the key under API keys. A key without it gets
403 forbidden_collection_create. This is a property of the key itself, so it keeps working regardless of later changes to the team member who created it. - You need a wallet in that currency. Collections credit an existing GBP or EUR wallet. If you hold no wallet in the currency, the request is rejected — there is no separate enrolment step beyond having the wallet.
Idempotency-Keyis required. A retry with the same key returns the same collection and the samecheckout_url. That matters more here than elsewhere: a payer may already be part-way through paying, and a second checkout would leave one of them stranded.- There is a minimum amount. A collection must be worth more than it costs to collect — see Minimum amount.
POSTCreate collection
Open a hosted checkout and get back the link to send your payer to.
The amount to request from the payer, in minor units (string for values > 2⁵³). Must exceed the cost of collecting it — see Minimum amount.
GBP or EUR.
Where the payer is returned after paying or abandoning. Must be https.
Shown to the payer on the checkout page. Up to 255 characters.
Your own reference — an invoice number, an order id. Echoed back on every representation of the collection. It is never used to match the payment, so it does not need to be unique.
Arbitrary key-value data stored with the collection and returned unchanged.
Response fields
| Field | Type | Notes |
|---|---|---|
object | string | Always collection. |
id | string | The collection id. Use it to retrieve the collection later. |
reference | string | null | Your reference, exactly as you sent it. null if you sent none. |
amount_minor | string | What you asked the payer for (minor units). |
settled_amount_minor | string | null | What actually arrived. null until settlement, and it may differ from amount_minor. |
currency | string | GBP or EUR. |
status | string | See Lifecycle. |
checkout_url | string | null | Where to send your payer. |
expires_at | string | null | After this the checkout can no longer be paid. |
settled_at | string | null | When the funds settled. |
settlement_rail | string | null | The rail the funds arrived over. Only meaningful once settled — and it affects timing, so read it rather than assuming. |
failure_code / failure_message | string | null | Present on a terminal failure. |
env | string | test or live. |
metadata | object | null | Whatever you stored. |
created_at | string | ISO 8601. |
Errors
| Code | HTTP | Cause |
|---|---|---|
idempotency_key_required | 400 | The Idempotency-Key header was absent. |
missing_field | 400 | amount_minor, currency or redirect_url was not supplied. |
invalid_amount | 400 | amount_minor is not a positive integer. |
invalid_format | 400 | redirect_url is not https, or narration / reference / metadata is the wrong shape or too long. |
invalid_json / invalid_body | 400 | The body is not a JSON object. |
forbidden_collection_create | 403 | The API key is not enabled for collections. |
wallet_frozen | 403 | The destination wallet is frozen and cannot receive. |
collections_not_enabled | 403 | Collections are not enabled for this account. Account-level and not retryable — contact support to enable them. |
collections_paused | 403 | Collections are temporarily paused for this account. Not retryable — contact support. |
idempotency_key_conflict | 409 | The same Idempotency-Key was already used with a different body. |
currency_not_supported | 422 | The currency is outside GBP/EUR, or collections in it are not currently available. |
wallet_not_found | 422 | You hold no wallet in that currency in this environment. |
amount_below_minimum | 422 | The amount does not exceed the cost of collecting it. See below. |
collection_create_failed | 502 | The checkout could not be opened. No payment was requested — retry with a fresh Idempotency-Key. |
collections_unavailable | 503 | Collections are temporarily not being opened. |
collection_cost_unpriceable | 503 | The collection could not be priced. Contact support if it persists. |
collections_disabled | 503 | Collections are paused platform-wide. Temporary and platform-side — retry with backoff. |
collections_rail_disabled | 503 | The collection rail is unavailable for this environment. Temporary and platform-side — retry with backoff. |
The two 503 codes collections_disabled and collections_rail_disabled return the same message — “Collections are temporarily unavailable. Please try again later.” The code is the only thing that distinguishes them; both are temporary and platform-side, so retry with backoff.
# Collect £1,500.00 (150000 minor) from a payer
curl -X POST 'https://api.swappr.me/api/v1/collections' \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": "150000",
"currency": "GBP",
"redirect_url": "https://merchant.example/collections/return",
"narration": "Invoice 4021",
"reference": "INV-4021"
}'{
"object": "collection",
"id": "clx8k2p0000abc123def456",
"reference": "INV-4021",
"amount_minor": "150000",
"settled_amount_minor": null,
"currency": "GBP",
"status": "awaiting_user",
"checkout_url": "https://checkout.example/c/01KZ4M8N2P",
"expires_at": "2026-07-29T15:25:00.000Z",
"settled_at": null,
"settlement_rail": null,
"failure_code": null,
"failure_message": null,
"env": "live",
"metadata": null,
"created_at": "2026-07-29T15:00:00.000Z"
}Lifecycle
A collection moves through these states. Only settled means money reached you.
| Status | Meaning |
|---|---|
pending | Opened on our side; the checkout is being prepared. |
awaiting_user | Waiting for the payer to authorise the payment in their bank. |
processing | The payer has authorised; the payment is in flight. |
pending_settlement | Authorised and clearing. The funds have not arrived yet. |
settled | The funds arrived and your wallet has been credited. |
failed | Terminal. The payment did not complete. |
expired | Terminal. The checkout was not paid before expires_at. |
cancelled | Terminal. The payment was cancelled. |
Wait for settled — and prefer the webhook. processing and pending_settlement mean a payer has authorised a payment, not that money has arrived, and a payment can still fail after authorisation. Release goods or credit a customer on settled only.
Knowing when it settles
You do not need to poll. A settled collection credits your wallet and fires the standard wallet_funded webhook, which is the same event a virtual-account credit produces — so if you already handle wallet funding, settlement is already wired.
settled_amount_minor is the authoritative figure for what arrived, and it can differ from amount_minor. Reconcile on the settled amount, not the requested one.
Minimum amount
Collecting money costs money, and that cost does not scale to zero. A collection small enough that the cost swallows it can complete successfully while leaving nothing to credit — so Swappr refuses to open one, rather than letting your payer pay for a transfer that delivers nothing.
If the amount is too small you get 422 amount_below_minimum, and the error tells you both figures:
{
"error": {
"type": "invalid_request_error",
"code": "amount_below_minimum",
"message": "amount_minor 100 is not greater than the provider cost 500 for a GBP collection, so the collection could settle without the funds ever arriving. The minimum accepted amount_minor is 501.",
"field": "amount_minor",
"detail": {
"provider_cost_minor": "500",
"minimum_amount_minor": "501"
}
}
}Read detail.minimum_amount_minor rather than deriving it — the cost is not always a flat figure, so the minimum is not always “cost plus one”.
GETRetrieve collection
Fetch one collection. Reading needs no capability beyond authentication — Create collections governs opening one, not looking at it.
A collection belonging to another account, or to the other environment, returns the same 404 as one that does not exist.
| Code | HTTP | Cause |
|---|---|---|
collection_not_found | 404 | No collection with that id is visible to this key. |
curl 'https://api.swappr.me/api/v1/collections/clx8k2p0000abc123def456' \
-H "Authorization: Bearer sk_live_..."GETList collections
Your collections, newest first, scoped to the environment of the API key.
Filter by lifecycle status — any value from Lifecycle.
Filter by GBP or EUR.
1–100. Defaults to 50.
Cursor — the id of the last collection on the previous page.
| Code | HTTP | Cause |
|---|---|---|
invalid_limit | 400 | limit is outside 1–100. |
invalid_filter | 400 | status or currency is not a recognised value. |
curl 'https://api.swappr.me/api/v1/collections?status=settled¤cy=GBP&limit=20' \
-H "Authorization: Bearer sk_live_..."{
"object": "list",
"has_more": false,
"data": [
{
"object": "collection",
"id": "clx8k2p0000abc123def456",
"reference": "INV-4021",
"amount_minor": "150000",
"settled_amount_minor": "150000",
"currency": "GBP",
"status": "settled",
"checkout_url": "https://checkout.example/c/01KZ4M8N2P",
"expires_at": "2026-07-29T15:25:00.000Z",
"settled_at": "2026-07-29T15:11:42.000Z",
"settlement_rail": "bacs",
"failure_code": null,
"failure_message": null,
"env": "live",
"metadata": null,
"created_at": "2026-07-29T15:00:00.000Z"
}
]
}Sandbox
A sandbox (sk_test_…) key opens sandbox collections against your sandbox wallets; a live key opens live ones. The two are entirely separate — a sandbox key can neither see nor create a live collection, and Idempotency-Key values are namespaced per environment.