Skip to content

Testing

Everything works in test mode with a test key (sk_test_…), and no money moves. Test and live data never mix: a test key can’t see a live payment, and the dashboard shows the mode on every page.

KeysEvery account gets a standard test pair (secret and publishable) at sign-up, under Developers → API keys. When you’re signed in, these pages fill them into the examples
Standard keysCan be rolled but never revoked, so there’s always one to start from
UnderneathA full test environment: the test cards, bank accounts and timings below behave as live ones do

Wherever you type a card (our payment page, a payment link, @tuppence/js fields), use any future expiry, any CVC (four digits for Amex) and any postcode or ZIP code. On your server, confirm with the name instead: POST /v1/payments/{id}/confirm with payment_method: "pm_card_gb"payment_method: "pm_card_us"payment_method: "pm_card_de" (more).

Card numberWhat happensServer-side name
4000 0582 6000 0005Succeeds (UK Visa debit)pm_card_gb
4242 4242 4242 4242Succeeds (US Visa)pm_card_us, pm_card_visa
3782 822463 10005Succeeds (Amex)pm_card_amex
4000 0000 0000 0077Succeeds; money available at oncepm_card_bypassPending
4000 0037 2000 0278Succeeds; money available at once (issued abroad)pm_card_bypassPendingInternational
4000 0025 0000 3155The bank checks it’s the cardholder (3D Secure)pm_card_authenticationRequired
4000 0000 0000 0002Declined, generic_declinepm_card_chargeDeclined
4000 0000 0000 9995Declined, insufficient_fundspm_card_chargeDeclinedInsufficientFunds
4000 0000 0000 0259Succeeds, then disputed as fraudulentpm_card_createDispute

A card issued outside your account’s country pays the foreign-card rate: the US cards here on a UK account, the UK card on a US account.

A test payment’s money stays pending for days, as in live mode, and no API releases it sooner.

You wantDo
Money to pay out at oncePay with pm_card_bypassPending (Visa •••• 0077) or pm_card_bypassPendingInternational (•••• 0278). The money, and a platform’s fee on it, are available straight away. Issued in the US and Ireland: the foreign-card rate applies to a card from outside your account’s country
To pay out pending moneyAn instant payout (method: "instant"): it’s advanced to you, in test as in live. Once a new account’s first 168 hours are up and it has seasoned (£1,000$1,000€1,000 from at least four cards, seven days old; move the clock to get there), instant_payouts.destinations.bank on GET /v1/payout_settings says whether you can, or why not (reason, message, fix_url)
To see a payment clear your settlement daysMove your test clock: seven days while the account is new, then three

Money from any other test card shows on Balance under Pending (still clearing). The clock moves Tuppence’s settlement hold, not the card’s own pending period, so that money stays pending however far you move the clock.

  1. Paypm_card_bypassPending
  2. Move timeclock/advance 7 days
  3. Pay outPOST /v1/payouts
  4. Paidthe test bank
Terminal window
curl https://api.tuppence.ai/v1/test_helpers/clock/advance \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Content-Type: application/json" -d '{"days": 7}'
curl https://api.tuppence.ai/v1/payouts \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" -X POST

A test payout settles by itself, depending on the bank account’s number. To see one bounce, set a failing test account as your bank account and pay out to it. The money returns to your available balance and you receive payout.failed.

Sort code 10-88-00, accountResult
00012345 (the test bank, or the account test onboarding attaches)Paid
11111116Fails no_account
11111113Fails account_closed
22222227Fails insufficient_funds
66666668Takes no instant payout
Routing number 110000000, accountResult
000123456789 (the test bank, or the account test onboarding attaches)Paid
000111111116Fails no_account
000111111113Fails account_closed
000222222227Fails insufficient_funds

pm_card_authenticationRequired makes the bank check that the payer is the cardholder. Only the payer’s browser can answer: on our payment page (or @tuppence/js on yours), a test challenge opens with buttons to complete or fail it. Server-side examples, with no payer present, use a card without the check, such as pm_card_gbpm_card_uspm_card_de.

Helpers do on demand what would otherwise take days, a bank or an angry cardholder. They work in test mode only, and all are POST:

