# 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`

```json
{ "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}`

```json
{ "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`

```json
{ "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`

```json
{ "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`

```json
{ "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}`

```json
{ "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}`

```json
{ "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`

```json
{ "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`

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

---

## Analytics — `GET /analytics/summary?days=30`

```json
{ "object": "analytics_summary", "livemode": true, "days": 30, "currency": "eur",
  "gross_amount": 1284000, "net_amount": 1240000, "payment_count": 412,
  "refund_amount": 12000, "refund_count": 3 }
```

