Skip to the guide
PayZero
Showing Test (sandbox) examples.
On this page

PayZero API reference

PayZero accepts payments from customers in Japan and reports the outcome back to you. This reference covers the checkout API: the endpoints that list what a customer can pay, open a payment, and confirm whether it settled.

It is arranged to be read in order the first time and searched afterwards. New here? Start with Getting started, then Essentials for how the two payment methods behave, and Guides for testing and going live. Already integrating? The Reference has every endpoint, field by field, with the consolidated error table.


Getting started

Base URL: https://api.payzero.ai

Every endpoint returns JSON. Request bodies are JSON and must be sent with Content-Type: application/json. Responses are never cached.

Requests must come from an allowlisted origin. Send PayZero the origins you will call from before you start; a browser request from any other origin is refused with 403 origin_not_allowed. Server-to-server calls, which send no Origin header, are not subject to the check.

The sandbox

PayZero provisions a sandbox before go-live. It is the same API — the same endpoints, request bodies and response shapes — pointed at the payment provider's test environment, and nothing in it moves real money. Build and test your integration against it, then point the same code at live.

Ask PayZero to set up your sandbox and allowlist your origins to begin. When you are ready to exercise real payments, Testing has the test cards and the authentication PINs, and Sandbox and live covers the one difference between the two environments.

Your first payment

Both methods follow the same shape: create a payment through the API, send the customer to the redirectUrl you get back, and confirm the outcome afterwards. End to end, that is five steps.

  1. Ask PayZero to allowlist your origins and set up your sandbox.
  2. For bank transfer, call GET /checkout/banks, then GET /checkout/amounts for the bank the customer picked. For card, go straight to step 3.
  3. Create the payment with POST /checkout/deposit or POST /checkout/card, and store the reference it returns against your order.
  4. Send the customer to redirectUrl.
  5. Confirm the outcome with GET /checkout/status?ref=…. The customer's return does not confirm anything by itself.

The smallest single call is GET /checkout/banks: no body, no parameters, just your allowlisted Origin, and it returns live bank availability. Everything else in the flow hangs off the reference you store at step 3.


Essentials

Two payment methods, both redirect flows. This section is how they behave: what the customer does, what the amounts and currencies look like, and why a customer returning from the payment page is not yet a settled payment. The exact request and response shapes are in the Reference.

Authentication

Your secret key authenticates API requests. Send it as a bearer token:

curl -X POST https://api.payzero.ai/checkout/deposit \
  -H 'Authorization: Bearer pz_sk_test_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ ...payment fields... }'

The key identifies your merchant and which environment the request runs in: use your test key against the sandbox and your live key in live, so the same request routed with a test key never touches real money. A key looks like pz_sk_test_YOUR_KEY — a prefix that names the environment, then the credential.

Each key is issued from the portal and shown once. Treat it like a password: keep it on your server, never ship it to a browser or commit it to source, and have it reissued from the portal if it leaks. It carries the Authorization header on requests that create or read your own payments; some checkout lookups are public and need no key, so reach for it on server-to-server calls rather than trying to enumerate which endpoints demand it.

Payment methods

Two methods are available. Both are redirect flows: you create the payment through the API, send the customer to the redirectUrl it returns, and confirm the outcome afterwards with GET /checkout/status.

Bank transferCard
CurrencyJPYEUR
Customer locationJapanNot restricted
Who sets the amountThe customer, from the amounts currently availableYou
MinimumThe smallest available amount1.00 EUR
MaximumThe largest available amount10,000.00 EUR
Payment details enteredOn the secure payment pageOn the secure payment page
paymentMethod on settlementCOMMUNITY_BANKCREDIT_CARD

Amounts use the currency's normal decimal form, as documented per endpoint. EUR takes two decimal places — "25.00". JPY has none, so a yen amount is a whole number: 500, never 500.00. No sign, no exponent, no thousands separator, no currency symbol.

