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 }
| 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. |
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": null, "amount": 2500, "fee": null, "currency": "eur",
"status": "needs_response", "reason": "fraudulent", "evidence_due_by": 1756684799,
"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. |
past_due |
Live only — null in sandbox. |
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 not exposed in App API v1.
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, "days": 30, "currency": "eur",
"gross_amount": 1284000, "net_amount": 1240000, "payment_count": 412,
"refund_amount": 12000, "refund_count": 3 }