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

# Update a Draft Invoice — Invoice AI API Reference

> Partially update a draft invoice's header fields and line items. Only draft invoices can be modified; sending PATCH to a finalized invoice returns 409.

Updates a draft invoice in place. All body fields are optional — only the keys you include are changed. Once an invoice has been finalized (status `open`, `paid`, `overdue`, or `void`), it is immutable: the customer may already have the PDF, and a silent edit would create a discrepancy. Attempting to PATCH a non-draft invoice returns a `409 invalid_state` error.

When you supply an `items` array, it **replaces** all existing line items on the invoice. Omit `items` entirely to leave the existing items untouched.

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

**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:write` scope.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`.
</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>

***

## Body Parameters

All fields are optional. Omitting a field leaves its current value unchanged.

<ParamField body="customer" type="string">
  Reassign the invoice to a different customer by their `cus_…` public ID or UUID.
</ParamField>

<ParamField body="currency" type="string">
  Three-letter ISO 4217 currency code. Changing the currency clears any catalog-based lines whose price was denominated in the old currency.
</ParamField>

<ParamField body="collection_method" type="string">
  `"send_invoice"` or `"charge_automatically"`.
</ParamField>

<ParamField body="issue_date" type="string">
  ISO 8601 date (`YYYY-MM-DD`).
</ParamField>

<ParamField body="due_date" type="string">
  ISO 8601 date (`YYYY-MM-DD`), or `""` to clear the due date.
</ParamField>

<ParamField body="days_until_due" type="integer">
  Number of days from `issue_date` until payment is due (0–365). Ignored if `due_date` is also supplied.
</ParamField>

<ParamField body="description" type="string">
  Notes shown on the invoice body. Pass `null` to clear.
</ParamField>

<ParamField body="footer" type="string">
  Footer text shown at the bottom of the invoice, typically used for payment terms. Pass `null` to clear.
</ParamField>

<ParamField body="items" type="array">
  Replaces all existing line items. Omit this field to keep the current items unchanged. The array must contain at least one item if supplied.

  <Expandable title="Line item fields">
    <ParamField body="items[].price" type="string">
      The `price_…` public ID of a catalog price. When set, `description`, `unit_amount`, and `tax_rate` may be omitted and are inherited from the price.
    </ParamField>

    <ParamField body="items[].description" type="string">
      Description of the line. Required for ad-hoc lines (those without a `price`).
    </ParamField>

    <ParamField body="items[].quantity" type="number">
      Number of units. Must be greater than zero. Defaults to `1`.
    </ParamField>

    <ParamField body="items[].unit" type="string">
      Unit of measure. One of: `NOS`, `PCS`, `KGS`, `GMS`, `LTR`, `MTR`, `SQF`, `SQM`, `HRS`, `DAY`, `MON`, `BOX`, `SET`, `OTH`.
    </ParamField>

    <ParamField body="items[].unit_amount" type="integer">
      Price per unit in integer minor units. Required for ad-hoc lines; inherited from the catalog price otherwise.
    </ParamField>

    <ParamField body="items[].discount_percent" type="number">
      Percentage discount on this line (0–100). Defaults to `0`.
    </ParamField>

    <ParamField body="items[].tax_rate" type="number">
      Tax rate as a percentage (0–100). Defaults to `0` for ad-hoc lines.
    </ParamField>
  </Expandable>
</ParamField>

***

## Request Example

```bash theme={null}
curl -X PATCH https://invoice.horizonpay.co/api/v1/invoices/in_01JABCDEFGHIJKLMNOPQRSTU01 \
  -H "Authorization: Bearer inv_live_sk_1234abcd5678efgh" \
  -H "Content-Type: application/json" \
  -d '{
    "due_date": "2025-07-15",
    "description": "Website redesign — Phase 2 (revised scope)",
    "items": [
      {
        "description": "UI/UX Design",
        "quantity": 48,
        "unit": "HRS",
        "unit_amount": 15000,
        "discount_percent": 0,
        "tax_rate": 18
      },
      {
        "description": "Frontend Development",
        "quantity": 24,
        "unit": "HRS",
        "unit_amount": 18000,
        "discount_percent": 0,
        "tax_rate": 18
      }
    ]
  }'
```

***

## Response Example

```json theme={null}
{
  "data": {
    "id": "in_01JABCDEFGHIJKLMNOPQRSTU01",
    "object": "invoice",
    "number": null,
    "status": "draft",
    "customer": "cus_01J9ZXMQR7P4KBN2VWTF3D6H8A",
    "currency": "USD",
    "collection_method": "send_invoice",
    "issue_date": "2025-06-01",
    "due_date": "2025-07-15",
    "description": "Website redesign — Phase 2 (revised scope)",
    "footer": "Payment due within 30 days. Bank transfer preferred.",
    "subtotal": 1152000,
    "discount": 0,
    "taxable": 1152000,
    "tax": 207360,
    "total": 1359360,
    "amount_due": 0,
    "amount_in_words": null,
    "public_url_token": null,
    "finalized_at": null,
    "paid_at": null,
    "voided_at": null,
    "void_reason": null,
    "created": "2025-06-01T08:00:00.000Z",
    "updated": "2025-06-01T11:22:00.000Z",
    "lines": {
      "data": [
        {
          "id": "ii_01JABCDEFGHIJKLMNOPQRSTU04",
          "object": "invoiceitem",
          "price": null,
          "product": null,
          "description": "UI/UX Design",
          "quantity": 48,
          "unit": "HRS",
          "unit_amount": 15000,
          "amount": 720000,
          "discount_percent": 0,
          "tax_rate": 18,
          "tax_amount": 129600
        },
        {
          "id": "ii_01JABCDEFGHIJKLMNOPQRSTU05",
          "object": "invoiceitem",
          "price": null,
          "product": null,
          "description": "Frontend Development",
          "quantity": 24,
          "unit": "HRS",
          "unit_amount": 18000,
          "amount": 432000,
          "discount_percent": 0,
          "tax_rate": 18,
          "tax_amount": 77760
        }
      ]
    }
  }
}
```

<Warning>
  **PATCH is only allowed on draft invoices.** Attempting to update an invoice with status `open`, `paid`, `overdue`, or `void` returns `409 invalid_state`. Use [POST /api/v1/invoices/:id/void](/api-reference/invoices/void) to cancel an open invoice with an audit trail. There is no endpoint to modify a finalized invoice — issue a new one instead.
</Warning>

<Note>
  Supplying `items` in the body **fully replaces** all line items — there is no way to add, remove, or update individual lines via PATCH. To keep existing lines, read them first with [GET /api/v1/invoices/:id](/api-reference/invoices/retrieve), modify the array in your client, and send the complete updated array.
</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="null">
  Still `null` — the invoice remains a draft until finalized.
</ResponseField>

<ResponseField name="data.status" type="string">
  Always `"draft"` after a successful PATCH, confirming the invoice was editable.
</ResponseField>

<ResponseField name="data.updated" type="string">
  ISO 8601 datetime of this update.
</ResponseField>

<ResponseField name="data.lines" type="object">
  The complete, updated set of line items after the PATCH is applied.
</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:write` 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. Finalized, paid, overdue, and voided invoices are immutable. |
| `422`       | `validation`    | Request body failed validation. The `errors` array lists each failing field path.    |
| `429`       | `rate_limited`  | Per-credential rate limit exceeded. Check the `Retry-After` response header.         |
