> ## 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.

# Create an invoice for one-time charges

> Bills a customer once for one or more charges, with no subscription, under the same rules as the New Invoice form. It is never a subscription invoice, and it is not recurring revenue. It is recorded in the organisation's one-time charge account in Xero. **`Idempotency-Key` is required**: a repeat of this request would otherwise be a second invoice and a second bill. A retry with the same key and body returns the first response. If Xero refuses the invoice it still exists here and the response is 201 with `xero.sync_status` of `failed`. Whether Xero emails it follows the organisation's default invoice status and minimum invoice amount. **409 `one_time_account_not_set`** until an owner or admin chooses that account.

> [!NOTE]
>
> Requires the `invoices:write` scope and an `Idempotency-Key`. See [Authentication](https://docs.saasybill.com/api-reference/authentication.md#scopes) and [Bill a customer once](https://docs.saasybill.com/api-reference/guides/one-time-charges.md).


## OpenAPI

````yaml /api-reference/openapi.json post /v1/invoices
openapi: 3.1.0
info:
  title: Saasybill API
  version: 1.0.0
  description: >-
    Create and manage subscriptions in Saasybill from your own platform.


    **Authentication.** `Authorization: Bearer saasybill_live_…`. Create keys
    under Developer Settings.


    **Idempotency.** Send an `Idempotency-Key` on writes. It is required to
    create a subscription or an invoice.


    **Webhooks.** Saasybill calls your URL when something changes. Events:
    `customer.created`, `customer.updated`, `customer.deleted`,
    `subscription.created`, `subscription.updated`, `subscription.ended`,
    `subscription.restored`, `subscription.deleted`, `invoice.created`,
    `invoice.updated`, `invoice.paid`, `invoice.voided`, `invoice.deleted`,
    `credit_note.created`, `credit_note.updated`, `credit_note.voided`,
    `credit_note.deleted`. Each request is signed: `Saasybill-Signature: t=<unix
    seconds>,v1=<hex>`, where `v1` is `HMAC-SHA256(secret, "<t>.<raw body>")`.
    Reject a `t` more than five minutes old. Deliveries are at-least-once,
    retried for about 20 hours, and unordered: de-duplicate on the event `id`.
    Changes are detected about once a minute, so an event can arrive up to a
    minute late, and changes inside one minute are combined.


    **Testing.** Create a sandbox under Developer Settings → Configuration,
    connect it to a Xero Demo Company, and use a test key (`saasybill_test_…`)
    created inside it. A test key works only in its sandbox, which sends no
    emails and is never billed. Your plans, customers and subscriptions can be
    copied into the sandbox when it is created or reset, and its test clock can
    be advanced to see renewals without waiting. `GET /v1/organisation` returns
    `livemode: false` for a sandbox.
servers:
  - url: https://api.saasybill.com
    description: Production
security: []
tags:
  - name: Organisation
    description: The organisation an API key belongs to.
  - name: Customers
    description: >-
      Customers are synced from Xero. Create one only where the organisation
      allows it.
  - name: Plans
    description: Read the pricing plans you can sell.
  - name: Charges
    description: Read the one-off fees that can be added to a new subscription.
  - name: Subscriptions
    description: Create subscriptions, change units, and end or restore them.
  - name: Invoices
    description: >-
      Read the invoices Saasybill raises in Xero, and bill a customer once for
      charges.
  - name: Credit notes
    description: >-
      Read the credit notes Saasybill raises in Xero, and where their credit was
      applied.
  - name: Usage
    description: Report the usage that is billed in arrears against a subscription.
paths:
  /v1/invoices:
    post:
      tags:
        - Invoices
      summary: Create an invoice for one-time charges
      description: >-
        Bills a customer once for one or more charges, with no subscription,
        under the same rules as the New Invoice form. It is never a subscription
        invoice, and it is not recurring revenue. It is recorded in the
        organisation's one-time charge account in Xero. **`Idempotency-Key` is
        required**: a repeat of this request would otherwise be a second invoice
        and a second bill. A retry with the same key and body returns the first
        response. If Xero refuses the invoice it still exists here and the
        response is 201 with `xero.sync_status` of `failed`. Whether Xero emails
        it follows the organisation's default invoice status and minimum invoice
        amount. **409 `one_time_account_not_set`** until an owner or admin
        chooses that account.
      operationId: post_v1_invoices
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            1 to 255 printable characters. A retry with the same key and body
            returns the first response.
          schema:
            type: string
            minLength: 1
            maxLength: 255
            example: bill_2026-10_acme
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer:
                  description: The Saasybill customer id. Give this or `customer_code`.
                  type: string
                  minLength: 1
                  maxLength: 100
                customer_code:
                  type: string
                  description: >-
                    The organisation's own reference for the customer. Unique
                    per organisation.
                charges:
                  minItems: 1
                  maxItems: 50
                  type: array
                  items:
                    type: object
                    properties:
                      charge:
                        type: string
                        minLength: 1
                        maxLength: 100
                        description: The charge id.
                      price:
                        description: >-
                          Only where the charge allows its price to be
                          overridden, and then required. The charge's own price
                          is billed otherwise.
                        anyOf:
                          - type: string
                            minLength: 1
                            maxLength: 20
                          - type: number
                    required:
                      - charge
                    additionalProperties: false
                  description: >-
                    What to bill, once. All in one currency, each charge at most
                    once.
                reference:
                  anyOf:
                    - type: string
                      maxLength: 255
                    - type: 'null'
              required:
                - charges
              additionalProperties: false
            examples:
              Bill a customer by code:
                summary: Bill a customer by code
                value:
                  customer_code: ACME-001
                  charges:
                    - charge: 5d0c7e2a-91b4-4f3e-8a6d-2c1b9e7f4a10
                  reference: PO-4471
              Override a price:
                summary: Override a price
                value:
                  customer: 3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11
                  charges:
                    - charge: 8e4a6b1c-2d73-4a95-b0f8-7c3d5e9a1b22
                      price: '450.00'
      responses:
        '201':
          description: >-
            The invoice was created. If Xero refused it, `xero.sync_status` is
            `failed` and the invoice still exists.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  object:
                    type: string
                    const: invoice
                  customer:
                    type: string
                  subscription:
                    type:
                      - string
                      - 'null'
                  number:
                    description: Xero's invoice number, once it is in Xero.
                    type:
                      - string
                      - 'null'
                  reference:
                    type:
                      - string
                      - 'null'
                  status:
                    type: string
                    enum:
                      - draft
                      - awaiting_approval
                      - approved
                      - paid
                      - voided
                      - deleted
                      - not_in_external
                  currency:
                    type: string
                  invoice_date:
                    type: string
                    format: date-time
                    pattern: >-
                      ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
                    description: ISO 8601, UTC.
                  due_date:
                    anyOf:
                      - type: string
                        format: date-time
                        pattern: >-
                          ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
                        description: ISO 8601, UTC.
                      - type: 'null'
                  subtotal:
                    type: string
                    description: Excluding tax.
                  total:
                    type: string
                    description: Including tax, as Xero returns it.
                  credit_note_amount:
                    type: string
                    description: A decimal string, e.g. "19.9900".
                  amount_paid:
                    anyOf:
                      - type: string
                        description: A decimal string, e.g. "19.9900".
                      - type: 'null'
                  sent:
                    type: boolean
                  xero:
                    type: object
                    properties:
                      sync_status:
                        type: string
                        enum:
                          - synced
                          - failed
                          - pending
                        description: >-
                          `failed` means the invoice exists here and Xero
                          refused it; the subscription that raised it was still
                          created.
                      invoice_id:
                        type:
                          - string
                          - 'null'
                      sync_error:
                        type:
                          - string
                          - 'null'
                    required:
                      - sync_status
                      - invoice_id
                      - sync_error
                    additionalProperties: false
                  lines:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        type:
                          anyOf:
                            - type: string
                              enum:
                                - subscription
                                - addon
                                - charge
                                - usage
                            - type: 'null'
                          description: >-
                            What the line bills for. `usage` is metered usage,
                            one line per meter per billing period.
                        revenue_type:
                          anyOf:
                            - type: string
                              enum:
                                - new_subscription
                                - expansion
                                - renewal
                                - usage
                                - one_time
                            - type: 'null'
                          description: >-
                            The revenue bucket the line falls in. Metered usage
                            is `usage`, never `renewal`. A charge on an invoice
                            with no subscription is `one_time`. Null where it
                            was not recorded.
                        meter:
                          description: >-
                            The Saasybill meter id a `usage` line bills. Null on
                            every other line.
                          type:
                            - string
                            - 'null'
                        usage_quantity:
                          description: >-
                            The quantity a `usage` line was priced for, as a
                            decimal string, after the meter's minimum. `units`
                            is this rounded up. Null on every other line.
                          type:
                            - string
                            - 'null'
                        name:
                          type:
                            - string
                            - 'null'
                        description:
                          type:
                            - string
                            - 'null'
                        units:
                          anyOf:
                            - type: integer
                              minimum: -9007199254740991
                              maximum: 9007199254740991
                            - type: 'null'
                        amount:
                          type: string
                          description: A decimal string, e.g. "19.9900".
                        discount_amount:
                          type: string
                          description: A decimal string, e.g. "19.9900".
                        charge_amount:
                          type: string
                          description: 'What is billed: `amount` less `discount_amount`.'
                        effective_date:
                          anyOf:
                            - type: string
                              format: date-time
                              pattern: >-
                                ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
                              description: ISO 8601, UTC.
                            - type: 'null'
                      required:
                        - id
                        - type
                        - revenue_type
                        - meter
                        - usage_quantity
                        - name
                        - description
                        - units
                        - amount
                        - discount_amount
                        - charge_amount
                        - effective_date
                      additionalProperties: false
                  created:
                    type: string
                    format: date-time
                    pattern: >-
                      ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
                    description: ISO 8601, UTC.
                required:
                  - id
                  - object
                  - customer
                  - subscription
                  - number
                  - reference
                  - status
                  - currency
                  - invoice_date
                  - due_date
                  - subtotal
                  - total
                  - credit_note_amount
                  - amount_paid
                  - sent
                  - xero
                  - created
                additionalProperties: false
              example:
                id: c2d4e6f8-1a3b-4c5d-8e7f-9a0b1c2d3e44
                object: invoice
                customer: 3b9f1c1e-6a52-4c0e-9d7e-1f6f2f0b7a11
                subscription: null
                number: INV-0057
                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: '450.0000'
                total: '495.0000'
                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: 3d4e5f6a-7b8c-4d9e-a0f1-2b3c4d5e6f99
                    type: charge
                    revenue_type: one_time
                    meter: null
                    usage_quantity: null
                    name: Onboarding
                    description: One-time charge
                    units: null
                    amount: '450.0000'
                    discount_amount: '0.0000'
                    charge_amount: '450.0000'
                    effective_date: '2026-09-29T14:00:00.000Z'
                created: '2026-09-30T04:12:01.000Z'
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
        '401':
          description: The key is missing, malformed, revoked or expired.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: authentication_error
                  code: invalid_api_key
                  message: 'Provide a valid API key as `Authorization: Bearer <key>`.'
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '403':
          description: >-
            The key lacks the scope, is a test key, or the organisation has not
            allowed this.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: permission_error
                  code: insufficient_scope
                  message: This API key does not have the `invoices:write` scope.
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '404':
          description: No such object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: not_found
                  code: charge_not_found
                  message: There is no charge with that id.
                  param: charges.0.charge
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '409':
          description: >-
            The organisation is not ready to invoice (`setup_mode`,
            `xero_not_connected`, `one_time_account_not_set`), or the request
            conflicts with an earlier one using the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: conflict
                  code: one_time_account_not_set
                  message: >-
                    Choose the Xero account that one-time charges are recorded
                    in first.
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '422':
          description: A field is missing or invalid. `error.param` names it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: invalid_request_error
                  code: invalid_parameter
                  message: >-
                    `charges.0.price`: This charge's price cannot be overridden.
                    Leave `price` out to bill its own.
                  param: charges.0.price
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '429':
          description: 'Rate limited: 120 requests a minute per key. See `Retry-After`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: rate_limit_error
                  code: rate_limited
                  message: Too many requests. Try again in 12 seconds.
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
        '500':
          description: >-
            Something went wrong on our side. Retry; quote `error.request_id` if
            it persists.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: api_error
                  code: internal_error
                  message: >-
                    Something went wrong on our side. Retry the request; if it
                    keeps failing, quote the request id.
                  request_id: req_4f1a9c2e7b8d3a605e1c9f02
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
      security:
        - bearerAuth:
            - invoices:write
components:
  headers:
    Request-Id:
      description: A unique id for the request. Quote it when you contact support.
      schema:
        type: string
        example: req_4f1a9c2e7b8d3a605e1c9f02
    RateLimit-Limit:
      description: Requests allowed per minute for this key.
      schema:
        type: integer
        example: 120
    RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
        example: 117
    RateLimit-Reset:
      description: Seconds until the oldest counted request stops counting.
      schema:
        type: integer
        example: 41
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                - authentication_error
                - permission_error
                - invalid_request_error
                - not_found
                - conflict
                - rate_limit_error
                - api_error
            code:
              type: string
              description: Stable. Branch on this, never on `message`.
            message:
              type: string
            param:
              type: string
            request_id:
              type: string
          required:
            - type
            - code
            - message
            - request_id
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        Every failed request answers with this shape. Branch on `error.code`,
        never on `error.message`.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        An API key. Its scopes decide what it may call: `customers:read` (List
        and look up customers.) `customers:write` (Create customers, and Xero
        contacts where the organisation allows it.) `catalogue:read` (List plans
        and charges.) `subscriptions:read` (List and look up subscriptions.)
        `subscriptions:write` (Create subscriptions, change units, end and
        restore them.) `invoices:read` (List and look up invoices.)
        `invoices:write` (Bill a customer once for charges, without a
        subscription.) `credit_notes:read` (List and look up credit notes, and
        where their credit was applied.) `usage:write` (Send usage events, which
        are billed to the subscription they name.)

````

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