Skip to content
Sandbox open: magic test cards, hosted checkout and signed webhooks · 55 payment methods in the catalogue, cards to crypto · Built and hosted in the EEA · Read the API reference at /developers

Sandbox open: magic test cards, hosted checkout and signed webhooks

Documentation

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
}
FieldTypeNotes
idstringpay_ + ULID.
objectstringAlways payment.
statusenumSee the table below.
amountintegerMinor units, the amount requested.
amount_capturedintegerWhat has actually been captured. 0 until capture.
amount_refundedintegerSum of succeeded refunds.
currencystringLowercase ISO 4217.
referencestringYours, 1–64 characters, required. Not enforced unique.
descriptionstring or nullUp to 500 characters. Not shown to the payer.
capture_methodenumautomatic or manual.
method.typeenumcard, apple_pay, google_pay, ideal, blik, sepa_debit, … lowercase wire names.
method.cardobject or nullPresent for card-like methods: brand, last4, bin, exp_month, exp_year, issuer_country, funding, fingerprint, network_token. Never a full number.
method.walletstringPresent on apple_pay and google_pay.
method.detailsobjectMethod-specific data for non-card methods.
customerstring or nullcus_ id when a customer was attached.
outcome.response_codestring or nullISO 8583-style code. 00 is approved.
outcome.decline_codestring or nullPopulated on a decline. See errors.
outcome.decline_is_softboolean or nullWhether a retry is safe. The only field to base a retry on.
outcome.avs_resultstring or nullY, M, N, U, S, Z, A.
outcome.cvv_resultstring or nullM, N, P, U, S.
outcome.three_dsobject or nullstatus, eci, version.
outcome.sca_exemptionstring or nullThe exemption claimed, when one was.
outcome.risk_scoreinteger or null0–100, higher is riskier.
outcome.risk_decisionstring or nullaccept, review, reject.
feesobject or nullSnapshotted 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_urlstring or nullWhere the payer comes back after a redirect.
statement_descriptorstring or null2–22 characters, letters, digits, spaces and * . -.
metadataobjectUp 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.
livemodebooleanfalse for everything created with a test key.
createdtimestampRFC 3339 UTC.
authorized_attimestamp or null
captured_attimestamp or null
canceled_attimestamp or null
next_actionobjectPresent only while status is requires_action.

Statuses

statusMeaning
pendingCreated, no authorisation attempted yet. Waiting for confirm. Expires after 1 hour.
requires_actionThe payer must do something — a 3-D Secure challenge or an APM redirect. Expires after 1 hour.
processingAn async rail is settling. Resolves on its own.
authorizedFunds held, not taken. Expires after 7 days if not captured.
capturedTaken. A partial capture also reports captured, with amount_captured < amount.
partially_refundedSome of the captured amount has been refunded.
refundedFully refunded.
disputedA chargeback has been raised.
declinedThe issuer said no. outcome.decline_code says why.
failedNeither approved nor declined — a processing error.
canceledVoided before capture.
expiredTimed 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.

ParameterRequiredNotes
amountyesPositive integer, minor units.
currencyyesISO 4217, lowercase.
referenceyes1–64 characters.
payment_method.typeyesA wire method name.
payment_method.tokennoA tok_ from hosted fields or the sandbox tokenizer. Omit it for the two-step flow; alternative methods never need one.
capture_methodnoautomatic (default) or manual.
descriptionno≤ 500 characters.
customerno{ id? , email?, name?, phone?, billing_address? }. billing_address.country is a required 2-letter code when an address is given.
return_urlnoAbsolute https URL. Required in practice for any method that can redirect.
statement_descriptorno2–22 characters.
metadatanoSee 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:

OutcomeStatusBody
Approved201The payment object.
Needs the payer201The payment object with next_action.
Async rail201The payment object, status: "processing".
Declined402card_error with decline_code and the payment id in message.
Processing failure422processing_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.

FilterAccepts
statusOne wire status or a comma-separated list: status=captured,refunded.
currencyLowercase ISO 4217.
referenceExact match on your reference.
customerA cus_ id.
method_typeA 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.
limit1–100, default 25.
starting_afterA cursor or a bare pay_ id.
ending_beforeA 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.

ParameterRequiredNotes
amountnoMinor 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.

ParameterRequiredNotes
amountnoMinor units. Omit for the full remaining amount.
reasonnoFree text, ≤ 200 characters. For your records.
metadatanoSame 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.