Bank transfer: the customer selects an available amount

This is the constraint to design around. With bank transfer the customer does not enter an amount. They choose a bank, then choose one of the exact amounts available for that bank at that moment. There is no rounding, no nearest match, no partial fill and no combining of two amounts.

Availability changes continuously. An amount listed a minute ago may be gone, a bank may have nothing available, and the method as a whole can be temporarily unavailable. Design for that:

  • Fetch availability immediately before you show it, and refresh it if the customer pauses.
  • Build a "no amounts available" state with real copy and a real next action. Nothing has gone wrong when it appears.
  • Offer card as a fallback. For a Japanese customer that fallback is a euro charge.
  • Bank transfer suits top-ups, wallet funding and account credit. It cannot collect a fixed cart total unless that exact amount happens to be available.
  • Reconcile the amount actually paid against your own order afterwards, using the reference.

Cards are charged in euro

Card payments are processed in EUR only. A Japanese cardholder is charged in euro and their own bank sets the exchange rate, so the yen figure on their statement will not be the figure you displayed. Tell them before they pay.

The minimum card payment is 1.00 EUR and the maximum is 10,000.00 EUR. A postal code is required. Card details are entered by the customer on the secure payment page — POST /checkout/card refuses any request containing a card number, expiry, CVV or similar field.

Status and settlement

Settlement is confirmed asynchronously. When the customer finishes on the payment page they are returned to a PayZero page. That return means a browser came back — nothing more.

A customer returning from the payment page does not mean the payment succeeded. Confirm every payment with GET /checkout/status?ref=… before you release goods or credit an account.

settledstatusMeaning
falsePENDINGNo notification yet. The payment may still complete.
trueAPPROVEDThe payment completed. Release the goods or credit the account.
trueDECLINEDThe payment did not complete.

Card payments typically resolve within seconds of the customer finishing on the payment page. Bank transfers take longer, and there is no guaranteed time to settlement. Poll every few seconds while the customer is waiting, then back off to minutes, and treat a pending payment as a normal state in your order flow rather than an error.


Guides

The things worth getting right before you ship: how to test an integration against the sandbox, and what changes when you move to live.

Testing

PayZero provisions a sandbox environment before go-live. It is the same API — the same endpoints, request bodies and response shapes — pointed at the payment provider's test environment. Nothing in it moves real money, and every card number below is a test number that works in sandbox only.

Use a different customer email for each test payment. Repeating the same customer details, bank and amount while an earlier payment is still unfinished can be refused, and it reaches you as psp_error rather than as anything that names the cause.

Test cards

The customer enters these on the secure payment page.

BrandNumberExpiryCVV
Visa401288888888188112/30123
Mastercard550000000000000412/30123
JCB353011133330000012/30123
Visa490000000000008612/30123

Which of these you use does not decide the outcome. Nothing about a test number makes a payment approve or decline — the authentication step does, and that is the next section.

Forcing an authentication outcome

A card payment in sandbox will usually present an authentication step after the redirect: a test authentication page with a single PIN field. The PIN decides the result, and the page lists the ones it accepts.

PINResult
mbyAuthentication approved
mbnAuthentication declined
mbuUnknown
mbaAttempted

This is how to test what your integration does with a failed authentication, and it needs no special card: open an ordinary card payment with any number above, send the customer to redirectUrl, and submit mbn. Expect the authentication step on sandbox card payments as a matter of course — a payment that never reaches it is the exception, not the rule.

The PIN decides the authentication, not the payment. The outcome still arrives through GET /checkout/status, exactly as in live.

Bank transfer in sandbox

The amounts a sandbox bank offers are live test inventory, not a fixture. Expect the list to differ between runs, and expect it to be empty sometimes — for one bank, or for every bank at once. Sandbox behaves the way live behaves here, which makes it the place to exercise that constraint rather than something to work around; see Bank transfer: the customer selects an available amount. Do not write a test that depends on a particular amount being available.

