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

# Finalize a Draft Invoice — Invoice AI API Reference

> Finalize a draft invoice: assigns a permanent consecutive invoice number, moves status to open, and freezes business and customer snapshots.

Transitions a draft invoice to `open` status. Finalization does three things atomically:

1. **Assigns a permanent invoice number** from your configured number series (e.g. `INV/25-26/0042`). Invoice numbers are consecutive and can never be reused or skipped — an auditor seeing a gap reads it as a hidden transaction.
2. **Freezes the business and customer snapshots** — the name, address, and tax ID of both parties as they appear on the PDF are locked at this moment. Future edits to your business profile or the customer record do not alter the document.
3. **Sets `status` to `open`** and assigns `finalized_at`. The invoice is now visible to the customer via its public URL token.

Once finalized, an invoice cannot be edited or deleted. To cancel it, use [POST /api/v1/invoices/:id/void](/api-reference/invoices/void).

```http theme={null}
POST /api/v1/invoices/:id/finalize
```

**Base URL:** `https://invoice.horizonpay.co`

***

## Authentication

<ParamField header="Authorization" type="string" required>
  Bearer token in the form `Bearer inv_live_…`. The token must carry the `invoices:finalize` scope.
</ParamField>

<ParamField header="Idempotency-Key" type="string" required>
  A unique key (UUID or any string up to 255 characters) you generate per logical finalize operation. This endpoint **requires** the header — the request is rejected with `428` if it is absent.
</ParamField>

***

## Path Parameters

<ParamField path="id" type="string" required>
  The invoice's public ID (`in_…`) or its UUID. The invoice must currently be in `draft` status.
</ParamField>

***

## Request Body

This endpoint does not accept a request body.

***

## Request Example

```bash theme={null}
curl -X POST https://invoice.horizonpay.co/api/v1/invoices/in_01JABCDEFGHIJKLMNOPQRSTU01/finalize \
  -H "Authorization: Bearer inv_live_sk_1234abcd5678efgh" \
  -H "Idempotency-Key: 018e4c70-3f2a-7b8d-9c12-b2c3d4e5f601"
```

***

## Response Example

```json theme={null}
{
  "data": {
    "id": "in_01JABCDEFGHIJKLMNOPQRSTU01",
    "object": "invoice",
    "number": "INV/25-26/0042",
    "status": "open",
    "customer": "cus_01J9ZXMQR7P4KBN2VWTF3D6H8A",
    "currency": "USD",
    "collection_method": "send_invoice",
    "issue_date": "2025-06-01",
    "due_date": "2025-07-01",
    "description": "Website redesign — Phase 2",
    "footer": "Payment due within 30 days. Bank transfer preferred.",
    "subtotal": 700000,
    "discount": 50000,
    "taxable": 650000,
    "tax": 108000,
    "total": 758000,
    "amount_due": 758000,
    "amount_in_words": "Seven thousand five hundred and eighty dollars",
    "public_url_token": "tok_share_a1b2c3d4e5f6",
    "finalized_at": "2025-06-01T09:00:00.000Z",
    "paid_at": null,
    "voided_at": null,
    "void_reason": null,
    "created": "2025-06-01T08:00:00.000Z",
    "updated": "2025-06-01T09:00:00.000Z",
    "lines": {
      "data": [
        {
          "id": "ii_01JABCDEFGHIJKLMNOPQRSTU02",
          "object": "invoiceitem",
          "price": null,
          "product": null,
          "description": "UI/UX Design",
          "quantity": 40,
          "unit": "HRS",
          "unit_amount": 15000,
          "amount": 600000,
          "discount_percent": 0,
          "tax_rate": 18,
          "tax_amount": 108000
        },
        {
          "id": "ii_01JABCDEFGHIJKLMNOPQRSTU03",
          "object": "invoiceitem",
          "price": "price_01J9ZXMQR7P4KBN2VWTF3D6HXX",
          "product": "prod_01J9ZXMQR7P4KBN2VWTF3D6HYY",
          "description": "Domain Registration (1 year)",
          "quantity": 1,
          "unit": "NOS",
          "unit_amount": 10000,
          "amount": 10000,
          "discount_percent": 10,
          "tax_rate": 0,
          "tax_amount": 0
        }
      ]
    }
  }
}
```

<Warning>
  **`Idempotency-Key` is required on this endpoint.** Finalization spends an invoice number from a consecutive series. A network timeout after the server commits but before the response arrives leaves you uncertain whether the number was assigned. Without a key, a retry runs the handler again, potentially burning a second number and creating a gap in the series that auditors will flag. Always generate a fresh UUID per finalize operation and retain it for retries.

  If a replay occurs, the response header `Idempotent-Replayed: true` is present, and the body is identical to the original successful response — including the invoice number that was assigned.
</Warning>

<Note>
  The server guarantees that even if two requests are sent simultaneously without an `Idempotency-Key`, only one invoice number is ever assigned — the second concurrent request will find the invoice already finalized and return its existing number. The key is still required to protect against the case where the caller cannot detect this race.
</Note>

<Note>
  After finalization, `amount_due` changes from `0` to the invoice `total`. The `public_url_token` is also populated on finalization, enabling the shareable customer-facing link.
</Note>

***

## Response Fields

<ResponseField name="data.id" type="string">
  Unique public identifier for the invoice, prefixed `in_`.
</ResponseField>

<ResponseField name="data.object" type="string">
  Always `"invoice"`.
</ResponseField>

<ResponseField name="data.number" type="string">
  The newly assigned, permanent invoice number (e.g. `"INV/25-26/0042"`). Never `null` after a successful finalization.
</ResponseField>

<ResponseField name="data.status" type="string">
  `"open"` immediately after finalization, unless `due_date` is already in the past, in which case subsequent reads return `"overdue"`.
</ResponseField>

<ResponseField name="data.amount_due" type="integer">
  Now equals `total` — the full amount is owed.
</ResponseField>

<ResponseField name="data.public_url_token" type="string">
  Populated on finalization. Use this token to construct the shareable PDF link for your customer.
</ResponseField>

<ResponseField name="data.finalized_at" type="string">
  ISO 8601 datetime when the invoice was finalized.
</ResponseField>

<ResponseField name="data.lines" type="object">
  The frozen set of line items. These cannot be changed after finalization.
</ResponseField>

For all other fields, see the [Retrieve Invoice response fields](/api-reference/invoices/retrieve#response-fields).

***

## Error Codes

| HTTP Status | `code`                     | Meaning                                                                                                         |
| ----------- | -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `401`       | `unauthorized`             | Missing or invalid `Authorization` header.                                                                      |
| `403`       | `forbidden`                | Token does not carry the `invoices:finalize` scope.                                                             |
| `404`       | `not_found`                | No invoice with that ID exists, or it belongs to a different account.                                           |
| `409`       | `invalid_state`            | Invoice is not a draft (already finalized, paid, or voided). The response `detail` includes the invoice number. |
| `428`       | `idempotency_key_required` | `Idempotency-Key` header was not sent.                                                                          |
| `429`       | `rate_limited`             | Per-credential rate limit exceeded. Check the `Retry-After` response header.                                    |
