# Issuance and auto-send

> Issuing an invoice yourself or leaving it to the daily run, a booked invoice that was not delivered, and credit notes.

Source: https://www.tabba.io/developers/api/issuance

A draft invoice is Tabba's own record. Issuing it makes the accounting system
book the invoice and send it to the customer. From then on the invoice is
frozen. There are two ways to issue, and both behave the same on either
accounting system: see [Accounting platforms](/developers/api/platforms).

## Issue it yourself [#issue-it-yourself]

Call the issue endpoint when your own workflow says the invoice is ready:

```bash
# A one-time invoice.
curl -X POST https://www.tabba.io/api/v1/invoices/ext:crm:acme-migration/issue \
  -H "Authorization: Bearer $TABBA_API_KEY"

# An occurrence of a recurring series, named by the first day of its period.
curl -X POST https://www.tabba.io/api/v1/recurring-series/ext:crm:acme-retainer/occurrences/2026-07-01/issue \
  -H "Authorization: Bearer $TABBA_API_KEY"
```

The two endpoints are `POST /invoices/{ref}/issue` and
`POST /recurring-series/{ref}/occurrences/{period}/issue`. Neither takes a
body.

The accounting system books and sends the invoice inside the request, and the
answer is the issued invoice: `status` is `issued`, and `issued_at`,
`platform_ref` and `platform_number` are set. It is the explicit "bill this
one now": an invoice with `auto_send` off is issued all the same, and one
with no `issue_date`, or a date ahead, is dated today.

**Sending it again is safe.** An invoice that is issued already is answered
as it stands, `200`, and nothing is booked twice. While another attempt holds
the invoice, the answer is `409` with `ISSUE_IN_PROGRESS` and a `Retry-After`
header: send the same request again after the interval.

An occurrence that is not an invoice yet is made one, and that invoice is
issued. `{period}` is the first day of the occurrence's billing period, as
`GET /recurring-series/{ref}/occurrences` states it.

Both endpoints are counted against the `platform_write` rate limit as well as
the default one: see [Errors](/developers/api/errors).

## Leave it to the daily run [#leave-it-to-the-daily-run]

Every invoice carries an `auto_send` boolean. While it is on, the daily run
issues the invoice without your involvement:

* An invoice with an `issue_date` is issued on that date.
* An invoice with no `issue_date` is issued once all the work on it is
  complete.

`auto_send` is **on for a new invoice** unless the create says otherwise.
Subscribe to the `invoice.issued` event to learn of the booking:

```js
// Your webhook receiver, after the signature is verified.
app.post('/webhooks/tabba', async (req, res) => {
  const event = req.body
  if (event.type === 'invoice.issued') {
    await syncInvoiceToYourSystem(event.data)
  }
  res.status(200).end()
})
```

To turn it off on a draft, replace the draft with `PUT /invoices/{ref}` and
`auto_send: false`. The body of a `PUT` is the whole draft: a line that
states its `id` stays the line it was, and a line the body leaves out is
removed. Left out, `auto_send` stays as it is.

## Booked, and not delivered [#booked-and-not-delivered]

Issuing is two things: the accounting system **books** the invoice, and it
**sends** it to the customer. Booking is what puts the invoice in the books.
When the booking succeeds and the delivery is not known to have succeeded,
the answer is still `200`, with a `send_error`:

```json
{
  "object": "invoice",
  "id": "…",
  "status": "issued",
  "issued_at": "2026-07-01T08:00:00.000Z",
  "platform_ref": "…",
  "platform_number": 1042,
  "send_error": {
    "reason": "…",
    "platform_reason": "…"
  }
}
```

`reason` is a code of Tabba's own, and `platform_reason` is the accounting
system's own words, where it gave any. On an invoice whose delivery
succeeded, `send_error` is `null`.

**Do not issue it again.** The invoice is in the books, and a second call
answers the same invoice. The `invoice.send_failed` event is sent beside
`invoice.issued` for such an invoice. Following up on the delivery is done in
the workspace: the API has no endpoint that sends a booked invoice again.

## When an invoice is not issued [#when-an-invoice-is-not-issued]

Anything short of issued is an error with its own code, never a `200`:

