Objects & fields

The shape of every resource you can read.

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

Sandbox and live return the same object shape. The only difference is the data source and the livemode flag — you never need two parsers.

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 unix seconds created, arrival_date, due_date, current_period_end are integers (UTC seconds), or null.
livemode tells you the source false = your sandbox environment, true = the installed merchant's real data.
IDs are opaque Sandbox uses UUIDs, live uses processor IDs (ch_…, dp_…, po_…). Never parse them.
Objects are additive Ignore unknown fields instead of failing.
object names the type payment, refund, dispute, payout, product, invoice, subscription, merchant, balance, analytics_summary, list.
Fields that do not exist in one environment are null e.g. fee / net are sandbox-only, past_due is live-only.

Lists are { "object": "list", "data": [...], "has_more": bool } and accept ?limit= (max 100) and ?offset=.


Merchant — GET /merchant

{ "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": 1755423672 }
Field Meaning
business_name Legal/company name from onboarding.
status Onboarding state — only approved merchants process live payments.
plan_id The merchant's Southbill plan; null in sandbox.
created Unix seconds of the account creation.

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

{ "object": "payment", "livemode": true, "id": "ch_3Nk91x", "payment_intent": "pi_3Nk91x",
  "charge": "ch_3Nk91x", "amount": 2500, "amount_refunded": 0, "fee": null, "net": null,
  "currency": "eur", "status": "succeeded", "refunded": false, "disputed": null,
  "description": "Order #1042", "customer_email": "ada@example.com", "customer_name": "Ada Lovelace",
  "payment_method_type": "card", "card_brand": "visa", "card_last4": "4242", "card_country": "DE",
  "receipt_url": "https://…", "failure_code": null, "failure_message": null,
  "created": 1755423672 }
Field Meaning
id Use it for GET /payments/{id}. Live: the charge id. Sandbox: the row id.
amount / amount_refunded Minor units. amount - amount_refunded is what the merchant kept.
fee / net Simulated Southbill fee and net amount — sandbox only, null in live.
status succeeded means paid. Also pending, failed, canceled.
refunded true once fully refunded; a non-zero amount_refunded with false is a partial refund.
created Unix seconds; also the sort key of the list.

Filter live payments with ?status=succeeded.


Refund — GET /refunds

{ "object": "refund", "livemode": true, "id": "ch_3Nk91x_refund", "charge": "ch_3Nk91x",
  "payment_intent": "pi_3Nk91x", "amount": 500, "currency": "eur",
  "status": "succeeded", "reason": null, "created": 1755500530 }

In live, one row represents the running refunded total of a charge (reason is null). In sandbox, each refund row is returned individually including its reason.


Dispute — GET /disputes

{ "object": "dispute", "livemode": true, "id": "dp_7c", "charge": "ch_3Nk91x",
  "payment_intent": "pi_3Nk91x", "amount": 2500, "fee": null, "currency": "eur",
  "status": "needs_response", "reason": "fraudulent", "evidence_due_by": 1756684799,
  "evidence_submitted": false, "evidence_submitted_at": null, "past_due": false,
  "closed_at": null, "created": 1755602100 }
Field Meaning
status needs_response → under_review → won or lost.
evidence_due_by Hard deadline in unix seconds.
evidence_submitted Boolean — whether evidence was filed. Present in both environments.
evidence_submitted_at Unix seconds; sandbox only (null in live — use evidence_submitted).
past_due Live only — null in sandbox.
closed_at Sandbox only; in live read status for the outcome.

Payout — GET /payouts

{ "object": "payout", "livemode": true, "id": "po_1M2", "amount": 128400, "currency": "eur",
  "status": "paid", "method": "standard", "destination_bank_name": "N26",
  "destination_last4": "4321", "arrival_date": 1755561600,
  "failure_code": null, "failure_message": null, "created": 1755561600 }

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

{ "object": "product", "livemode": true, "id": "…", "name": "T-Shirt", "description": "…",
  "active": true, "images": [], "tags": [], "unit_label": null, "url": null,
  "created": 1750000000, "updated": 1755000000 }

Prices are managed through the Store surface in App API v1. Create them with POST /products/{id}/prices using the products:write scope. The product response does not inline a price list; use the dedicated price route. Option groups and variants are available through /products/{id}/options and /products/{id}/variants.n App API v1: create them with POST /products/{id}/prices using products:write. The product object itself does not inline a price list; retrieve or manage prices through that dedicated route.


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

{ "object": "invoice", "livemode": true, "id": "…", "invoice_number": "SPK-INV-2026-0042",
  "status": "paid", "currency": "eur", "subtotal_amount": 10000, "tax_amount": 1900,
  "total_amount": 11900, "customer_name": "Ada GmbH", "customer_email": "ada@example.com",
  "customer_type": "business", "issue_date": 1755000000, "due_date": 1757000000,
  "paid_at": 1755600000, "created": 1755000000 }

Live only. In sandbox, the list is empty and GET /invoices/{id} returns 404.


Subscription — GET /subscriptions

{ "object": "subscription", "livemode": true, "id": "…", "status": "active",
  "currency": "eur", "amount": 1990, "quantity": 1,
  "customer_email": "ada@example.com", "customer_name": "Ada Lovelace",
  "current_period_start": 1755000000, "current_period_end": 1757592000,
  "cancel_at_period_end": false, "created": 1750000000 }

Balance — GET /balance

{ "object": "balance", "livemode": true, "available": [{ "amount": 128400, "currency": "eur" }],
  "pending": [{ "amount": 25000, "currency": "eur" }] }

Analytics — GET /analytics/summary?days=30

{ "object": "analytics_summary", "livemode": true, "period_days": 30, "currency": "eur",
  "gross_amount": 1284000, "net_amount": 1240000, "payment_count": 412,
  "refunded_amount": 12000 }

`period_days` echoes the requested window (max 90). `net_amount` is
`gross_amount - refunded_amount`. Only succeeded payments are counted.