https://api.southpay.io/api/v2.
Authentication and scope
Reads requireaccounts:read, writes require accounts:write, and acting on a sub-merchant requires accounts:act. None of the three are in the default API key scope set. A key created before your store became a platform will not hold them. Issue a new key with the platform scopes.
Create a sub-merchant
POST /platform/accounts. Scope accounts:write.
All fields go inside a required account object.
string
required
The sub-merchant’s business name. Whitespace is trimmed; a blank value is rejected.
string
Your own identifier for this merchant. Creation is idempotent on this value. Posting the same
external_id twice returns the existing account rather than creating a second one.string
Your fee on this sub-merchant’s payments, as a decimal fraction (
"0.015" is 1.5%).string
ISO country code. Used once, to seed the new store’s defaults. Ignored on update.
Response fields
string
required
The sub-merchant’s store id. Send this in
X-Southpay-Account to act on their behalf.string
required
string | null
Your identifier, as supplied at create time.
string | null
The effective fee as a string, not a number.
string | null
When verification was approved. Null until then.
boolean
required
True once KYC is confirmed. Until then the sub-merchant cannot take payments.
boolean
required
True once the sub-merchant has chosen a payout destination.
List sub-merchants
GET /platform/accounts. Scope accounts:read.
Returns a bare array of active sub-merchants, newest first. No envelope, no pagination.
string
Exact match on your own identifier. The way to resolve one of your ids to a SouthPay account id.
Retrieve a sub-merchant
GET /platform/accounts/{id}. Scope accounts:read. Returns a bare account object.
An unknown id returns 404 with a machine code: {"error": {"message": "Unknown sub-merchant account", "code": "account_not_found"}}.
Update a sub-merchant
PATCH /platform/accounts/{id}. Scope accounts:write.
Applies name, platform_fee_percentage and external_id. Re-using an external_id that another of your sub-merchants already holds is refused with 422, and the account keeps its previous value.
country is accepted by the request and ignored. It only seeds default assets at creation time and cannot be changed afterwards.Delete a sub-merchant
DELETE /platform/accounts/{id}. Scope accounts:write.
Retires a sub-merchant you own. The account is soft-deleted, its API keys and agent credentials are revoked, and its webhook endpoints are disabled. Ledger history is preserved, and a store.deleted platform event is emitted. Returns 204 with no body.
The account stops appearing in GET /platform/accounts and can no longer be acted on through the X-Southpay-Account header.
409 and a blockers list naming what is in the way: store_balance, pending_payout, in_flight_payment_intent or pending_refund. Settle or pay out those first, then retry. There is no force flag on this endpoint. Funds are never stranded by a platform call.
Restore a sub-merchant
POST /platform/accounts/{id}/restore. Scope accounts:write.
Reverses a delete and returns the account object. Revoked API keys are not reissued and disabled webhook endpoints stay disabled; re-create those as needed.
Restoring an account that was never deleted returns 422 with code account_not_deleted.
Attest verification
POST /platform/accounts/{account_id}/verification. Scope accounts:write.
Submits KYB details you have already collected, so the sub-merchant does not have to re-enter them. Fields may be sent inside a verification_request object or at the top level.
string
string
string
string
string
string
string
string
201 with the verification request: id, status, the submitted fields, plus rejection_reason, submitted_at, reviewed_at, created_at, updated_at.
status is pending, approved or rejected. Approval is a human review. Attesting does not approve anything by itself. Subscribe to store.verification.approved and store.verification.rejected to learn the outcome; both fan out to the platform’s own webhook endpoints.
Issue an onboarding link
POST /platform/accounts/{account_id}/onboarding_links. Scope accounts:write.
Creates a single-use dashboard link the sub-merchant opens to complete a step themselves. Fields may be sent inside an onboarding_link object or at the top level.
string
onboarding, kyb or payout_destination. Pick payout_destination when the only thing missing is where their money goes. A platform is not allowed to set that itself.string
Where the sub-merchant lands after finishing.
integer
Link lifetime in seconds. Defaults to 7 days and is capped at 30 days.
Response 201
token and url are returned only on create. Retrieving the link later gives you token_prefix and nothing usable. Hand the URL to the sub-merchant immediately or store it securely.422 {"error": {"message": "…", "code": "invalid_onboarding_link"}}.
Platform overview
GET /platform/overview. Scope accounts:read.
Aggregate figures across your sub-merchants for a date range.
string
ISO date. Defaults to 29 days before
to.string
ISO date. Defaults to today.
Response 200
activated_count is the number of sub-merchants with confirmed KYC. platform_fee_earned is your accrued fee per asset in atomic units, net of debits. top_sub_merchants is capped at five.
Platform payments
GET /platform/payments. Scope accounts:read.
Payments across every sub-merchant, newest first. This is a purpose-built summary, not the payment intent shape. There is no reference lookup, no addresses and no metadata.
string
Limit to one sub-merchant.
string
pending, processing, completed, failed, expired or refunded. Unrecognized values are ignored rather than rejected.integer
default:"1"
integer
default:"25"
Between 1 and 100.
Response 200
settlement_amount_cents is an integer here, unlike the two-decimal settlement_amount string on a payment intent. platform_fee_percentage is the rate snapshotted at payment time, as a string, and may be null on older payments.
Acting on a sub-merchant
To call any other endpoint as a sub-merchant, add the header:accounts:act in addition to the scope the target endpoint needs. Everything then behaves as if the sub-merchant made the call, against their balances, their payments and their webhooks.
Request
Guardrails
Two things a platform can never do while acting, regardless of scope:
For payout destinations, issue an onboarding link with
purpose: "payout_destination" and let the sub-merchant set it themselves.
Header errors
Webhook fan-out
Payment and store-verification events for a sub-merchant are delivered twice: once to the sub-merchant’s own endpoints, and once to yours with an extra top-levelaccount key holding the sub-merchant’s store id. Use that key to route the event.
Billing events do not fan out to platforms.

