Objects & fields

The shape of every resource you can read.

This page describes the actual JSON the App API returns, field by field.

One thing to internalise first: the API serves two different data sources depending on the token.

Token Data source Field style
spk_at_test_… (sandbox environment) Your simulated environment data amount_cents, created_at, UUID id
spk_at_live_… (installed merchant) The merchant's real processed data amount, charge_created_at, processor IDs

Both are documented below. Write your parser against both, or gate features by livemode.

Global rules

Rule What it means
Amounts are integers in minor units 2500 with "currency": "eur" is €25.00. Never use floats.
Currencies are lowercase ISO-4217 eur, chf, usd.
Timestamps are ISO-8601 UTC strings 2026-08-17T09:41:12Z. Date-only fields (arrival_date, due_date) have no time part.
IDs are opaque Sandbox rows use UUIDs, live rows use processor IDs (ch_…, pi_…, po_…). Never parse them.
Objects are additive Ignore unknown fields instead of failing.
object names the type payment, product, invoice, merchant, balance, analytics_summary, list.

Merchant — GET /v1/merchant

Who installed your app.

{ "object": "merchant", "id": "…", "business_name": "Ada GmbH", "email": "ops@ada.de",
  "country": "DE", "city": "Berlin", "vat_number": "DE123456789", "reference": "M-10423",
  "status": "approved", "plan_id": "…", "livemode": true, "created_at": "2026-05-02T08:00:00Z" }
Field Meaning
business_name Legal/company name from onboarding.
reference Short merchant reference used in support tickets.
status Onboarding state — only approved merchants process live payments.
plan_id The merchant's spuke plan; null in sandbox.
livemode false means you are talking to a sandbox test merchant.

Payment — GET /v1/payments, /v1/payments/{id}

One processed payment. In sandbox the row is a simulated checkout session; in live it is the real charge.

Live

{ "object": "payment", "livemode": true, "charge_id": "ch_3Nk91x", "payment_intent_id": "pi_3Nk91x",
  "amount": 2500, "amount_refunded": 0, "currency": "eur", "status": "succeeded", "refunded": false,
  "customer_email": "ada@example.com", "customer_name": "Ada Lovelace", "description": "Order #1042",
  "payment_method_type": "card", "card_brand": "visa", "card_last4": "4242", "card_country": "DE",
  "receipt_url": "https://…", "failure_code": null, "failure_message": null,
  "charge_created_at": "2026-08-17T09:41:12Z" }
Field Meaning
charge_id The identifier of this payment. Use it for /v1/payments/{id} and to match refunds/disputes.
payment_intent_id The intent the charge belongs to; matches payment_intent in webhook payloads.
amount Amount charged, minor units. Never changes.
amount_refunded Total refunded so far. amount - amount_refunded is what the merchant kept.
refunded true once fully refunded.
status succeeded (money captured), pending, failed. Only succeeded means paid.
customer_email / customer_name Buyer details from checkout — personal data.
payment_method_type card, twint, sepa_debit, paypal, …
card_brand / card_last4 / card_country Display only. Full card data never leaves the processor.
receipt_url Hosted receipt you can link to.
failure_code / failure_message Populated on failed payments.
charge_created_at When the payment was processed. Also the sort key of the list.

Sandbox

{ "object": "payment", "livemode": false, "id": "…uuid", "amount_cents": 2500,
  "amount_refunded_cents": 0, "fee_cents": 74, "net_cents": 2426, "currency": "eur",
  "status": "completed", "customer_email": "test@spuke.test", "customer_name": "Test Buyer",
  "description": "Order #1", "payment_method_type": "card", "card_brand": "visa", "card_last4": "4242",
  "failure_code": null, "failure_message": null, "disputed": false,
  "payment_intent_id": "pi_test_…", "charge_id": "ch_test_…", "created_at": "2026-08-17T09:41:12Z" }
Extra field Meaning
fee_cents / net_cents Simulated spuke fee and net amount credited to the test balance.
disputed true once you simulated a dispute on it.
status Sandbox uses completed / open / failed / canceled.

Filter live payments with ?status=succeeded.


Refund — GET /v1/refunds

Sandbox returns real refund rows:

{ "id": "…uuid", "amount_cents": 500, "currency": "eur", "status": "succeeded",
  "reason": "requested_by_customer", "charge_id": "ch_test_…", "payment_intent_id": "pi_test_…",
  "created_at": "2026-08-18T07:02:10Z" }

Live returns the refunded charges (one row per charge, not per refund):

{ "charge_id": "ch_3Nk91x", "amount_refunded": 500, "currency": "eur",
  "refunded": false, "updated_at": "2026-08-18T07:02:10Z" }

refunded: false with a non-zero amount_refunded means a partial refund. amount_refunded is the authoritative running total for that payment.


Dispute — GET /v1/disputes

A chargeback: the disputed amount is held and there is a hard deadline.

Live

{ "dispute_id": "dp_7c", "charge_id": "ch_3Nk91x", "amount": 2500, "currency": "eur",
  "status": "needs_response", "reason": "fraudulent", "evidence_due_by": "2026-08-31T23:59:59Z",
  "has_evidence": false, "past_due": false, "dispute_created_at": "2026-08-19T11:15:00Z" }
