Skip to content
Zuuppa
Dashboard

API Reference

Checkout SDK

The client-facing endpoints your checkout page calls directly. They are authenticated by a per-intent client_secret (cs_...) rather than your sk_ API key, so they are safe to call from an untrusted browser. Base URL https://api.zuuppa.com.

Every endpoint here is authenticated by the client_secret returned from POST /intents. A cs_ secret is scoped to a single intent: it can quote, select a token, read status, attach buyer details, and cancel — but only for that one intent. It cannot create intents, sweep funds, list your account, or touch any other intent. That is exactly why it is safe to ship to the browser.

Warning

Never send your sk_ key to the client.

The whole point of the cs_ secret is that your sk_ key stays on your server. Create the intent server-side with sk_, forward only client_secret (and address) to the browser, and drive the rest of checkout with the endpoints below.

The buyer flow, in order#

A typical hosted or custom checkout calls these in sequence:

Text
1. GET  /intents/quote          → show live prices in each accepted token
2. POST /intents/select-token   → buyer picks an asset; locks amount + returns payment_uri
3. (optional) POST /intents/details → attach buyer name / email / shipping
4. GET  /intents/status         → poll until paid & settled (render the QR from payment_uri)

A buyer who abandons can hit POST /intents/cancel to release the intent early. Every request below carries the intent's client_secret; a wrong or unknown one is a 401.

Endpoints#

Live per-token pricing for the intent: the USD price converted to each accepted asset at the current spot rate, plus how long the checkout window has left. Call it to render the token picker before the buyer selects.

Pass client_secret as a query parameter.

200 OK
JSON
{
  "price_usd_cents": 5000,
  "expires_in_seconds": 540,
  "quotes": [
    { "symbol": "SOL",  "decimals": 9, "expected_lamports": 500000000 },
    { "symbol": "USDC", "decimals": 6, "expected_lamports": 50000000 }
  ]
}
  • expected_lamports is the amount in that asset's base units — divide by 10^decimals for the UI figure.
  • Quotes are a live snapshot; re-fetch before selection if the picker has been open a while. expires_in_seconds counts down the fixed 10-minute window.

Errors: 401 unknown/invalid client_secret; 409 if the intent is no longer quotable (already paid, expired, cancelled); 502 on a pricing/RPC failure.

POST
cs_ client secret

The buyer commits to a pay-in asset. Locks mint, mint_decimals, and expected_lamports at the current rate and returns the payment_uri to render as the QR code. Required before any payment can be detected.

Request body#

client_secretstringRequired
The intent's cs_ secret.
mintstring | nullRequired
The chosen asset: null for native SOL, or the SPL mint address. Must be one of the intent's accepted_tokens; anything else is a 400.
{ "client_secret": "cs_7pKf...9dQ2", "mint": null }
200 OK
JSON
{
  "derivation_index": 42,
  "address": "9xQe...pump",
  "mint": null,
  "mint_decimals": 9,
  "expected_lamports": 500000000,
  "status": "pending",
  "payment_uri": "solana:9xQe...pump?amount=0.5&label=Acme%20Store"
}
Tip

Re-selectable while pending.

The buyer can change their mind: calling select-token again while the intent is still pending re-locks the amount to the new asset and returns a fresh payment_uri. Once a payment lands the selection is frozen — a further call returns 409. Each successful selection emits an intent.token_selected webhook.

Errors: 400 token not in accepted_tokens; 401 bad client_secret; 409 a payment already arrived (selection locked); 502 on a pricing/RPC failure.

The client-side status poll. Returns the same body as the server-side GET /status — the flattened intent plus action, message, payment_uri, shortfall_lamports, settlement, and any token_refunds — but authenticated by client_secretinstead of your API key.

Pass client_secret as a query parameter. Poll it to drive the checkout UI: re-render the QR from payment_uri, show message, and switch to a success state when action becomes swept (read exact figures from settlement). See GET /status for the full field reference and the settlement object.

200 OK
JSON
{
  "derivation_index": 42,
  "address": "9xQe...pump",
  "status": "underpaid",
  "action": "underpaid",
  "message": "Received 0.30 of 0.50 SOL. Send the rest to complete payment.",
  "expected_lamports": 500000000,
  "received_lamports": 300000000,
  "shortfall_lamports": 200000000,
  "payment_uri": "solana:9xQe...pump?amount=0.2&label=Acme%20Store"
}
Note
On an underpaid intent the payment_uri's amount is the remaining shortfall — render it directly so the buyer tops up the exact difference.

Errors: 401 unknown/invalid client_secret.

POST
cs_ client secret

Attach or update buyer details (name, email, shipping address) on the intent from the client — for order confirmation, receipts, or shipping. All fields optional except the secret.

Request body#

client_secretstringRequired
The intent's cs_ secret.
first_namestringOptional
Buyer's given name.
last_namestringOptional
Buyer's family name.
emailstringOptional
Buyer email. Validated; a malformed address is a 400.
addressobjectOptional
Postal address: line1, line2, city, state, postal_code, country (ISO 3166-1 alpha-2 — a bad code is a 400).
JSON
{
  "client_secret": "cs_7pKf...9dQ2",
  "first_name": "Ada",
  "email": "ada@example.com",
  "address": { "line1": "1 Market St", "city": "SF", "country": "US" }
}
Note

Merged by key.

Fields you send overwrite their previous values; fields you omit are left untouched. To clear a value, send it explicitly as null rather than omitting it. Returns 200 with the updated customer_details.

Errors: 400 invalid email or country code; 401 bad client_secret; 409 if the intent is in a terminal state that no longer accepts detail edits.

POST
cs_ client secret

Let the buyer cancel from the client — the untrusted-client counterpart of POST /cancel, authenticated by client_secret instead of your sk_ key. Same behavior: only acts while pending/underpaid, idempotent, and race-safe against a confirming payment.

Request body#

JSON
{ "client_secret": "cs_7pKf...9dQ2" }

Response 200 OK with the intent's current status (same shape as GET /intents/status), now cancelled (or refunding/refunded if a partial SOL balance is being returned). A webhook fires exactly as with the server-side cancel — see POST /cancel.

Errors#

CodeMessage
400Malformed request body.
401unknown / invalid client_secret
409payment already received; cannot cancel

© 2026 Zuuppa. All rights reserved.