Stable Pro

Stable Pro API · v1

Conventions

Amounts, dates, codes, taxes, lists and filters, languages, versions.

Requests and responses

GET reads. Responses are JSON, field names in English snake_case. Every response carries Cache-Control: no-store. Every object has an object field that says what it is (charge, payment, event…).

Amounts

Amounts are integers in the minor units of their currency (ISO 4217), with the currency next to them: €300.00 is 30000 with currency: "EUR"; ¥3,000 is 3000; KWD 25.500 is 25500.

Every amount is the total the customer pays (amount_basis: "gross"): when Stable Pro calculates VAT the total includes it, and Stable Pro gives net and tax next to it. A program that applies its own tax to a Stable Pro amount derives it from that total, it does not add it.

Amounts are signed where the direction matters: an adjustment that lowers a charge is negative, a refund is negative.

Dates

  • Calendar dates (due dates, periods, the date of a payment) are YYYY-MM-DD; months are YYYY-MM.
  • Instants (created_at, issued_at, occurred_at…) are RFC 3339 in UTC: 2026-11-10T14:32:05.120Z.
  • The accounting month (accounting_month) is computed by Stable Pro in the stable's time zone: the later of the competence month and the month the fact was recorded. You never need to do a time-zone calculation.

The stable's time zone, currency, country and language are in GET /organization.

Codes, not text

The contract exposes codes — action, reason, category, origin, charge types, event types — never sentences in a language. Names for people come from the dictionary, in the requested language:

curl "https://app.stable-pro.com/api/v1/codes?lang=de" -H "Authorization: Bearer sp_live_…"

GET /codes lists every family of codes, where each family appears (fields, like adjustment.reason) and a name per code. The name of a reason depends on its action: look it up with both.

Code lists are open: a new value can arrive inside v1. Your integration must accept a code it does not know yet (and can read its name from /codes). taxonomy_version says which version of the adjustment taxonomy produced a row.

Standard lists are used wherever one exists: currency ISO 4217, country ISO 3166-1 and subdivision ISO 3166-2 (IT-MI, US-OH), tax identifiers with an ISO 6523 scheme, tax category UNCL 5305 (S, Z, E, AE, K, G, O), units UNECE Rec. 20 (MON, ANN, C62).

Taxes

Every charge and adjustment says:

  • tax_calculated_by: stable_pro (Stable Pro calculates VAT and gives it), or external — which only means Stable Pro does not calculate it: it does not mean the tax is zero;
  • tax_treatment: standard_rated, zero_rated, not_calculated, or undetermined (an older fact for which it is not known);
  • taxable_amount and taxes[]: a list, even with one element — { type, jurisdiction, category, rate_bp, amount }. Rates are in basis points: 22% is 2200.

A charge is read as it is now: its amount and its taxes are its birth plus its adjustments plus any later tax change.

Lists

A list holds every fact of its kind, paid or not: GET /charges is every charge to the customers (never the staff pay), GET /customers is everyone with at least one charge, member or staff.

Lists are cursor-based:

GET /charges?limit=100&starting_after=<id of the last item you read>
  • limit from 1 to 100 (25 when missing);
  • the envelope is { "object": "list", "data": [...], "has_more": true }; ask again with starting_after = the id of the last item;
  • the order is always from the oldest: created_at, then id (receipts: issued_at, then id).

Filters

Some lists can be narrowed with filters, which apply together, with limit and with the cursor. For example, a customer's charges in one month:

GET /charges?customer=<id of the customer>&accounting_month=2026-10
ListFilters
GET /chargescustomer, accounting_month
GET /adjustmentscharge, accounting_month
GET /paymentscustomer
GET /credit-entriescustomer
GET /receiptscustomer, payment

customer, charge and payment take an id; accounting_month takes YYYY-MM and is the accounting month, the month the fact counts in (see "Dates" above).

Unknown parameters and values that are not valid

A parameter the endpoint does not know is never ignored: the answer is 400 invalid-request, and invalid_param says which. So a misspelt filter (accountingmonth instead of accounting_month) never returns the whole list. The same goes for a limit, a cursor or a filter whose value is not valid. The key and the permissions are checked first: without a key the answer is still 401, without the permission 403.

Lists are for searching and checking; to stay in sync, read the events (Events).

Languages

The contract is in English and never changes with the language. Sentences for people — the detail of an error, the names in /codes — come in the requested language: ?lang= wins over Accept-Language; without either, English. Available: en, it, fr, de, es. The response says the language in Content-Language.

Versions

The version is in the path: /api/v1/. Inside v1 there are only additions: a new field, a new endpoint, a new value of a code list. Anything that would break an integration is a v2, with at least one year of coexistence and the Deprecation and Sunset headers.

Receipts are not tax documents

Stable Pro receipts confirm a payment (fiscal_document: false). The tax document is issued by your software. The stable may choose not to give Stable Pro receipts to its customers while your software is connected: GET /organization says so in receipts_shown_to_customers. The receipts still exist, numbered, and you can read them.