/v1/test_helpers/…Does
payments/{id}/disputeThe cardholder disputes a payment (reason optional)
disputes/{id}/closeThe bank decides: outcome: "won" or "lost"
webhook_inboxesA URL that receives your webhooks
clock/advance, clock/resetMove time
issuing/fundUK: a bank transfer of amount arrives in the card balance, spendable at once (Top up the cards). US cards spend cleared earnings instead: a test payment, once it has cleared
issuing/authorizationsA purchase on an agent’s card (card, amount, merchant), decided by its rules as a real one would be. It’s declined insufficient_funds first if the cards can’t cover it. Options below
issuing/authorizations/{id}/captureThe merchant takes the money (all, or amount); the rest of the hold is released
issuing/authorizations/{id}/reverseThe merchant lets the hold go
issuing/transactions/{id}/refundThe merchant refunds a capture: all, or amount
card_disputes/{id}/closeThe network decides a submitted card dispute: outcome: "won" (money back to what the cards can spend) or "lost"; or a draft runs out of time: "expired"
cards/{id}/shipping/{step}The post moves a physical card: ship, deliver, fail or return
accounts/{id}/verifyA connected account passes verification with the test values; it can take payments once the checks finish (charges_enabled; up to a minute or two)
accounts/{id}/requirementsSet what verification asks of a connected account: currently_due, past_due, pending_verification, disabled_reason, current_deadline. Anything past due turns charges and payouts off
bookings/seed_weekA plausible week of bookings for example clients at example.test, from your own services, staff and hours (week: any day of it, next week by default; location; density 1–100). The same week every time, never twice. Example bookings message nobody and reach no calendar
bookings/remove_seededEvery seeded week removed: the example bookings and the example clients nothing else names. Anything you’ve used since (money recorded against a booking, a note or form on a client) stays, and kept lists it

issuing/authorizations also takes:

FieldMakes it
merchant_currency (and merchant_amount)A purchase abroad, which costs FX
risk_assessment (fraud_risk: {level, score}, merchant_dispute_risk, card_testing_risk)What the fraud checks reported. A high score is declined suspected_fraud where your account’s limits apply
verification_data (cvc_check, expiry_check, address_line1_check, address_postal_code_check)What the card checks reported

In test mode, a card dispute is decided by its explanation. Each sends its card_dispute.* events, as a live one would.

explanationOutcome
"winning_evidence"Won as soon as it’s submitted
"losing_evidence"Lost as soon as it’s submitted
anything elseStays submitted until POST /v1/test_helpers/card_disputes/{id}/close (outcome: won or lost; expired runs a draft out of time)
Terminal window
curl https://api.tuppence.ai/v1/card_transactions/ctx_…/disputes \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"reason": "fraudulent", "explanation": "winning_evidence", "submit": true}'

To get a purchase to dispute, fund the cards, then make and capture a purchase with the Issuing helpers above.

Card limits always apply in live mode. In test mode they apply only when limit enforcement is on for the account, so a card count never stops CI. enforced on GET /v1/issuing/limits says which. Our team switches it on, with the rest of our fraud checks, on request (support@tuppence.ai). Test mode then refuses cards and purchases over the limits exactly as live does.

Your account’s test mode can run ahead of real time. Every test-mode request then sees the new time: what you create is stamped then, and “the last 30 days” ends then. Live mode, and everyone else, stay on real time.

CallDoes
POST /v1/test_helpers/clock/advanceForward by days, hours, minutes, seconds (up to five years in all)
GET /v1/test_helpers/clockWhere it is
POST /v1/test_helpers/clock/resetBack to real time
Terminal window
curl https://api.tuppence.ai/v1/test_helpers/clock/advance \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Content-Type: application/json" -d '{"days": 30}'

Scheduled work (payout runs, statements, hourly usage rollups) still runs on real time. These exceptions follow the clock:

When you advance the clock
SubscriptionsRenewals in the skipped time are charged immediately, so you can test a year of billing in a second
Agents’ spending profilesWhat’s normal for each agent moves with it: a week of routine bought a day at a time reads as a week on GET /v1/agents/{id}/profile at once
BookingsLapsed holds and requests free their slots; long-over visits are marked no-shows where the business asked; recurring series fill the new weeks; waitlist offers pass on; late-cancellation and no-show fees the policy charges automatically are charged when due; a bill to charge after the visit is charged an hour after completion; three days after the booking page went live, the owner’s one email about what’s left goes

Point your tests at the test API with a test key, and use the helpers above for the slow parts. The SDKs read TUPPENCE_API_URL; these docs use it to run their own examples.

To use another Tuppence environment you’ve been given, change the address in one place:

WhereHow
@tuppence/node, tuppence (Python), @tuppence/mcpTUPPENCE_API_URL, or the baseUrl / base_url option
@tuppence/jsloadTuppence("pk_test_…", { apiBase: "https://…" }), and that address in your page’s connect-src
curl, or your own clientThat address in place of https://api.tuppence.ai

Keys belong to one environment: a key from one dashboard is refused by another’s API.

Docs assistant

Open as a page
Loading the assistant…