Creating a deposit reserves that amount immediately, in sandbox as well as live, whether or not anyone completes it. Looping over POST /checkout/deposit will exhaust the amounts available to your next test.

Confirming a test payment

A customer returning from the payment page means a browser came back and nothing more, in sandbox exactly as in live. GET /checkout/status?ref=… is what confirms a payment: poll it until settled is true, then read status. See Status and settlement.

What sandbox cannot simulate

  • A specific card decline reason. You can decline the authentication; you cannot choose the reason an issuer would refuse the charge.
  • A dispute. There is no way to raise one against a test payment.
  • A settlement notification on demand. A payment notifies when it settles, and there is no way to ask for that notification again.

Sandbox and live

Sandbox and live are the same API pointed at different environments, so anything you build against the checkout endpoints — banks, amounts, deposit, card, status — works the same in both, and bank transfer in particular behaves identically.

The one difference is how a card is captured. In live, card details are entered by the customer on the secure payment page: you call POST /checkout/card and send them to the redirectUrl. The Card API (sandbox) — hosted sessions and direct card — accepts card details through PayZero for building and testing, and does not run in live; a card API request against a live environment is answered 403 card_direct_sandbox_only before its body is read.

PayZero provisions your sandbox before go-live. When your integration is working against it, the same code points at live — nothing in the checkout flow changes but the card-capture path above.


Card API (sandbox)

Everything in this section runs in sandbox only. These endpoints accept card details through PayZero — on a PayZero payment page, or directly in the request — instead of on the secure payment page. They exist for building and testing an integration against the sandbox PayZero provisions for you (see Testing). The server refuses to run them in live: a card API request against a live environment is answered 403 card_direct_sandbox_only before the body is read. They are not a way to take a real payment.

The card API is not enabled by default and is not open. Ask PayZero to enable it on your sandbox; a key is issued at the same time. This is a distinct sandbox-only credential — not the API secret key from Authentication: it gates the sandbox direct-card flow and has no meaning in live. Send it in the request body as key — never in a query string, where it would sit in access logs and browser history. The examples below show it as <your-card-direct-key>. A missing or wrong key is answered 403 card_direct_key_invalid, and the response does not say which.

Use the test cards; never send a real card number to these endpoints. Sandbox card payments present the authentication step described in Forcing an authentication outcome, and the outcome arrives through GET /checkout/status, exactly as Status and settlement describes — nothing in this section changes how a payment is confirmed.

Two flows. With a hosted checkout session your server opens a session and sends the customer to a PayZero payment page; they enter their card there and come back to your returnUrl. With direct card your server sends the card fields itself and steers the customer through the authentication step from the response. Creating a session, paying it, and a direct card request all count as payment creation for rate limiting.

Hosted checkout session

Create a session, send the customer to the url it returns, they enter their card on PayZero's page, and they come back to your returnUrl with the result appended. A session expires 15 minutes after it is created and can be paid once.

POST /checkout/session

Opens a checkout session and returns the URL of the payment page. Server to server: this is the call that carries your key, so it is not for a browser.

FieldTypeRequiredDescription
keystringYesThe key PayZero issued for your sandbox.
amountstring | numberYesPlain decimal, at most two places. Between 1.00 and 10000.00.
currencystringNoEUR. Defaults to EUR.
firstNamestringYesUp to 40 characters.
lastNamestringYesUp to 40 characters.
emailstringYesCustomer's email address. Up to 120 characters.
dateOfBirthstringYesYYYY-MM-DD.
postCodestringYesUp to 12 characters.
countrystringNoISO 3166-1 alpha-2. Defaults to JP.
citystringNoUp to 40 characters. Defaults to Tokyo.
addressstringNoUp to 80 characters.
returnUrlstringYesAbsolute URL, up to 512 characters. The customer is returned here after paying.

returnUrl must be an absolute URL whose origin is on your origin allowlist — the same list browser requests are checked against — or the session is refused with 400 bad_return_url. The path and query are yours; the result is appended to whatever they carry.

