Endpoints
Everything the App API exposes today.
The App API is how your app reads and writes the merchant's data. Paths below are relative to the base URL — /payments means https://api.southbill.com/v1/app/payments. Payments, payouts, invoices, subscriptions and analytics are read-only. The Store surface (products, prices, options, values, variants, files, orders) also supports writes with the products:write, files:write and orders:write scopes — no merchant secret key is needed, the installation's sbat_live_… / sbat_test_… token is enough. Unsupported methods return 405 method_not_allowed.
Write requests should send an Idempotency-Key header; a repeated key returns the original result instead of creating a second object.
Base URL: https://api.southbill.com/v1/app · Auth: Authorization: Bearer sbat_live_… (see Authentication).
Endpoints
| Method | Path | Scope | What it gives you |
|---|---|---|---|
| GET | /merchant |
merchant:read |
Profile of the merchant that installed your app: company, country, VAT number, status. Call once after install to label the connection. |
| GET | /payments |
payments:read |
Processed payments, newest first. Live traffic supports ?status=succeeded. |
| GET | /payments/{id} |
payments:read |
One payment. The {id} is the charge_id in live and the row id in sandbox. |
| GET | /refunds |
refunds:read |
Sandbox: refund rows. Live: refunded charges with the running amount_refunded. |
| GET | /disputes |
disputes:read |
Chargebacks including evidence_due_by and past_due. |
| GET | /payouts |
payouts:read |
Bank settlements. Restricted scope — reconciliation tools only. |
| GET | /balance |
payouts:read |
Available and pending balance of the merchant. |
| GET | /products |
products:read |
The merchant's catalog (no prices in v1). |
| GET | /products/{id} |
products:read |
One product. |
| GET | /invoices |
invoices:read |
Issued invoices. Live only — sandbox returns an empty list. |
| GET | /invoices/{id} |
invoices:read |
One invoice. |
| GET | /subscriptions |
subscriptions:read |
Recurring agreements with status and period end. |
| GET | /analytics/summary |
analytics:read |
Aggregated volume for a window: ?days=30 (1–365). |
Store write endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /store |
store:read |
The merchant's Southbill Store: name, slug, branding, status. |
| POST | /products |
products:write |
Create a store product (name, description, images, active/visible flags). |
| PATCH | /products/{id} |
products:write |
Update a product, incl. default_price_id. |
| DELETE | /products/{id} |
products:write |
Archive a product (soft delete — stays referenced by past orders). |
| POST | /products/{id}/prices |
products:write |
Add a price (minor units + currency). |
| GET | /products/{id}/options |
products:read |
Option groups (Size, Colour) with values. |
| POST | /products/{id}/options |
products:write |
Create an option group. |
| PATCH | /options/{id} |
products:write |
Rename / reorder an option group. |
| DELETE | /options/{id} |
products:write |
Delete an option group — 409 while variants reference it. |
| POST | /options/{id}/values |
products:write |
Add an option value. |
| DELETE | /values/{id} |
products:write |
Delete a value — 409 while a variant uses it. |
| GET | /products/{id}/variants |
products:read |
Variants incl. their option combination. |
| POST | /products/{id}/variants |
products:write |
Create a variant. Duplicate combinations return 409. |
| PATCH | /variants/{id} |
products:write |
Update price, stock, SKU or option combination. |
| DELETE | /variants/{id} |
products:write |
Delete a variant. The implicit default variant cannot be deleted. |
| POST | /files |
files:write |
Upload product images / documents (max 5 MB). |
| GET | /orders |
orders:read |
Store orders. Only Southbill Store payments create orders. |
| GET | /orders/{id} |
orders:read |
One order. |
| PATCH | /orders/{id} |
orders:write |
Update fulfillment status, tracking, carrier. |
| POST | /orders/{id}/evidence |
orders:write |
Attach delivery evidence. |
| POST | /orders |
orders:write |
Sandbox only — create an order fixture to test fulfillment. |
Subscription links
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /subscription_links |
subscriptions:read |
List the merchant's subscription links. |
| GET | /subscription_links/{id} |
subscriptions:read |
One subscription link. |
| POST | /subscription_links |
subscriptions:write |
Create a subscription link (amount in minor units, currency, interval). |
| POST | /subscription_links/{id} |
subscriptions:write |
Update a subscription link. |
| POST | /subscription_links/{id}/archive |
subscriptions:write |
Archive a subscription link. |
Subscription links are only available on live installations; the merchant and store are always taken from the installation, never from the request body.
Ownership is always derived from the access token, never from ids in the body: an app can only touch the store it was installed on, in the environment of its token.
Field-by-field descriptions of every response are in Objects & fields.
Query parameters
| Parameter | Where | Behaviour |
|---|---|---|
limit |
all list endpoints | 1–100, default 25. |
offset |
all list endpoints | Row offset, default 0. |
status |
/payments (live) |
Exact match, e.g. ?status=succeeded. |
days |
/analytics/summary |
1–365, default 30. |
Unknown query parameters are ignored, not rejected. There are no date-range filters in v1 — page by offset and stop at the first object older than your cursor.
Pagination
curl "https://api.southbill.com/v1/app/payments?limit=50&offset=100" \
-H "Authorization: Bearer sbat_live_…"
async function* allPayments(accessToken) {
for (let offset = 0; ; offset += 50) {
const res = await fetch(
`https://api.southbill.com/v1/app/payments?limit=50&offset=${offset}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const page = await res.json();
yield* page.data;
if (!page.has_more) return;
}
}
def all_payments(access_token):
offset = 0
while True:
page = requests.get(
"https://api.southbill.com/v1/app/payments",
params={"limit": 50, "offset": offset},
headers={"Authorization": f"Bearer {access_token}"},
timeout=30,
).json()
yield from page["data"]
if not page["has_more"]:
return
offset += 50
<?php
$offset = 0;
do {
$ch = curl_init("https://api.southbill.com/v1/app/payments?limit=50&offset={$offset}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"],
]);
$page = json_decode(curl_exec($ch), true);
foreach ($page["data"] as $payment) {
// handle $payment
}
$offset += 50;
} while ($page["has_more"]);
{ "object": "list", "data": [ … ], "has_more": true }
Lists are sorted newest first and carry no total count. Iterate until has_more is false. New objects arrive while you page, so for exports read the first page frequently rather than deep-paging a moving list.
Test vs live
The token decides the data source: a sandbox token reads your environment's simulated data, a live token reads the merchant's real data. Field names differ between the two — see the table at the top of Objects & fields.
Amounts and timestamps
Amounts are integer minor units with a lowercase ISO currency: { "amount": 2500, "currency": "eur" } is €25.00. Never use floats. Timestamps are unix seconds (UTC integers) — including arrival_date, issue_date and due_date, which are midnight UTC of the respective day. See Objects & fields.
Errors
{ "error": { "type": "insufficient_scope", "message": "Missing required scope: payments:read" } }
See Error & status codes.