API reference
Sandbox endpoints
The four test-mode endpoints: tokenise a magic card, advance an async payment, simulate a webhook, and read the card table from the API.
Updated
Any cp_*_test_ key routes to the simulator. There is no mode parameter: the key is the environment, and a test key cannot reach a live connector however hard you try.
These four endpoints exist in test mode only. A live key gets 403 authorization_error with code test_mode_required.
The card numbers, the outcome overrides and the challenge-page behaviour are on the sandbox and test cards page, rendered from the simulator's own module. This page is the endpoint reference.
Tokenise a card
POST /v1/sandbox/tokens → 201. The only endpoint in the API that accepts card data. Publishable keys may call it, because this is what hosted fields do from the browser.
| Parameter | Required | Notes |
|---|---|---|
type | no | card (default), apple_pay, google_pay. |
number | yes | A magic card number. Spaces and dashes are fine. |
exp_month | yes | 1–12. |
exp_year | yes | Any future year. |
cvc | yes | 3 or 4 digits. Any value. |
name | no | ≤ 200 characters. |
curl https://cleared-pay.com/api/v1/sandbox/tokens \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"card","number":"4242424242424242","exp_month":12,"exp_year":2029,"cvc":"123"}'{
"id": "tok_01JBXQ2M4K7T9V3ZC8HR5N6PWD",
"object": "token",
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"bin": "424242",
"exp_month": 12,
"exp_year": 2029
},
"livemode": false,
"created": "2026-08-19T10:15:00Z",
"expires_at": "2026-08-19T10:30:00Z"
}What is kept: brand, last4, BIN, expiry, a fingerprint, and the outcome the number maps to. What is not: the number and the security code. They exist for the duration of the request and are never written anywhere, including the request log, which redacts card-shaped strings as a second line of defence.
Tokens are single-use and valid for 15 minutes. A number outside the magic-card table is 400 invalid_card_number; a past expiry is 400 card_expired. The endpoint ignores Idempotency-Key — a token is single-use, so replaying one would give you a token you cannot spend.
Advance an async payment
POST /v1/sandbox/advance/:paymentId → 200 with the payment.
curl -X POST https://cleared-pay.com/api/v1/sandbox/advance/pay_01JBXQ6P9R4T2V7XZ1BD5GH8KM \
-H "Authorization: Bearer $CLEARED_SECRET_KEY"Resolves a processing payment now instead of waiting out its 30 seconds — useful in a test suite, where waiting is the slowest thing you will do. A payment in any other status is 422 payment_not_advanceable, naming the status it is in.
A processing payment also resolves lazily on read once its resolve time has passed, so GET /v1/payments/:id after 30 seconds gives the same result without this call.
Simulate a webhook
POST /v1/sandbox/simulate-webhook → 201 with the event and its delivery attempts.
| Parameter | Required | Notes |
|---|---|---|
type | yes | Any event type. |
endpoint | no | A whe_ id. Omitted, every subscribed endpoint receives it. |
resource | no | An id of yours to use as data.object. Omitted, the newest matching resource is used; with none, a clearly synthetic sample. |
curl https://cleared-pay.com/api/v1/sandbox/simulate-webhook \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"payment.captured","resource":"pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD"}'The event is stored like any other — real id, real signature, livemode: false — and delivered inline, so the HTTP result of your receiver comes back in the response rather than in a log you have to go and find:
{
"id": "evt_01JBXQ9R4V8X6Z2B5DG9KL3NQS",
"object": "event",
"type": "payment.captured",
"api_version": "2026-08-19",
"livemode": false,
"created": "2026-08-19T10:17:00Z",
"data": { "object": { "id": "pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD", "object": "payment", "status": "captured" } },
"attempts": [
{
"id": "wha_01JBXQ9R6X2Z8B4D7FH1MN5QTV",
"object": "webhook_attempt",
"event": "evt_01JBXQ9R4V8X6Z2B5DG9KL3NQS",
"endpoint": "whe_01JBXQ8Q2T6V4X9Z3CF7HJ1LNP",
"attempt": 1,
"status": "delivered",
"http_status": 200,
"response_body": "ok",
"error": null,
"duration_ms": 142,
"next_retry_at": null,
"attempted_at": "2026-08-19T10:17:00Z"
}
]
}A synthetic sample uses obviously fake ids such as pay_SIMULATED0000000000000000, so nothing in your logs can be mistaken for a real resource. If no endpoint is subscribed to the type you asked for, the answer is 422 no_matching_endpoint.
Signatures on a simulated event are real, so this is the right way to test your verification code — including the rotation case: roll the secret, then simulate, and check that your receiver accepts a header carrying two v1= values.
Read the card table
GET /v1/sandbox/test-cards → 200 with the same table the sandbox page renders and the simulator reads, so a test fixture can be generated from it instead of copied.
curl https://cleared-pay.com/api/v1/sandbox/test-cards \
-H "Authorization: Bearer $CLEARED_SECRET_KEY"{
"object": "list",
"data": [
{
"number": "4242 4242 4242 4242",
"brand": "visa",
"outcome": "approved",
"label": "Approved"
},
{
"number": "4000 0000 0000 9995",
"brand": "visa",
"outcome": "declined",
"decline_code": "insufficient_funds",
"decline_is_soft": true,
"label": "Declined — insufficient_funds (soft)"
}
],
"metadata_overrides": {
"_cp_test_outcome": ["approved", "declined:<decline_code>", "failed", "requires_action"],
"_cp_test_latency_ms": "integer, delays the response",
"_cp_test_http_error": "HTTP status to return instead of processing"
},
"has_more": false,
"next_cursor": null
}Abridged above; the response carries all nine cards. A publishable key may call this one too.
What the sandbox does not simulate
- Wallets. Apple Pay and Google Pay need a real device flow. The hosted checkout page excludes them in test mode rather than faking a result.
- Settlement and payouts. Fees are calculated and snapshotted at capture, so the numbers are real; money never moves.
- Disputes. The event types exist; there is no way to raise one yet.
- Issuer variety. Every simulated card is issued in
EEwithfunding: "credit". Do not write logic that depends on issuer country until you have live data.