curl -X POST https://api.payzero.ai/checkout/session \
  -H 'Content-Type: application/json' \
  -d '{
    "key": "<your-card-direct-key>",
    "amount": "25.00",
    "currency": "EUR",
    "firstName": "Aoi",
    "lastName": "Tanaka",
    "email": "aoi.tanaka@example.com",
    "dateOfBirth": "1990-04-18",
    "postCode": "100-0001",
    "returnUrl": "https://checkout.example.com/return"
  }'
{
  "sessionId": "0f31c7a89bd44e21a6c05d9e8f3b7a12",
  "url": "https://sandbox.payzero.ai/pay/0f31c7a89bd44e21a6c05d9e8f3b7a12",
  "expiresAt": "2026-07-22T09:15:00.000Z"
}

The response is 201. Send the customer to url before expiresAt, which is ISO 8601 UTC, fifteen minutes after creation. No card is sent at this step and none may be — the card is entered once, on the payment page. The amount and customer are fixed when the session is created; nothing entered on the payment page can change them.

Errors: bad_json, bad_amount, currency_not_supported, bad_name, bad_email, bad_dob, bad_country, bad_post_code, bad_return_url, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, internal.

GET /checkout/session/:id

What the payment page reads to render the checkout. You do not call it yourself. Anyone holding the id can, so it answers with the minimum a card form needs — no customer detail comes back.

{
  "amount": "25.00",
  "currency": "EUR",
  "expiresAt": "2026-07-22T09:15:00.000Z",
  "status": "open"
}

status is open on every 200; there is no other value. A session that is unknown, expired or already paid is answered 404 session_not_found, with a body that is identical in all three cases.

Errors: session_not_found, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, origin_not_allowed, rate_limited, internal.

POST /checkout/session/:id/pay

The payment page posts the card, once. The body is the card and nothing else — no amount, no customer, no key. Those were fixed when the session was created, and any other field sent here is ignored unread.

FieldTypeRequiredDescription
cardNumberstringYes12–19 digits, sent as a string. Spaces and hyphens are ignored. A test card number, never a real one.
expDatestringYesMM-YY or MM-YYYY, with - or /. Must not be in the past.
cvvstringYes3 or 4 digits.
nationalIdstringYes4–32 letters, digits and hyphens.
browserobjectNoThe customer's browser data for the authentication step: acceptHeader, javaEnabled, language, colorDepth, screenHeight, screenWidth, timeZoneOffset. Missing values fall back to sandbox defaults.
{
  "reference": "PZ-MJ4K6D8R-5B2F9C31",
  "status": "PENDING",
  "requires3ds": true,
  "redirectUrl": "https://secure.example-psp.com/3ds?token=41c9e2"
}

reference is PayZero's identifier for the payment, and what GET /checkout/status answers on. status is always PENDING — entering a card settles nothing. The customer is sent to redirectUrl: the authentication step when requires3ds is true, the client's returnUrl directly when it is not. Either way they arrive back at the returnUrl with ?ref= carrying the reference and result=completed or result=failed appended. result says which way the customer came back, not whether money moved — confirm with GET /checkout/status before crediting anything.

Paying spends the session: a second call with the same id is answered 404 session_not_found and opens no second payment.

Errors: bad_json, bad_card_number, bad_expiry, bad_cvv, bad_national_id, session_not_found, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, session_storage_unavailable, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.

Direct card

POST /checkout/card-direct

The server-to-server variant: one request carries the key, the amount, the customer and the card, and opens the payment. There is no session and no PayZero page — your integration collects the card and sends the customer to the authentication step itself.

