id and an object field naming its type. Formats are described in Requests and responses.
Money fields are decimal strings with four decimal places. Timestamps are ISO 8601 in UTC. Calendar dates are
YYYY-MM-DD in the subscription’s timezone.Organisation
The organisation the API key belongs to. Retrieve it.{
"id": "7c3e9a1b-5d20-4f68-a4b7-8e6d2c0f9a99",
"object": "organisation",
"name": "Northwind Software",
"timezone": "Australia/Sydney",
"default_currency": "AUD",
"setup_mode": false,
"can_create_xero_contacts": true,
"livemode": true
}
| Field | Type | Description |
|---|---|---|
id | string | The organisation’s id. |
name | string | The organisation’s name. |
timezone | string | The organisation’s timezone. Dates you send are read in this timezone. |
default_currency | string or null | The default currency code, such as AUD. |
setup_mode | boolean | true while Setup Mode is on. No invoices are raised in Xero. |
can_create_xero_contacts | boolean | Whether an owner or admin has allowed the API to create Xero contacts. |
livemode | boolean | false when the key belongs to a sandbox, true for a live organisation. Every webhook event carries the same value. |
Customer
A customer, synced from a Xero contact. Endpoints.{
"id": "3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11",
"object": "customer",
"code": "ACME-001",
"name": "Acme Pty Ltd",
"first_name": null,
"last_name": null,
"email": "accounts@acme.example",
"status": "active",
"xero_contact_id": "d5f2a8c1-6b73-4e09-8a1d-4c7e9b3f0a12",
"created": "2026-08-14T02:31:08.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The Saasybill customer id. |
code | string or null | Your own reference for the customer. Unique in the organisation. It is the contact number in Xero. |
name | string or null | The contact’s name in Xero. Xero requires one, so it is null only for a customer imported without it. |
first_name | string or null | The contact person’s first name, in Xero. |
last_name | string or null | The contact person’s last name, in Xero. |
email | string or null | The customer’s email address. |
status | string | active or inactive. |
xero_contact_id | string or null | The id of the contact in Xero. |
created | timestamp | When the customer was added to Saasybill. |
A customer with no code can’t be found by
customer_code. Give the customer a code before your integration needs to look them up.Plan
A pricing plan. Endpoints. For how each type prices, see Plans explained.{
"id": "b6c1e0a4-2f7d-4a61-8f0c-5d3e9a7b2c44",
"object": "plan",
"code": "TEAM-MONTHLY",
"name": "Team",
"type": "per_unit",
"interval": { "unit": "month", "count": 1 },
"currency": "AUD",
"price": "12.0000",
"minimum_units": 5,
"free_units": 0,
"unit_labels": { "singular": "seat", "plural": "seats" },
"allows_price_override": false,
"status": "active",
"tiers": [],
"meters": [],
"created": "2026-06-02T23:10:44.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The plan id. |
code | string | The plan’s code. Unique in the organisation. |
name | string | The plan’s name. |
type | string | flat_fee, per_unit, tiered, volume, stairstep or no_base_fee. |
interval | object | The billing period: unit is day, week, month or year, and count is how many of them. |
currency | string | The plan’s currency code. |
price | string | The flat fee, or the per-unit price. On tiered, volume and stairstep plans, read the prices in tiers. 0.0000 on a no_base_fee plan. |
minimum_units | integer | The fewest units a subscription can have. This is about the recurring fee’s units, not a meter’s minimum. |
free_units | integer | Units included at no charge. |
unit_labels | object | The singular and plural names of a unit, such as seat and seats. Either can be null. |
allows_price_override | boolean | true when a flat fee subscription can set its own flat_fee_price. |
status | string | draft, active, inactive or legacy. Only active and draft plans can be sold. |
tiers | array | The price bands, ordered by from_units. Empty on flat fee and per unit plans. |
meters | array | The meters the plan bills for beside its recurring fee, each priced on its own. Empty for a plan that bills no usage. |
created | timestamp | When the plan was created. |
Plan types
| Type | How it prices |
|---|---|
flat_fee | One price per period, whatever the units. Takes no units. |
per_unit | A price multiplied by the units, less any free units. |
tiered | Each band of units is priced at its own rate, and the bands add up. |
volume | The band the total falls in sets one rate for every unit. |
stairstep | The band the total falls in sets one price for the whole quantity. |
no_base_fee | No recurring fee. The plan bills only its meters, in arrears. It has no units and its price is 0.0000. |
Tier
| Field | Type | Description |
|---|---|---|
from_units | integer | The first unit in the band. |
to_units | integer or null | The last unit in the band. null for the top band, which has no limit. |
price | string | The band’s price. |
Plan meter
A meter a plan bills for, and how. A plan can bill any number of meters. Usage is billed in arrears, when the subscription’s period ends. See Usage-based billing.{
"meter": "c4d8e2f6-7a19-4b30-95e1-3f6a8b0d2c55",
"meter_code": "api_calls",
"name": "API Calls",
"unit_labels": { "singular": "call", "plural": "calls" },
"pricing_model": "per_unit",
"price": "0.0200",
"included_units": 10000,
"minimum_units": 0,
"tiers": []
}
| Field | Type | Description |
|---|---|---|
meter | string | The Saasybill meter id. |
meter_code | string | The meter’s ID as shown in Saasybill, such as api_calls. It is what POST /v1/usage_events takes. |
name | string | The meter’s name. |
unit_labels | object | What one unit of this meter is called: singular and plural. Either can be null. |
pricing_model | string | per_unit, tiered, volume or stairstep. These price as the plan types of the same name do. |
price | string or null | The rate per unit. Only for per_unit. null when the meter prices from tiers. |
included_units | integer or null | Units included before charging starts. Only for per_unit. null for the others, where a free first band does the same job. |
minimum_units | integer | The least a period bills for this meter. A period bills the larger of what was reported and this, before the pricing. 0 means none. A subscription can raise it for itself. |
tiers | array | The meter’s price bands, as for a plan’s tiers. Empty for per_unit. Rates can have up to four decimal places. |
Charge
A one-off fee that can be added to a new subscription. It bills once and doesn’t recur. Endpoints.{
"id": "e2a7d5c8-91b3-4d16-a0f4-7c8b6e5d3a22",
"object": "charge",
"code": "ONBOARDING",
"name": "Onboarding",
"description": "One-off setup and training",
"currency": "AUD",
"price": "450.0000",
"allows_price_override": false,
"status": "active",
"created": "2026-06-02T23:14:09.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The charge id. Charges are sent on a subscription by id. |
code | string | The charge’s code. |
name | string | The charge’s name. |
description | string or null | A description shown on the invoice line. |
currency | string | The charge’s currency. It must match the plan’s. |
price | string | The charge’s price. |
allows_price_override | boolean | true when a subscription can set its own price for this charge. |
status | string | draft, active, inactive or legacy. |
created | timestamp | When the charge was created. |
Subscription
A customer’s subscription to a plan. Endpoints. For the lifecycle, see Subscriptions explained.{
"id": "9d4e2b7a-13c5-4f8e-b6a0-2e1d7c9f5b33",
"object": "subscription",
"customer": "3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11",
"customer_code": "ACME-001",
"plan": "b6c1e0a4-2f7d-4a61-8f0c-5d3e9a7b2c44",
"status": "active",
"currency": "AUD",
"interval": { "unit": "month", "count": 1 },
"timezone": "Australia/Sydney",
"start_date": "2026-09-30",
"renewal_date": "2026-10-30",
"end_date": null,
"units": 10,
"minimum_units": 5,
"discount_percent": "10.00",
"cancel_at_period_end": false,
"end_after_cycles": null,
"label": "Acme – Team plan",
"invoice_reference": "PO-4471",
"renewal_value": "108.0000",
"usage_value": "0.0000",
"usage_value_annual": "0.0000",
"minimum_usage_value": "0.0000",
"meters": [],
"cpi_enabled": false,
"created": "2026-09-30T04:12:00.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The subscription id. |
customer | string | The customer’s id. |
customer_code | string or null | The customer’s code. |
plan | string | The plan’s id. |
status | string | pending, active, complete or ended. See below. |
currency | string | The subscription’s currency. |
interval | object | The billing period, copied from the plan. |
timezone | string | The subscription’s timezone. Its dates are in this timezone. |
start_date | date | When the subscription starts. |
renewal_date | date or null | When it next renews. |
end_date | date or null | When it ends, if it is set to end. |
units | integer | The current number of units. 0 on plans with no units: flat fee and no_base_fee. |
minimum_units | integer | The minimum set on this subscription. 0 means the plan’s minimum applies. A subscription can raise its plan’s minimum, never lower it. |
discount_percent | string | The discount, from "0.00" to "100.00". |
cancel_at_period_end | boolean | true when the subscription is set to end at its renewal date. |
end_after_cycles | integer or null | The number of billing cycles before it ends. null for an ongoing subscription. |
label | string or null | A label for you. |
invoice_reference | string or null | The reference printed on each invoice, such as a purchase order number. |
renewal_value | string | What the next period is certain to bill, after discount: the plan at its current units, the add-ons, and the minimum units of each meter the plan bills. Usage above the meter minimums is not included. See usage_value. |
usage_value | string | The average, over the last three closed usage periods, of what each billed for usage above the meter minimums, after discount. An estimate at the plan’s current meter prices. It is never part of renewal_value. 0.0000 until a usage period has closed, and for a plan that bills no meter. |
usage_value_annual | string | usage_value times the number of periods in a year. |
minimum_usage_value | string | The part of renewal_value that is the meter minimums, after discount. 0.0000 for a plan that bills no meter. |
meters | array | The meters the plan bills, with the minimum each is billed at. See below. |
cpi_enabled | boolean | true when the price rises with CPI at each renewal. On a plan that bills meters, the meter rates rise too. See CPI price increases. |
created | timestamp | When the subscription was created. |
Subscription meter
One entry in a subscription’smeters, for each meter its plan bills.
| Field | Type | Description |
|---|---|---|
meter | string | The Saasybill meter id. |
meter_code | string | The meter’s ID as shown in Saasybill. |
name | string | The meter’s name. |
minimum_units | integer | The minimum in force: this subscription’s own where it raised one, never below the plan’s. |
plan_minimum_units | integer | The plan’s minimum for the meter. |
Subscription status
| Status | Meaning |
|---|---|
| pending | Created with a future start date. The first invoice has been created. |
| active | Live and billing. Renews automatically. |
| complete | Reached its scheduled end. |
| ended | Stopped by request. Can be restored before its renewal date. |
A subscription set to end at period end stays
active with cancel_at_period_end: true until its end date.Invoice
An invoice raised in Xero. Endpoints. Invoices are read-only in the API.{
"id": "5f8a3c9d-7e21-4b04-9c6d-0a2b4e8f1d55",
"object": "invoice",
"customer": "3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11",
"subscription": "9d4e2b7a-13c5-4f8e-b6a0-2e1d7c9f5b33",
"number": "INV-0042",
"reference": "PO-4471",
"status": "awaiting_approval",
"currency": "AUD",
"invoice_date": "2026-09-29T14:00:00.000Z",
"due_date": "2026-10-29T13:00:00.000Z",
"subtotal": "558.0000",
"total": "613.8000",
"credit_note_amount": "0.0000",
"amount_paid": null,
"sent": false,
"xero": {
"sync_status": "synced",
"invoice_id": "f0e9d8c7-b6a5-4f43-9210-fedcba987654",
"sync_error": null
},
"lines": [
{
"id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c77",
"type": "subscription",
"revenue_type": "new_subscription",
"meter": null,
"usage_quantity": null,
"name": "Team",
"description": "Team – 10 seats",
"units": 10,
"amount": "120.0000",
"discount_amount": "12.0000",
"charge_amount": "108.0000",
"effective_date": "2026-09-29T14:00:00.000Z"
}
],
"created": "2026-09-30T04:12:01.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The Saasybill invoice id. |
customer | string | The customer’s id. |
subscription | string or null | The subscription that raised it. |
number | string or null | Xero’s invoice number, once the invoice is in Xero. |
reference | string or null | The invoice reference. |
status | string | The invoice’s status. See below. |
currency | string | The invoice’s currency. |
invoice_date | timestamp | The invoice date. |
due_date | timestamp or null | The due date. |
subtotal | string | The total excluding tax. |
total | string | The total including tax, as Xero returns it. |
credit_note_amount | string | Credit applied to the invoice. |
amount_paid | string or null | The amount paid so far. |
sent | boolean | Whether the invoice has been sent to the customer. |
xero | object | Whether the invoice is in Xero. See below. |
lines | array | The invoice’s line items. |
created | timestamp | When the invoice was created in Saasybill. |
Invoice status
| Status | Meaning |
|---|---|
draft | Created, not yet approved in Xero. |
awaiting_approval | Ready to be approved in Xero. |
approved | Approved and awaiting payment. |
paid | Fully paid. |
voided | Voided in Xero. |
deleted | Deleted in Xero. |
not_in_external | Not in Xero. Check xero.sync_status. |
Xero sync
| Field | Description |
|---|---|
xero.sync_status | synced the invoice is in Xero. pending it hasn’t reached Xero yet. failed Xero refused it. |
xero.invoice_id | The invoice’s id in Xero. null until it is there. |
xero.sync_error | Why Xero refused it. null unless sync_status is failed. |
Invoice line
| Field | Type | Description |
|---|---|---|
id | string | The line’s id. |
type | string or null | subscription, addon, charge or usage. A usage line bills metered usage: one line per meter per billing period. |
revenue_type | string or null | The revenue bucket the line falls in: new_subscription, expansion, renewal or usage. Metered usage is usage, never renewal. null where it wasn’t recorded. |
meter | string or null | The Saasybill meter id a usage line bills. null on every other line. |
usage_quantity | string or null | The quantity a usage line was priced for, after the meter’s minimum. units is this rounded up. null on every other line. |
name | string or null | The item’s name. |
description | string or null | The line’s description. |
units | integer or null | The units billed. null where the line has none, such as a charge. On a usage line, usage_quantity rounded up. |
amount | string | The amount before discount. |
discount_amount | string | The discount taken off. |
charge_amount | string | What is billed: amount less discount_amount. |
effective_date | timestamp or null | When the line takes effect. |
On a subscription created through the API, a charge takes the subscription’s discount only when you set
apply_discount to true on that charge.Usage event batch
WhatPOST /v1/usage_events returns: how each event in the request fared. See Usage-based billing.
{
"object": "usage_event_batch",
"created": 1,
"duplicates": 1,
"rejected": 1,
"results": [
{ "event_id": "evt_20261012_0001", "status": "created" },
{ "event_id": "evt_20261012_0002", "status": "duplicate" },
{
"event_id": "evt_20261012_0004",
"status": "rejected",
"error": {
"code": "meter_not_on_plan",
"message": "This subscription's plan does not bill for this meter."
}
}
]
}
| Field | Type | Description |
|---|---|---|
created | integer | Events stored by this request. |
duplicates | integer | Events already stored with the same details, counted once. |
rejected | integer | Events not stored. Resend them once fixed. |
results | array | One entry per event, in the order they were sent. |
Usage event result
| Field | Type | Description |
|---|---|---|
event_id | string | The event_id you sent. |
status | string | created, duplicate or rejected. |
error | object | Present only when status is rejected. code is stable, so branch on it and never on message. See rejection codes. |
Credit note
A credit note raised in Xero, such as when a volume plan’s units cross into a cheaper tier or a reduction in units is credited. Endpoints. Credit notes are read-only in the API.{
"id": "c2d7e1a4-8b36-4d19-a5f0-7e3b9c1d2f66",
"object": "credit_note",
"customer": "3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11",
"subscription": "9d4e2b7a-13c5-4f8e-b6a0-2e1d7c9f5b33",
"number": "CN-0007",
"reference": null,
"status": "approved",
"currency": "AUD",
"credit_note_date": "2026-09-29T14:00:00.000Z",
"subtotal": "60.0000",
"total": "66.0000",
"remaining_credit": "0.0000",
"allocation_status": "applied",
"sent": false,
"xero": {
"sync_status": "synced",
"credit_note_id": "a1b2c3d4-e5f6-4789-8abc-def012345678",
"sync_error": null
},
"lines": [
{
"id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e99",
"name": "Team",
"description": "Team – reduction from 12 to 10 seats",
"amount": "60.0000",
"effective_date": "2026-09-29T14:00:00.000Z"
}
],
"allocations": [
{
"id": "3d4e5f6a-7b8c-4d9e-8f10-2b3c4d5e6faa",
"invoice": "5f8a3c9d-7e21-4b04-9c6d-0a2b4e8f1d55",
"amount": "66.0000",
"date": "2026-10-29T14:00:00.000Z",
"status": "active",
"xero_sync_status": "synced"
}
],
"created": "2026-09-29T14:00:05.000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | The Saasybill credit note id. |
customer | string | The customer’s id. |
subscription | string or null | The subscription it relates to. |
number | string or null | Xero’s credit note number, once the credit note is in Xero. |
reference | string or null | The credit note reference. |
status | string | The credit note’s status. It uses the same values as the invoice status. |
currency | string | The credit note’s currency. |
credit_note_date | timestamp or null | The credit note date. |
subtotal | string | The total excluding tax. |
total | string | The total including tax, as Xero returns it. |
remaining_credit | string | What is left to apply to invoices. |
allocation_status | string or null | How much of the credit has been applied. See below. |
sent | boolean | Whether the credit note has been sent to the customer. |
xero | object | Whether the credit note is in Xero. It has the same shape as the invoice’s Xero sync, with credit_note_id in place of invoice_id. |
lines | array | The credit note’s line items. |
allocations | array | Where the credit has been applied. |
created | timestamp | When the credit note was created in Saasybill. |
Allocation status
| Status | Meaning |
|---|---|
applied | All of the credit has been applied to invoices. |
partial | Some of the credit has been applied. |
unapplied | None of the credit has been applied. |
error | Applying the credit hit an error. Check each allocation’s xero_sync_status. |
Credit note line
| Field | Type | Description |
|---|---|---|
id | string | The line’s id. |
name | string or null | The item’s name. |
description | string or null | The line’s description. |
amount | string | The amount credited, excluding tax. |
effective_date | timestamp or null | When the line takes effect. |
Allocation
| Field | Type | Description |
|---|---|---|
id | string | The allocation’s id. |
invoice | string | The id of the invoice the credit was applied to. |
amount | string | The amount applied. |
date | timestamp | When it was applied. |
status | string | active, or reverted if the allocation was undone. |
xero_sync_status | string | synced, pending or failed. |
A reverted allocation no longer counts towards
remaining_credit.