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.