FieldTypeRequiredDescription
keystringYesThe key PayZero issued for your sandbox.
amountstring | numberYesPlain decimal, at most two places. Between 1.00 and 10000.00.
currencystringNoEUR. Defaults to EUR.
firstNamestringYesUp to 40 characters.
lastNamestringYesUp to 40 characters.
emailstringYesCustomer's email address. Up to 120 characters.
dateOfBirthstringYesYYYY-MM-DD.
postCodestringYesUp to 12 characters.
countrystringNoISO 3166-1 alpha-2. Defaults to JP.
citystringNoUp to 40 characters. Defaults to Tokyo.
addressstringNoUp to 80 characters.
cardNumberstringYes12–19 digits, sent as a string. Spaces and hyphens are ignored. A test card number, never a real one.
expDatestringYesMM-YY or MM-YYYY, with - or /. Must not be in the past.
cvvstringYes3 or 4 digits.
nationalIdstringYes4–32 letters, digits and hyphens.
browserobjectNoAs on POST /checkout/session/:id/pay.
curl -X POST https://api.payzero.ai/checkout/card-direct \
  -H 'Content-Type: application/json' \
  -d '{
    "key": "<your-card-direct-key>",
    "amount": "25.00",
    "currency": "EUR",
    "firstName": "Aoi",
    "lastName": "Tanaka",
    "email": "aoi.tanaka@example.com",
    "dateOfBirth": "1990-04-18",
    "postCode": "100-0001",
    "cardNumber": "4012888888881881",
    "expDate": "12-30",
    "cvv": "123",
    "nationalId": "AB123456"
  }'
{
  "reference": "PZ-MJ4K7E3S-8C4A1D62",
  "transactionId": "TR-8842164",
  "amount": "25.00",
  "currency": "EUR",
  "status": "PENDING",
  "requires3ds": true,
  "redirectUrl": "https://secure.example-psp.com/3ds?token=7d20b5"
}

status is always PENDING. requires3ds says whether an authentication step stands between the customer and the outcome, and redirectUrl is where it happens: a response carrying one is a payment waiting on a step nobody has completed, so send the customer there and expect the PIN page from Forcing an authentication outcome. With no redirectUrl there is nothing to complete. In both cases the outcome arrives through GET /checkout/status?ref=…, and nothing is settled until it reports so. After the authentication step the customer finishes on a PayZero page — this flow has no returnUrl.

Errors: bad_json, bad_amount, currency_not_supported, bad_name, bad_email, bad_dob, bad_country, bad_post_code, bad_card_number, bad_expiry, bad_cvv, bad_national_id, card_direct_sandbox_only, card_direct_disabled, card_direct_key_invalid, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.


Reference

Every endpoint, field by field, with a runnable example and the errors it can return, then the consolidated error table, what the API does not do, and how to reach support. The flow these fit into is in Getting started.

GET /checkout/banks

Lists the banks available for bank transfer, each with a snapshot of what it currently has on offer. Takes no parameters.

curl https://api.payzero.ai/checkout/banks \
  -H 'Origin: https://checkout.example.com'
{
  "banks": [
    { "swiftCode": "MHCBJPJT", "bankName": "Mizuho Bank", "available": 3, "amounts": [100, 500, 777] },
    { "swiftCode": "BOTKJPJT", "bankName": "MUFG Bank", "available": 0, "amounts": [] }
  ]
}

swiftCode is the bank identifier you pass to the other bank transfer endpoints. available is how many amounts the bank has on offer right now, and amounts lists the distinct ones, ascending. Banks are ordered by available, highest first; available: 0 means that bank has nothing on offer at this moment and is not an error. Use this endpoint to build a bank picker, not to reserve anything — availability is confirmed again when you create the payment.

Errors: origin_not_allowed, rate_limited, psp_error, internal.

GET /checkout/amounts

The exact amounts currently available for one bank.

ParameterTypeRequiredDescription
bankstringYesSWIFT code, 8–11 letters and digits. Case-insensitive.
curl 'https://api.payzero.ai/checkout/amounts?bank=MHCBJPJT' \
  -H 'Origin: https://checkout.example.com'
{
  "swiftCode": "MHCBJPJT",
  "amounts": [
    { "id": 84213, "amount": 100, "currency": "JPY" },
    { "id": 84219, "amount": 777, "currency": "JPY" }
  ]
}