| Code                                              | Status | What happened                                                     |
| ------------------------------------------------- | ------ | ----------------------------------------------------------------- |
| `HOURLY_INCOMPLETE`                               | `409`  | A person on an hourly line is below full completion               |
| `NOTHING_TO_BILL`                                 | `409`  | The invoice has no hours to bill                                  |
| `CUSTOMER_GONE`, `PRODUCT_GONE`                   | `409`  | The accounting system no longer holds what the invoice names      |
| `ISSUE_IN_PROGRESS`                               | `409`  | Another attempt holds the invoice. Retry after `Retry-After`      |
| `MISSING_LINES`                                   | `422`  | The invoice has no lines                                          |
| `INVALID_SEND_METHOD`, `MISSING_EINVOICE_DETAILS` | `422`  | The customer cannot be sent to. Checked before anything is booked |
| `ISSUE_REFUSED`                                   | `422`  | The accounting system refused to book. `refusal` says why         |
| `PLATFORM_API_ERROR`                              | `502`  | The accounting system could not be reached, or did not answer     |

Issuing an occurrence adds three: `OCCURRENCE_SKIPPED` (`409`) for an
occurrence an admin skipped, `OCCURRENCE_HAS_NO_LINES` (`422`) where the
series states no line for the occurrence, and `OCCURRENCE_OUT_OF_RANGE`
(`404`) for a date that starts no period of the series.

## Changing an issued invoice [#changing-an-issued-invoice]

An issued invoice is frozen. `PUT /invoices/{ref}` and
`DELETE /invoices/{ref}` on one are refused with `INVOICE_ALREADY_ISSUED`
(`409`). To take it back, in full or in part, raise a **credit note**:

```bash
curl -X POST https://www.tabba.io/api/v1/credit-notes \
  -H "Authorization: Bearer $TABBA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "crm:acme-migration:credit-1",
    "invoice": "ext:crm:acme-migration",
    "kind": "partial",
    "credit_date": "2026-07-15",
    "lines": [
      { "invoice_line_id": "5f8e2c1a-3b4d-4e6f-8a9b-0c1d2e3f4a5b", "amount": 2500 }
    ]
  }'
```

* `kind: "full"` takes the whole invoice back and reads no `lines`. It is
  refused with `INVOICE_ALREADY_CREDITED` once anything was credited.
* `kind: "partial"` names lines of the invoice, each by no more than is left
  on it. Amounts are before VAT. Too much is a `VALIDATION_FAILED` with
  `AMOUNT_EXCEEDS_BILLED` under the line.

The accounting system books and sends the credit note inside the request, and
the answer is the booked credit note, `201`. As with an invoice, a credit
note that was booked and not delivered is a success that carries a
`send_error`.

**The `external_id` is the credit note.** Sending the request again under the
same `external_id` books nothing twice: a booked credit note is answered as
it stands, `200`, and an attempt that did not book is taken up again. An
invoice that is not issued is refused with `INVOICE_NOT_ISSUED`. While
another credit of the invoice is running the answer is `CREDIT_IN_PROGRESS`
with `Retry-After`, and an attempt the accounting system gave no definite
answer to holds every other credit of the invoice back
(`CREDIT_ATTEMPT_UNRESOLVED`) until the request that made it is sent again.

`GET /invoices/{ref}/credit-notes` lists the credit notes booked against an
invoice. There is no endpoint that reads a single credit note.

## Occurrences of a recurring series [#occurrences-of-a-recurring-series]

An occurrence of a recurring series is read as an invoice once it is one:
`GET /invoices` lists it with its `series_id`, `period_from` and `period_to`.
It is written through its series. `PUT /invoices/{ref}` and
`DELETE /invoices/{ref}` on an occurrence are refused with
`INVOICE_IS_OCCURRENCE` (`409`): change its lines through
`PATCH /recurring-series/{ref}/lines/{lineRef}` instead.

A series created through the API is active at once, and the occurrences whose
period has started are invoices from that moment.

## Issuing many invoices [#issuing-many-invoices]

There is no batch endpoint. To issue several invoices, call the issue
endpoint for each: every call is safe to repeat, so a run that stopped
halfway can be started again from the top. The `platform_write` limit bounds
how fast one key can issue.

For invoices that should go out on a date without a call from you, leave
`auto_send` on and let the daily run issue them.
