Skip to main content

Checkout API reference

Base URL https://pay.apiosk.com. Currency is USDC only, 6 decimals. Every response carries Cache-Control: no-store.

Authentication

Two endpoints take your marketplace secret key, as either header:
Only the hash of a key is ever compared, and the lookup accepts secret keys that have not been revoked. A publishable apk_pub_ key cannot authenticate here. There is no shared platform key.
“No key”, “unknown key”, “revoked key” and “that was a publishable key” all return the same 401 unauthorized. Distinguishing them would tell someone probing with a guessed key which half of the guess was right.
The payer-facing endpoints take no key. The checkout ID is the credential: it is a UUID, nothing guessable is attached to it, and what it exposes is what the buyer is entitled to see.

POST /v1/checkout

Creates a checkout and returns the URL to redirect the buyer to. amount is validated, not rounded. Scientific notation, padding, negatives and more than 6 decimals are all rejected rather than coerced, because silently rounding somebody’s price is worse than refusing it. There is deliberately no field for a wallet address, a fee, a marketplace name or a logo. See /pay/overview for why. 201 response
Errors

GET /v1/payments/

The authoritative status, scoped to your own marketplace.
No wallets, no URLs, no fee configuration. A payment belonging to another marketplace answers 404, not 403: a 403 would confirm the ID exists and turn this endpoint into an enumeration oracle.

The payer-facing endpoints

You do not normally call these. The hosted page does, and they are documented so the flow is auditable. GET /v1/session/{id} returns what the checkout page renders: amounts, the product line, the marketplace’s name and logo, the three split lines, and the chain and token to pay in. It never returns a recipient address of any kind. A non-UUID answers 404, not 500. POST /v1/payments/{id}/prepare takes { payer_address, permit_signature?, permit_deadline? } and returns opaque calldata for the splitter. With an EIP-2612 permit signature there is one transaction; wallets that cannot sign a permit get an approve step first, so two. POST /v1/payments/{id}/pay takes { tx_hash } and records it. This is a claim, not a proof. It is a compare-and-swap: of many concurrent submits exactly one wins, and a hash already used to pay another checkout is rejected. GET /v1/payments/{id}/status is the poll that actually settles. It reads the on-chain receipt and only marks the payment paid if the transaction carries a split event for this checkout plus a matching transfer for every expected leg, each consumed once. An RPC failure leaves the payment pending and is retried, it never invents a result. POST /v1/payments/{id}/cancel returns { ok: true, status: "cancelled" }, or 409 not_cancellable once the payment has left pending or failed.