Each entry is one amount the customer can choose: id is what you send as activeAmountId, amount is a whole number of yen, and currency is JPY.

An empty amounts array means the bank has nothing available. It is a normal answer, not an error. Identifiers rotate within minutes, so fetch this immediately before the customer chooses.

Errors: bad_bank, origin_not_allowed, rate_limited, psp_error, internal.

POST /checkout/deposit

Opens a bank transfer payment for one of the available amounts and returns the URL to send the customer to.

FieldTypeRequiredDescription
swiftCodestringYesSWIFT code from GET /checkout/banks.
activeAmountIdnumberYesThe id of the amount the customer chose.
amountnumberRecommendedThe amount the customer chose. See the note below.
firstNamestringYesUp to 40 characters.
lastNamestringYesUp to 40 characters.
emailstringYesCustomer's email address. Up to 120 characters.
dateOfBirthstringYesYYYY-MM-DD.
citystringNoUp to 40 characters. Defaults to Tokyo.
addressstringNoUp to 80 characters.

Send amount as well as activeAmountId. Identifiers rotate, and if the one you hold has changed since you fetched it, PayZero binds another entry at the same amount instead of failing. Without amount a rotated identifier returns 409 slot_taken. The authoritative amount is always the one PayZero confirms, never the one in your request.

curl -X POST https://api.payzero.ai/checkout/deposit \
  -H 'Content-Type: application/json' \
  -H 'Origin: https://checkout.example.com' \
  -d '{
    "swiftCode": "MHCBJPJT",
    "activeAmountId": 84219,
    "amount": 777,
    "firstName": "Aoi",
    "lastName": "Tanaka",
    "email": "aoi.tanaka@example.com",
    "dateOfBirth": "1990-04-18",
    "city": "Tokyo",
    "address": "1-1 Chiyoda"
  }'
{
  "reference": "PZ-MJ4K2X9A-7F3C1B92",
  "transactionId": "TR-8842119",
  "amount": 777,
  "currency": "JPY",
  "status": "WAITING",
  "redirectUrl": "https://secure.example-psp.com/s/9f2ac1"
}

reference is PayZero's identifier for this payment — store it against your order. transactionId is the payment provider's. amount is the confirmed amount in JPY. status describes the redirect step and is not a payment outcome. Send the customer to redirectUrl to authorise the transfer.

409 slot_taken means the amount was taken between your two calls. The response body carries a fresh amounts array in the same shape as GET /checkout/amounts, so you can re-offer the choice without another round trip.

Errors: bad_json, bad_bank, bad_amount, bad_name, bad_email, bad_dob, slot_taken, invalid, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.

POST /checkout/card

Opens a card payment and returns the URL of the secure payment page.

FieldTypeRequiredDescription
amountstring | numberYesPlain decimal, at most two places. Between 1.00 and 10000.00.
currencystringNoEUR. Defaults to EUR.
firstNamestringYesUp to 40 characters.
lastNamestringYesUp to 40 characters.
emailstringYesCustomer's email address. Up to 120 characters.
dateOfBirthstringYesYYYY-MM-DD.
postCodestringYesUp to 12 characters.
countrystringNoISO 3166-1 alpha-2. Defaults to JP.
citystringNoUp to 40 characters. Defaults to Tokyo.
addressstringNoUp to 80 characters.

Do not send card details. A request containing a card number, expiry, CVV, PIN or track data — under any spelling, at any nesting depth — is refused whole with 400 card_data_not_accepted, and nothing in it is read or forwarded. In sandbox there is a separate card API that does accept card details, for building and testing only — see Card API (sandbox).

