API reference
Payments
The payment object field by field, create and confirm, retrieve and list, captures including partial, cancel, and refunds.
Updated
A payment is the whole life of one charge: the authorisation, the capture, every refund against it, and the outcome data the issuer returned. It is the only object most integrations need.
The payment object
{
"id": "pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD",
"object": "payment",
"status": "captured",
"amount": 2000,
"amount_captured": 2000,
"amount_refunded": 0,
"currency": "eur",
"reference": "ORDER-10432",
"description": "Order 10432 — 1200 credits",
"capture_method": "automatic",
"method": {
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"bin": "424242",
"exp_month": 12,
"exp_year": 2029,
"issuer_country": "EE",
"funding": "credit",
"fingerprint": "fp_9a3c…",
"network_token": false
}
},
"customer": "cus_01JBXQ2M4K7T9V3ZC8HR5N6PWD",
"outcome": {
"response_code": "00",
"decline_code": null,
"decline_is_soft": null,
"avs_result": "Y",
"cvv_result": "M",
"three_ds": { "status": "authenticated", "eci": "05", "version": "2.2.0" },
"sca_exemption": null,
"risk_score": 12,
"risk_decision": "accept"
},
"fees": { "amount": 78, "currency": "eur", "breakdown": { "mdr": 58, "fixed": 20, "scheme": 0 } },
"return_url": "https://merchant.example/checkout/return",
"statement_descriptor": "CLRDPAY*MERCHANT",
"metadata": { "cart_id": "c_881" },
"livemode": false,
"created": "2026-08-19T10:15:00Z",
"authorized_at": "2026-08-19T10:15:01Z",
"captured_at": "2026-08-19T10:15:01Z",
"canceled_at": null
}| Field | Type | Notes |
|---|---|---|
id | string | pay_ + ULID. |
object | string | Always payment. |
status | enum | See the table below. |
amount | integer | Minor units, the amount requested. |
amount_captured | integer | What has actually been captured. 0 until capture. |
amount_refunded | integer | Sum of succeeded refunds. |
currency | string | Lowercase ISO 4217. |
reference | string | Yours, 1–64 characters, required. Not enforced unique. |
description | string or null | Up to 500 characters. Not shown to the payer. |
capture_method | enum | automatic or manual. |
method.type | enum | card, apple_pay, google_pay, ideal, blik, sepa_debit, … lowercase wire names. |
method.card | object or null | Present for card-like methods: brand, last4, bin, exp_month, exp_year, issuer_country, funding, fingerprint, network_token. Never a full number. |
method.wallet | string | Present on apple_pay and google_pay. |
method.details | object | Method-specific data for non-card methods. |
customer | string or null | cus_ id when a customer was attached. |
outcome.response_code | string or null | ISO 8583-style code. 00 is approved. |
outcome.decline_code | string or null | Populated on a decline. See errors. |
outcome.decline_is_soft | boolean or null | Whether a retry is safe. The only field to base a retry on. |
outcome.avs_result | string or null | Y, M, N, U, S, Z, A. |
outcome.cvv_result | string or null | M, N, P, U, S. |
outcome.three_ds | object or null | status, eci, version. |
outcome.sca_exemption | string or null | The exemption claimed, when one was. |
outcome.risk_score | integer or null | 0–100, higher is riskier. |
outcome.risk_decision | string or null | accept, review, reject. |
fees | object or null | Snapshotted at capture: amount, currency, breakdown.mdr, breakdown.fixed, breakdown.scheme. null before capture. Scheme pass-through is non-zero only on an IC++ schedule. |
return_url | string or null | Where the payer comes back after a redirect. |
statement_descriptor | string or null | 2–22 characters, letters, digits, spaces and * . -. |
metadata | object | Up to 50 keys, key ≤ 40 characters, value a string ≤ 500 characters, a number, a boolean or null. Never put card data or personal data here. |
livemode | boolean | false for everything created with a test key. |
created | timestamp | RFC 3339 UTC. |
authorized_at | timestamp or null | |
captured_at | timestamp or null | |
canceled_at | timestamp or null | |
next_action | object | Present only while status is requires_action. |
Statuses
status | Meaning |
|---|---|
pending | Created, no authorisation attempted yet. Waiting for confirm. Expires after 1 hour. |
requires_action | The payer must do something — a 3-D Secure challenge or an APM redirect. Expires after 1 hour. |
processing | An async rail is settling. Resolves on its own. |
authorized | Funds held, not taken. Expires after 7 days if not captured. |
captured | Taken. A partial capture also reports captured, with amount_captured < amount. |
partially_refunded | Some of the captured amount has been refunded. |
refunded | Fully refunded. |
disputed | A chargeback has been raised. |
declined | The issuer said no. outcome.decline_code says why. |
failed | Neither approved nor declined — a processing error. |
canceled | Voided before capture. |
expired | Timed out before the next step. |
Status is written in one place only, inside the same transaction as the event row and the webhook outbox row, so a status you read has always already produced its event. Parse defensively: treat an unrecognised status as "not terminal yet" and read the payment again.
next_action
"next_action": {
"type": "redirect",
"redirect": {
"url": "https://cleared-pay.com/3ds-test/sbx_4f0a2c6e9b314c0d9a7e1f2b",
"return_url": "https://merchant.example/return"
}
}Send the payer to redirect.url. They come back to your return_url. Read the payment again when they land — the redirect is not proof of an outcome.
Create a payment
POST /v1/payments → 201 with a payment object.
| Parameter | Required | Notes |
|---|---|---|
amount | yes | Positive integer, minor units. |
currency | yes | ISO 4217, lowercase. |
reference | yes | 1–64 characters. |
payment_method.type | yes | A wire method name. |
payment_method.token | no | A tok_ from hosted fields or the sandbox tokenizer. Omit it for the two-step flow; alternative methods never need one. |
capture_method | no | automatic (default) or manual. |
description | no | ≤ 500 characters. |
customer | no | { id? , email?, name?, phone?, billing_address? }. billing_address.country is a required 2-letter code when an address is given. |
return_url | no | Absolute https URL. Required in practice for any method that can redirect. |
statement_descriptor | no | 2–22 characters. |
metadata | no | See the object table. |
curl https://cleared-pay.com/api/v1/payments \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": 2000,
"currency": "eur",
"reference": "ORDER-10432",
"capture_method": "automatic",
"payment_method": { "type": "card", "token": "tok_01JBXQ2M4K7T9V3ZC8HR5N6PWD" },
"customer": {
"email": "buyer@example.com",
"name": "A Buyer",
"billing_address": { "line1": "1 Main St", "city": "Tallinn", "postal_code": "10151", "country": "EE" }
},
"return_url": "https://merchant.example/return",
"metadata": { "cart_id": "c_881" }
}'A raw card number in this body is rejected with raw_card_data_not_allowed before the schema runs. That refusal is what keeps your integration in SAQ A scope.
Synchronous outcomes
The HTTP status of the create call carries the outcome, and the payment exists either way:
| Outcome | Status | Body |
|---|---|---|
| Approved | 201 | The payment object. |
| Needs the payer | 201 | The payment object with next_action. |
| Async rail | 201 | The payment object, status: "processing". |
| Declined | 402 | card_error with decline_code and the payment id in message. |
| Processing failure | 422 | processing_error, code payment_failed, id in message. |
An idempotent replay returns the same error, not a fresh attempt. GET /v1/payments/:id always returns the object.
Confirm a two-step payment
Omit payment_method.token at create and the payment is pending. Attach the token later — after the payer has entered their card, or after your own risk check — with POST /v1/payments/:id/confirm.
curl https://cleared-pay.com/api/v1/payments/pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD/confirm \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"payment_method":{"type":"card","token":"tok_01JBXQ4N7P2R8T5WY9AC3EF6HK"}}'200 with the payment object, or the same 402 / 422 as create. Confirming a payment that is not pending is 422 payment_not_confirmable. A pending payment expires after an hour.
Retrieve a payment
GET /v1/payments/:id → 200, always the object, whatever the status. A processing payment past its resolve time is advanced before it is returned, so a read is never stale in the sandbox.
curl https://cleared-pay.com/api/v1/payments/pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD \
-H "Authorization: Bearer $CLEARED_SECRET_KEY"List payments
GET /v1/payments → a cursor-paginated list, newest first. Every filter combines with AND.
| Filter | Accepts |
|---|---|
status | One wire status or a comma-separated list: status=captured,refunded. |
currency | Lowercase ISO 4217. |
reference | Exact match on your reference. |
customer | A cus_ id. |
method_type | A wire method name, e.g. card, ideal. |
created[gte] | RFC 3339 or unix seconds. |
created[lte] | As above. |
amount[gte] | Integer, minor units. |
amount[lte] | As above. |
limit | 1–100, default 25. |
starting_after | A cursor or a bare pay_ id. |
ending_before | A cursor or a bare pay_ id. |
curl -G https://cleared-pay.com/api/v1/payments \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-d "status=captured,partially_refunded" \
-d "currency=eur" \
-d "created[gte]=2026-08-01T00:00:00Z" \
-d "amount[gte]=1000" \
-d "limit=50"An unknown status value is a 400 naming it, rather than a silently empty page.
Capture
POST /v1/payments/:id/captures → 200 with the payment. For capture_method: "manual", after authorized.
| Parameter | Required | Notes |
|---|---|---|
amount | no | Minor units, at most the authorised amount. Omit to capture in full. |
A partial capture releases the remainder and cannot be repeated: the payment stays captured with amount_captured below amount, and a payment.partially_captured event fires instead of payment.captured.
curl https://cleared-pay.com/api/v1/payments/pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD/captures \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":1500}'Fees are calculated once, at capture, and snapshotted on the payment — so the fees you read a year later are the fees that were charged, not a recalculation. A refused capture is 422 capture_failed and leaves the payment authorized; a payment in any other state is 422 payment_not_capturable. An uncaptured authorisation expires after 7 days.
Cancel
POST /v1/payments/:id/cancel → 200 with the payment, status: "canceled" and canceled_at set. Voids an authorisation before capture, and also cancels a pending or requires_action payment.
curl -X POST https://cleared-pay.com/api/v1/payments/pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD/cancel \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)"After capture there is nothing to cancel — refund instead. A captured or terminal payment answers 422 payment_not_cancelable.
Refund
POST /v1/payments/:id/refunds → 201 with a refund object, not a payment.
| Parameter | Required | Notes |
|---|---|---|
amount | no | Minor units. Omit for the full remaining amount. |
reason | no | Free text, ≤ 200 characters. For your records. |
metadata | no | Same rules as on a payment. |
curl https://cleared-pay.com/api/v1/payments/pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD/refunds \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":500,"reason":"requested_by_customer"}'{
"id": "ref_01JBXQ8Q2T6V4X9Z3CF7HJ1LNP",
"object": "refund",
"payment": "pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD",
"amount": 500,
"currency": "eur",
"status": "succeeded",
"reason": "requested_by_customer",
"failure_code": null,
"fee": null,
"metadata": {},
"livemode": false,
"created": "2026-08-19T11:02:00Z"
}Refund status is pending, succeeded or failed. Several partial refunds are allowed up to the captured amount; going over is 400 amount_exceeds_refundable. The payment moves to partially_refunded and then to refunded when the total reaches amount_captured, and amount_refunded tracks the sum. A payment that has not been captured is 422 payment_not_refundable.
A refund is not a chargeback and does not prevent one: a payer who has been refunded can still raise a dispute, and the dispute is assessed on its own merits.
Retrieve a refund
GET /v1/refunds/:id → 200 with the refund object.
curl https://cleared-pay.com/api/v1/refunds/ref_01JBXQ8Q2T6V4X9Z3CF7HJ1LNP \
-H "Authorization: Bearer $CLEARED_SECRET_KEY"There is no GET /v1/refunds list in v1. Refunds against a payment are visible in the payment's timeline in the portal, and every refund emits refund.created and then refund.succeeded or refund.failed.
Events a payment emits
payment.created, payment.requires_action, payment.processing, payment.authorized, payment.captured, payment.partially_captured, payment.capture_failed, payment.declined, payment.failed, payment.canceled, payment.expired, and refund.created / refund.succeeded / refund.failed for refunds. See webhooks.