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_responseunder_reviewwon 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 }