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.