Field Meaning
status needs_response (act now) → under_reviewwon (funds returned) or lost (funds gone plus a fee).
reason Why the bank reversed it — drives which evidence helps.
evidence_due_by Hard deadline. Missing it is an automatic loss. Surface it prominently.
has_evidence Whether evidence was already submitted.
past_due true once the deadline passed.

Sandbox adds id, session_id, amount_cents, fee_cents, evidence_submitted_at, closed_at, created_at.


Payout — GET /v1/payouts

A transfer of the merchant's balance to their bank. It bundles many payments — never 1:1 with a payment. Requires the restricted payouts:read scope.

{ "payout_id": "po_44", "amount": 128400, "currency": "eur", "status": "paid",
  "method": "standard", "arrival_date": "2026-08-21",
  "destination_bank_name": "Commerzbank", "destination_last4": "4021",
  "payout_created_at": "2026-08-19T02:00:00Z" }
Field Meaning
amount Net amount sent to the bank, after fees, refunds and reserves.
status pendingin_transitpaid, or failed (returned to the balance).
arrival_date Expected bank value date (date only).
destination_* Which bank account received it.

Sandbox rows use id, amount_cents, created_at plus failure_code / failure_message.


Balance — GET /v1/balance

Money currently held for the merchant. Same scope as payouts.

{ "object": "balance", "livemode": false,
  "available": [{ "amount": 240000, "currency": "eur" }],
  "pending":   [{ "amount":  35000, "currency": "eur" }] }
Field Meaning
available Matured funds that can be paid out.
pending Funds still inside the settlement delay (T+2 in sandbox).

Live balances are served as empty arrays unless the installation has settlement access.


Product — GET /v1/products, /v1/products/{id}

{ "object": "product", "id": "…uuid", "name": "Pro plan", "description": "…", "active": true,
  "images": ["https://…"], "tags": ["saas"], "unit_label": "seat", "url": "https://…",
  "created_at": "…", "updated_at": "…" }
Field Meaning
active false = archived. Hide from new purchases, keep for history.
unit_label What one unit is called on the checkout page.
images / tags / url Listing data maintained by the merchant.

Prices are not embedded — they belong to the merchant's price list and are not exposed in v1.


Invoice — GET /v1/invoices, /v1/invoices/{id}

Live only; sandbox returns an empty list.

{ "object": "invoice", "id": "…uuid", "invoice_number": "2026-0301", "status": "paid",
  "currency": "eur", "subtotal_amount": 1900, "tax_amount": 361, "total_amount": 2261,
  "customer_name": "Ada GmbH", "customer_email": "ops@ada.de", "customer_type": "business",
  "issue_date": "2026-08-01", "due_date": "2026-09-01", "paid_at": "2026-08-04T10:00:00Z",
  "created_at": "2026-08-01T09:00:00Z" }
Field Meaning
invoice_number Legally sequential number. Show this to humans, use id in code.
subtotal_amount / tax_amount / total_amount Net, tax and gross, minor units.
customer_type individual or business — drives tax treatment.
status draft (not sent) · open (sent, unpaid) · paid · void (cancelled) · uncollectible (written off).
issue_date / due_date Dates only.
paid_at null until settled.

Subscription — GET /v1/subscriptions

{ "id": "…uuid", "status": "active", "currency": "eur", "amount_minor": 1900, "quantity": 1,
  "customer_email": "ada@example.com", "customer_name": "Ada Lovelace",
  "current_period_start": "2026-08-17T09:41:12Z", "current_period_end": "2026-09-17T09:41:12Z",
  "cancel_at_period_end": false, "created_at": "2026-05-02T08:00:00Z" }
Field Meaning
amount_minor × quantity What is billed each period.
current_period_end When the next invoice is generated — use it as the access expiry date.
cancel_at_period_end true = still active now, ends at current_period_end. Don't revoke access yet.
status trialing / active → grant access. past_due → dunning, grace period is your call. canceled / unpaid → revoke.

Analytics summary — GET /v1/analytics/summary?days=30

Pre-aggregated volume; far cheaper than paging every payment. days is clamped to 1–365 (default 30).

{ "object": "analytics_summary", "livemode": true, "period_days": 30, "payment_count": 512,
  "gross_amount": 1284000, "refunded_amount": 21500, "net_amount": 1262500, "currency": "eur" }
Field Meaning
period_days Length of the window, ending now.
payment_count Number of succeeded payments in the window.
gross_amount Sum of succeeded payments.
refunded_amount Refunded amount in the window.
net_amount gross_amount - refunded_amount. Processor fees are not deducted here.
currency Currency of the first row; null when there was no volume.

Aggregation reads at most the 1 000 most recent payments of the window.


List envelope

Every list endpoint wraps results the same way:

{ "object": "list", "data": [ … ], "has_more": true }
Field Meaning
data The array of objects for this page.
has_more true = keep paging with a higher offset. There is no total count and no cursor.

Error envelope

{ "error": { "type": "insufficient_scope", "message": "Missing required scope: payments:read" } }