curl -X POST https://api.payzero.ai/checkout/card \
  -H 'Content-Type: application/json' \
  -H 'Origin: https://checkout.example.com' \
  -d '{
    "amount": "25.00",
    "currency": "EUR",
    "firstName": "Aoi",
    "lastName": "Tanaka",
    "email": "aoi.tanaka@example.com",
    "dateOfBirth": "1990-04-18",
    "postCode": "100-0001",
    "country": "JP",
    "city": "Tokyo",
    "address": "1-1 Chiyoda"
  }'
{
  "reference": "PZ-MJ4K3B1Y-2D8E4A07",
  "transactionId": "TR-8842137",
  "amount": "25.00",
  "currency": "EUR",
  "status": "PENDING",
  "redirectUrl": "https://secure.example-psp.com/pay?token=8f21c0"
}

reference is PayZero's identifier for this payment — store it against your order. amount comes back normalised to two decimal places. status is always PENDING here: no card has been entered yet. Send the customer to redirectUrl to enter their card.

Errors: bad_json, card_data_not_accepted, currency_not_supported, bad_amount, bad_name, bad_email, bad_dob, bad_country, bad_post_code, merchant_suspended, unknown_merchant, origin_not_allowed, rate_limited, rejected, psp_error, internal.

GET /checkout/status

The current state of one payment. Works for both methods.

ParameterTypeRequiredDescription
refstringYesThe reference returned when the payment was created.
curl 'https://api.payzero.ai/checkout/status?ref=PZ-MJ4K3B1Y-2D8E4A07' \
  -H 'Origin: https://checkout.example.com'
{
  "reference": "PZ-MJ4K3B1Y-2D8E4A07",
  "settled": true,
  "status": "APPROVED",
  "amount": "25.00",
  "currency": "EUR",
  "paymentMethod": "CREDIT_CARD",
  "source": "ipn"
}
FieldTypeDescription
referencestringThe reference you asked about.
settledbooleantrue once PayZero has received and authenticated a settlement notification.
statusstringPENDING until a notification arrives, then APPROVED or DECLINED.
amountstringThe amount. Present once the payment is known.
currencystringISO 4217 code. Present once the payment is known.
paymentMethodstringCOMMUNITY_BANK or CREDIT_CARD. Present once a notification has arrived.
sourcestringipn, deposit or inbox. See below.

source says where the answer came from: ipn — an authenticated settlement notification. deposit — the payment exists and no notification has arrived yet. inbox — no record was found for this reference.

An unrecognised reference is not an error. A typo returns settled: false, status: "PENDING" and source: "inbox", which is indistinguishable from a payment that has not settled yet, so check the reference you stored rather than inferring existence from this endpoint.

This endpoint is not authenticated: anyone holding a reference can read its amount, currency and status. Treat a reference as you would an order number that should not be published.

Errors: bad_reference, origin_not_allowed, rate_limited, internal.

Errors

Every error response carries an error code string, and most also carry a human-readable message{ "error": "bad_amount", "message": "…" }. Some carry extra fields, noted below. Match on error, never on message.

