Authentication

Bearer tokens, modes and installation context.

Base URL:

https://api.southbill.com/v1/app

Every request carries an installation access token:

curl "https://api.southbill.com/v1/app/payments?limit=10" \
  -H "Authorization: Bearer sbat_live_…"
const res = await fetch("https://api.southbill.com/v1/app/payments?limit=10", {
  headers: { Authorization: `Bearer ${accessToken}` },
});
if (!res.ok) throw new Error((await res.json()).error?.type ?? res.statusText);
const { data, has_more } = await res.json();
import requests

res = requests.get(
    "https://api.southbill.com/v1/app/payments",
    params={"limit": 10},
    headers={"Authorization": f"Bearer {access_token}"},
    timeout=30,
)
res.raise_for_status()
payments = res.json()["data"]
<?php
$ch = curl_init("https://api.southbill.com/v1/app/payments?limit=10");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer {$accessToken}"],
]);
$body = json_decode(curl_exec($ch), true);
$payments = $body["data"];

The token identifies the app and the merchant. There is no merchant id parameter — you cannot address a merchant that has not installed you.

Getting a token

Tokens come from the OAuth flow at install time (authorization_code, PKCE supported) and are renewed with the refresh_token grant. See Installs & OAuth.

Property Value
Access token sbat_live_… / sbat_test_…, valid 1 hour (expires_in: 3600)
Refresh token sbrt_live_… / sbrt_test_…, single use and rotating — store the new one on every refresh
Mode Baked into the token, cannot be switched
Scopes Exactly what the merchant approved
Reissue Issuing a new access token revokes the installation's previous access tokens

Because refresh tokens rotate, refreshing twice with the same token invalidates the installation's tokens — serialise your refresh calls.

Failure modes

Status error.type Do this
401 invalid_token Refresh, then retry once
403 insufficient_scope Request the scope in a new version; do not retry
403 merchant_suspended Back off, retry later, keep data
401 installation_inactive Installation revoked or suspended — stop calling
404 not_found Object does not exist for this merchant
404 unknown_endpoint Path typo or unsupported resource
405 method_not_allowed Method not supported on this route. Reads are GET; Store writes use POST / PATCH / DELETE with the products:write, files:write or orders:write scope
429 rate_limit_exceeded Honour Retry-After

Error bodies are uniform:

{ "error": { "type": "insufficient_scope", "message": "Missing required scope: payments:read" } }

Server-side only

The client secret and refresh tokens must never reach a browser or a mobile binary. Public clients use PKCE and keep only the short-lived access token.