draft you can edit freely, becomes open when you finalize it, and is then payable through a hosted page the customer opens with a public token.
For recurring charges, see Recurring. Subscriptions generate invoices through this same resource.
Paths are relative to https://api.southpay.io/api/v2.
Authentication and scope
Reads requirebilling:read, writes require billing:write. Publishable keys are rejected. The two hosted-invoice endpoints are unauthenticated and identified by a public token instead.
Idempotency keys are not used anywhere in billing. Retrying a create makes a second record.
Customers
Create a customer
POST /billing/customers. Scope billing:write. Fields go inside a required customer object.
string
required
Must be unique within the store.
string
string
object
Free-form address object stored as sent and rendered on invoices.
object
Arbitrary key-value pairs.
409 {"error": "A customer with this email already exists"}.
List customers
GET /billing/customers. Scope billing:read. Active customers only, newest first.
string
Case-insensitive substring match on email or name.
integer
default:"1"
integer
default:"25"
Between 1 and 100.
Response 200
Retrieve, update and delete
GET /billing/customers/{id}. Scope billing:read.
PATCH /billing/customers/{id}. Scope billing:write. Accepts the same fields as create. Returns 200.
DELETE /billing/customers/{id}. Scope billing:write. Returns 204 with no body.
Deleting a customer is a soft delete. The record stops appearing in the list and cannot be used on new invoices, but existing invoices keep their
customer_id and still resolve the embedded customer object. Nothing is destroyed, and the email is not freed for reuse.404 {"error": "Customer not found"}.
Invoices
Create an invoice
POST /billing/invoices. Scope billing:write. Creates a draft. Fields go inside a required invoice object.
string
required
Must be an active customer in this store.
string
required
Fiat currency for the whole invoice.
object[]
string
ISO 8601 due date.
string
string
Override the auto-assigned invoice number. Must be unique in the store.
string
The customer’s purchase order reference.
string
amount or percent.integer
Minor units when
discount_type is amount; percentage points when it is percent.integer
Tax rate in basis points. 2000 is 20%.
string
Shown on the invoice above the footer.
Shown at the bottom of the invoice.
object
total_cents = subtotal_cents - discount_cents + tax_cents. Do not compute them yourself; read them back.
Response fields
integer
required
Sequential per store.
string
required
The display number. Reflects
custom_number when one is set.string | null
The token in the hosted invoice URL. Null while the invoice is a draft.
string | null
Set when the invoice was generated by a subscription cycle.
string | null
Identifies which billing cycle produced the invoice. Deduplicates cycles.
integer
required
How much has been received against this invoice.
object[]
Present on retrieve, create, update and the action endpoints.
amount_cents is quantity * unit_amount_cents; position is the display order.List invoices
GET /billing/invoices. Scope billing:read. Newest first.
string
draft, open, paid, void or uncollectible.string
string
Only invoices generated by one subscription.
integer
default:"1"
integer
default:"25"
Between 1 and 100.
{data, meta} envelope.
Retrieve and update
GET /billing/invoices/{id}. Scope billing:read. Includes line_items.
PATCH /billing/invoices/{id}. Scope billing:write. Draft only. Accepts currency, due_at, description, metadata, all the enhancement fields, and line_items. Sending line_items replaces the whole set. Editing an open invoice returns 422 {"error": "Only draft invoices can be edited"}.
Finalize
POST /billing/invoices/{id}/finalize. Scope billing:write. No body.
Moves draft to open, stamps finalized_at, assigns public_token, and emits invoice.finalized. Line items and totals are locked from here.
Send
POST /billing/invoices/{id}/send. Scope billing:write. No body.
Emails the hosted invoice link to the customer. The invoice must be open and the customer must have an email.
Void
POST /billing/invoices/{id}/void. Scope billing:write. No body.
Cancels a draft or open invoice, stamps voided_at, and emits invoice.voided. Voiding an already-void invoice is a 200 no-op. A paid invoice returns 422 {"error": "Cannot void a paid invoice"}. Refund the underlying payment instead.
Mark uncollectible
POST /billing/invoices/{id}/mark_uncollectible. Scope billing:write. No body.
Writes off an open invoice you have given up collecting. It stays on the books, unlike a void.
Delete
DELETE /billing/invoices/{id}. Scope billing:write.
Draft only, and a genuine hard delete: the row and its line items are removed. Returns 204 with no body. An open invoice returns 422 {"error": "Only draft invoices can be deleted"}. Void it instead.
Invoice lifecycle
Allowed transitions:
draft to open or void; open to paid, void or uncollectible. Nothing leaves a terminal state.
Three webhook events fire here: invoice.finalized, invoice.paid and invoice.voided. There is no event for a draft being created or for uncollectible. Billing events carry the full invoice object in data.object, unlike payment events, which carry a small envelope. Billing events also do not fan out to platform accounts.
Hosted invoice page
Two unauthenticated endpoints back the page the customer opens. They are keyed onpublic_token, not on the invoice id, and require no credential. Send the customer the URL and do not proxy these yourself.
GET /billing/invoice/{token} returns the invoice as the customer sees it: public_token, number, formatted_number, po_number, status, currency, subtotal_cents, discount_cents, tax_cents, total_cents, due_at, description, notes, footer, amount_paid_cents, merchant_name, customer{name, email}, line_items[], and payment, which is a checkout object or null if no payment has been started.
POST /billing/invoice/{token}/payment_intent creates the crypto payment intent for the invoice and returns the same body with payment populated.
A draft invoice returns 404 {"error": "Invoice not found"} on both, since drafts have no token. A non-payable invoice returns 422 on the second.

