> ## Documentation Index
> Fetch the complete documentation index at: https://docs.saasybill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bill a customer once

> Create an invoice for one-time charges, with no subscription

> [!NOTE]
>
> This page covers the API. To create the same invoice by hand, see [Invoices](https://docs.saasybill.com/billing-workspace/invoices.md). For the charges you bill, see [Charges](https://docs.saasybill.com/product-catalogue/charges.md).

`POST /v1/invoices` bills a customer once for one or more [charges](https://docs.saasybill.com/api-reference/objects.md#charge). There is no subscription and nothing recurs. It needs the `invoices:write` scope and an [`Idempotency-Key`](https://docs.saasybill.com/api-reference/idempotency). It returns the [invoice](https://docs.saasybill.com/api-reference/objects.md#invoice) with status `201`.

Saasybill applies the same rules as the New Invoice form and sends the invoice to Xero before it answers.

> [!NOTE]
>
> This is never a subscription invoice and it is not recurring revenue. Each line has the revenue type `one_time`. Saasybill records it in the one-time charge account chosen for the organisation in Xero.

***

## Request fields

| Field | Type | Description |
| - | - | - |
| `customer` or `customer_code` | string | The customer. Exactly one. The customer must already exist and be active. This endpoint never creates one. |
| `charges` | array | Required. From 1 to 50 charges to bill. Each charge can appear once, and all must share one currency. |
| `charges[].charge` | string | Required. The charge id. |
| `charges[].price` | string or number | The price, for a charge that allows its price to be overridden. Required there. The charge's own price is billed otherwise. |
| `reference` | string or null | A reference shown on the invoice. Up to 255 characters. |

A charge allows an override when `allows_price_override` is `true` on the [charge](https://docs.saasybill.com/api-reference/objects.md#charge). Sending `price` for any other charge is refused with `422`, so you never bill something other than what you asked for.

```bash theme={null}
curl https://api.saasybill.com/v1/invoices \
  -H "Authorization: Bearer $SAASYBILL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: bill_2026-10_acme" \
  -d '{
    "customer_code": "ACME-001",
    "charges": [
      { "charge": "5d0c7e2a-91b4-4f3e-8a6d-2c1b9e7f4a10" },
      { "charge": "8e4a6b1c-2d73-4a95-b0f8-7c3d5e9a1b22", "price": "450.00" }
    ],
    "reference": "PO-4471"
  }'
```

***

## What happens

Saasybill creates the invoice, then sends it to Xero. Whether Xero emails it follows the organisation's default invoice status and minimum invoice amount.

| Outcome | Response |
| - | - |
| Xero accepted the invoice. | `201`, with `xero.sync_status` of `synced`. |
| Xero refused the invoice. | `201`, with `xero.sync_status` of `failed` and the reason in `xero.sync_error`. The invoice exists in Saasybill. |

> [!WARNING]
>
> A `failed` sync is not an error response. Don't retry the request: the invoice already exists, and a new `Idempotency-Key` would bill the customer twice. Check `xero.sync_status` on the response instead.

The invoice has no `subscription`. Webhook events follow as for any invoice. See [Webhook events](https://docs.saasybill.com/api-reference/webhooks/events.md).

***

## Retries

`Idempotency-Key` is required, because a repeated request would otherwise be a second invoice and a second bill. A retry with the same key and body returns the first response. The invoice id is derived from the key, so a crash partway through can't produce a second invoice.

> [!TIP]
>
> Build the key from your own record of what you are billing, such as an order id. A random key on each attempt defeats the purpose.

***

## When a request is refused

The organisation's state decides some refusals. They answer `409`, and nothing in the request can fix them.

| Code | Meaning |
| - | - |
| `setup_mode` | The organisation is in Setup Mode, where invoices aren't created. |
| `xero_not_connected` | The organisation isn't connected to Xero. |
| `one_time_account_not_set` | No Xero account is chosen for one-time charges. An owner or admin chooses it in Saasybill. |

A problem with a field answers `422`, and `param` names it.

| `param` | Cause |
| - | - |
| `customer` | Neither or both of `customer` and `customer_code` were sent, or the customer is inactive or can't be billed in the charges' currency. |
| `charges` | A charge is no longer available, a charge is repeated, the charges use more than one currency, or a price is missing for a charge that needs one. |
| `charges.{n}.price` | A `price` was sent for charge `n`, which doesn't allow its price to be overridden. |

An unknown customer answers `404 customer_not_found`, and an unknown charge answers `404 charge_not_found`. See [Errors](https://docs.saasybill.com/api-reference/errors.md).

> [!NOTE]
>
> A key made before `invoices:write` existed doesn't have it. An owner or admin adds the scope to the key, or creates a new one. See [Authentication](https://docs.saasybill.com/api-reference/authentication.md#scopes).

***

## Refunds

The API can't credit a one-time invoice, and neither can the app. Raise a refund as a credit note in Xero.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.