errorHTTPWhen it occursWhat to do
origin_not_allowed403The Origin header is not allowlisted.Ask PayZero to allowlist it, or call server to server.
rate_limited429Too many requests in a short period. Creating payments is limited more tightly than reading.Back off and retry. Never retry a create blindly — each accepted call opens a new payment.
bad_json400The body is not valid JSON.Fix the request.
bad_bank400bank or swiftCode is missing or is not 8–11 letters and digits.Use a swiftCode from GET /checkout/banks.
bad_amount400On deposit, activeAmountId is missing or not positive. On card, the amount is not a plain decimal, has too many decimal places, or is outside 1.00–10,000.00 EUR.Read message; it names the rule that failed.
bad_name400firstName or lastName is empty.Collect both.
bad_email400email is missing or malformed.Validate before sending.
bad_dob400dateOfBirth is not YYYY-MM-DD.Reformat.
bad_country400country is not two letters.Send an ISO 3166-1 alpha-2 code.
bad_post_code400postCode is missing on a card payment.Collect a postal code.
card_data_not_accepted400The body contains cardholder data. field names which one.Remove it. Card details are entered on the payment page.
currency_not_supported400currency is not offered for cards. supported lists what is.Send EUR.
slot_taken409The chosen amount was taken before the payment could be opened. amounts carries current availability.Show the fresh amounts and let the customer choose again.
invalid400The amount stopped being available between your request and the reservation.Refresh availability and let the customer choose again.
rejected400The payment provider refused the request because of its contents. code and message are theirs.Correct the field named in message.
bad_reference400ref is missing on GET /checkout/status.Send the stored reference.
bad_return_url400Card API: returnUrl is missing, not an absolute URL, or its origin is not allowlisted.Send a URL on an allowlisted origin.
bad_card_number400Card API: the card number is not 12–19 digits, or fails its check digit.Fix the number. Test cards only.
bad_expiry400Card API: the expiry is not MM-YY or MM-YYYY, or is in the past.Reformat, or use an unexpired date.
bad_cvv400Card API: the security code is not 3 or 4 digits.Fix it.
bad_national_id400Card API: nationalId is missing or not 4–32 letters, digits and hyphens.Collect one.
session_not_found404Card API: the checkout session is unknown, expired or already paid. The three cases are answered identically.Create a new session.
card_direct_sandbox_only403A card API request reached a live environment. The body is not read and nothing is called.Use your sandbox. The card API does not run in live.
card_direct_disabled403The card API is not enabled on this deployment.Ask PayZero to enable it on your sandbox.
card_direct_key_invalid403The key is missing or wrong. The response does not say which.Send the key PayZero issued, in the request body.
session_storage_unavailable503This deployment cannot store checkout sessions.Contact PayZero.
merchant_suspended403The account cannot currently accept payments.Contact PayZero.
unknown_merchant400The account could not be resolved.Contact PayZero.
not_found404No such endpoint.Check the path.
psp_error502The payment provider returned an error. message carries theirs.Retry after a short pause. If it persists, contact PayZero with the time and reference.
internal500Unexpected error.Retry. If it persists, contact PayZero.

Not currently available

Not availableNotes
Any authentication scheme but the bearer secretRequests carry your secret key as a bearer token — see Authentication. Browser requests are additionally checked against the origin allowlist, and the card API adds its own separate sandbox link key.
Outbound webhooksPoll GET /checkout/status for each reference.
RefundsNot available through the API.
PayoutsNot available through the API.
Recurring billing, subscriptions and card-on-fileNot available.
Card payments in JPYCards are EUR only.
Card capture in liveTaking card details through PayZero — the card API — runs against the sandbox only, for building and testing. In live, card details are entered on the secure payment page.
A return URL to your own siteThe customer finishes on a PayZero page. In sandbox, a hosted checkout session returns the customer to your returnUrl.
Order ids, customer ids or other metadataThe create endpoints accept no such field. Store the reference against your order.
Idempotency keysEach accepted POST opens a separate payment.

Support

Lead with the reference. It looks like PZ-MJ4K2X9A-7F3C1B92, it is returned when the payment is created, and it identifies that payment everywhere — in the API, at the payment provider, and on the settlement notification. With it a payment can be traced end to end. Without it, very little can be answered.

Email info@payzero.ai and include:

IncludeWhy
The reference (PZ-…)The identifier every record is keyed on.
The transactionId, if you have itThe payment provider's side of the same event.
Environment — sandbox or liveThey are separate systems.
Method — bank transfer or cardThe two behave differently.
Amount and currency as displayed to the customerThe figure to reconcile against.
When it happened, with a time zoneFor log correlation.
What the customer saw, verbatim, including any error textThe message usually names the field at fault.
Whether they completed the payment pageThe most useful single fact for a bank transfer.

Do not send card numbers. The API refuses them and so do we.