Core concepts
Everything in Tuppence belongs to one account, in one mode (test or live). The payment is central: links, invoices and saved cards create payments; refunds, disputes and events follow from them; payouts take their money to your bank.
The objects
Section titled “The objects”| Object | What it is | Read more |
|---|---|---|
Payment pay_ | An amount to be paid, by a card (or, in the UK, Bacs Direct Debit). Paid on the hosted page (approval_url), in your checkout (client_secret), or from a saved card | Take a payment |
Payment link link_ | A URL anyone can pay; each payment through it has payment_link set | Payment links |
Customer cus_ | Who pays: email, name, saved methods | Saved cards |
Saved payment method pm_ | A card or bank account kept under a mandate (mdt_) the customer agreed to, charged with nobody there | Saved cards |
Plan plan_, subscription sub_, invoice inv_ | What you sell on repeat; a customer paying for it; one invoice per charge | Subscriptions |
Refund rfnd_ | Money back on a payment, whole or part | Refunds |
Dispute dp_ | A cardholder asking their bank for the money back | Disputes |
Event evt_, webhook endpoint we_ | What happened, delivered signed to your URL | Webhooks |
Balance, payout po_ | Money waiting to clear, then paid to your bank | Payouts |
Account acct_, connected account | Your business; on Connect, a business your platform runs, acted as with Tuppence-Account: acct_… | Connect |
API key key_ | What your code authenticates with (below) | Going live |
A payment’s life
Section titled “A payment’s life”A declined card leaves it requires_payment_method, payable again (payment.failed). Each move
sends an event: payment.created, .requires_action, .requires_capture, .processing,
.succeeded, .failed, .canceled. Statuses in full →
Every request
Section titled “Every request”| Base URL | https://api.tuppence.ai/v1/…, JSON in and out |
| Auth | Authorization: Bearer sk_test_… |
| Amounts | Integers in the minor unit: 2000 is £20.00 or $20.00 |
| Idempotency | Idempotency-Key: <uuid> on every POST; a retry returns the first result (Idempotent-Replayed: true). Kept 24 hours. Required on payments, refunds, payouts and credit top-ups; the SDKs send one for you. More → |
| Version | Dated, pinned per account. Tuppence-Version: 2026-09-20 chooses one. More → |
| Acting for another account | Tuppence-Account: acct_… (Connect) |
| Rate limit | Per key: 100/s live, 25/s test. 429 carries Retry-After |
Errors
Section titled “Errors”{ "error": { "type": "invalid_request_error", "code": "amount_too_large", "message": "Only 1500 is left to refund on this payment.", "param": "amount", "request_id": "req_01J8ZKF8G0H2J4K6M8N0P2Q4R6" }}Branch on code (stable; never renamed), never on message. Quote request_id to support.
Retry only network errors, 5xx, 429 and 409 idempotency_in_progress.
type | HTTP |
|---|---|
invalid_request_error | 400, 404, 409, 410 |
authentication_error | 401 |
permission_error | 403 |
card_error | 402 |
idempotency_error | 409 |
rate_limit_error | 429 |
api_error | 5xx |
{ "object": "list", "data": [{ "id": "pay_01K…" }], "has_more": true }Newest first. ?limit= 1–100 (default 10); page on with starting_after=<last id>, or back with
ending_before=<first id>. The SDKs’ listAll() / list_all() walk every page for you.
<prefix>_<ULID>: 26 upper-case characters that sort by creation time, which is why an id works as
a list cursor. The prefix says what it is: pay_, link_, cus_, pm_, mdt_, plan_,
sub_, inv_, rfnd_, dp_, evt_, we_, po_, acct_, sets_ (setup session),
tr_ (transfer), fee_ (application fee), req_ (request).
Events
Section titled “Events”{ "id": "evt_…", "object": "event", "type": "payment.succeeded", "livemode": false, "created": 1790000000, "api_version": "2026-09-20", "request_id": "req_…", "data": { "object": { "id": "pay_…", "object": "payment", "amount": 2000 } }}Signed with Tuppence-Signature; retried for 72 hours until you answer 2xx. Connect events add
account. Webhooks →
Keys and modes
Section titled “Keys and modes”| Key | Where | Can |
|---|---|---|
sk_test_… / sk_live_… | Your server only | Everything the account can |
rk_test_… / rk_live_… | A service or an agent | Only its scopes (payments:read, …), optionally from fixed IPs |
pk_test_… / pk_live_… | The browser | Pay one payment it holds the client_secret for |
whsec_… | Your webhook handler | Verify deliveries (not an API key) |
| Test mode | Live mode | |
|---|---|---|
| Money | None moves | Real |
| Keys | From sign-up, under Developers → API keys | Take payments once your business is verified |
| Cards | Test cards | Real cards |
| Time | Test clock and helpers | Real time |
| Data | Never sees live data | Never sees test data |
Same API, same code: only the key changes. Going live →
- Quickstart: a payment link in two minutes.
- Use cases: which objects your integration needs.