Checkout Sessions
Create one-off payments programmatically — the only public payments endpoint today.
Checkout Sessions
A Checkout Session is the primary API endpoint for taking a payment. You create it from your server, redirect the customer to checkout_url, and receive a webhook when it's paid.
Base URL: https://api.spuke.com/v1
Auth: Authorization: Bearer sk_live_… (or sk_test_…)
Create a session
POST /v1/checkout/sessions
curl -X POST https://api.spuke.com/v1/checkout/sessions \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_12345" \
-d '{
"amount": 4990,
"currency": "eur",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"reference": "ORDER-12345",
"description": "Order #12345",
"line_items": [
{ "name": "Sneaker Runner Pro", "quantity": 1, "amount": 4990, "image_url": "https://…/sneaker.jpg", "price_id": "price_1Tu…" }
],
"success_url": "https://shop.example.com/thanks?o=12345",
"cancel_url": "https://shop.example.com/cart"
}'
Body
| Field | Type | Required | Notes |
|---|---|---|---|
amount |
integer | ✅ | Minor units (cents). Minimum 250 (= 2.50 in the currency). |
currency |
string | ✅ | 3-letter ISO, lowercase. |
customer_name |
string | ✅ | 2–120 chars. Needed to attribute the payment. |
customer_email |
string | ✅ | Valid email. Receipt is sent here. |
mode |
string | ➖ | payment (default) or subscription. For subscription, pass line_items[0].price_id pointing at a recurring price and omit amount. |
reference |
string | ➖ | Your order ID. Echoed on receipts and webhooks. |
description |
string | ➖ | Short description shown on the checkout. |
line_items |
array | ➖ | Display only. Fields: name, quantity, amount, image_url, price_id, product_id. |
success_url / cancel_url |
string | ➖ | Where the browser is sent after the session ends. |
metadata |
object | ➖ | Free-form key/value returned in webhooks. |
line_items[].price_id is optional — pass it when the item comes from a product you created in the dashboard, so it appears in receipts and analytics with its catalog link. The actual amount charged is amount — the price is not fetched from the catalog.
Response
{
"id": "cs_01H…",
"object": "checkout.session",
"mode": "live",
"status": "open",
"amount": 4990,
"currency": "EUR",
"client_secret": "cs_01H…_secret_…",
"checkout_url": "https://payments.spuke.com/c/cs_01H…?cs=…",
"embed_url": "https://payments.spuke.com/embed/cs_01H…?cs=…",
"expires_at": 1735776000,
"created": 1735689600
}
Redirect the buyer to checkout_url, or mount embed_url in an iframe.
Retrieve & expire
GET /v1/checkout/sessions/{id}
POST /v1/checkout/sessions/{id}/expire
Sessions expire automatically 24 hours after creation. An expired session emits checkout.session.expired.
Idempotency
Send Idempotency-Key: <your-key> on every POST. Keys are scoped to (merchant_id, method, path) and expire after 24 hours.
- Same key + same body → the original response is replayed verbatim (same status, same body).
- Same key + different body →
409 Conflictwith:
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "Idempotency-Key reused with a different request body"
}
}
See Error reference for the full model.
Fees
Fees are calculated in EUR on your active plan and deducted from the merchant's balance in the transaction currency (converted at live FX). See Fees, currency & minimum amounts.
Errors
| HTTP | error.type |
error.code |
Meaning |
|---|---|---|---|
| 400 | invalid_request |
field-specific | Missing / malformed field. |
| 400 | amount_too_small |
amount_below_minimum |
amount below the 2.50 minimum. |
| 401 | authentication_error |
invalid_api_key |
Bad, revoked or wrong-mode key. |
| 402 | card_error |
card_declined |
Buyer's card was declined. |
| 409 | idempotency_error |
idempotency_key_reused |
Same key sent with a different body. |
| 409 | resource_conflict |
merchant_not_ready |
Merchant onboarding incomplete. |
| 429 | rate_limit_error |
rate_limited |
Slow down. Respect Retry-After. |