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

# Mark an Invoice as Paid — Invoice AI API Reference

> Record an out-of-band payment against an open or overdue invoice, setting its status to paid and triggering the invoice.paid webhook event.

Marks an open or overdue invoice as paid. This is an out-of-band payment recording endpoint: it does not initiate a charge or move funds. Use it to reflect a payment that was received externally — via bank transfer, cheque, cash, or a payment processor outside of Invoice AI.

On success the invoice's `status` moves to `"paid"`, `paid_at` is set to the current server time (or to the `paid_on` date you supply), and `amount_due` drops to `0`. The `invoice.paid` webhook event fires after the response is committed.

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

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

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

<ParamField header="Content-Type" type="string">
  `application/json`. Required when sending a body; may be omitted if the body is empty.
</ParamField>

***

## Path Parameters

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

***

## Body Parameters

The request body is optional. If omitted, the invoice is marked paid at the current server time with no reference.

<ParamField body="paid_on" type="string">
  ISO 8601 date the payment was received (`YYYY-MM-DD`). Defaults to today if omitted. Stored in `paid_at`.
</ParamField>

<ParamField body="reference" type="string">
  Free-text payment reference (e.g. a bank transaction ID, cheque number, or payment processor charge ID). Stored for your records only — not exposed on the invoice PDF.
</ParamField>

***

## Request Example

```bash theme={null}
curl -X POST https://invoice.horizonpay.co/api/v1/invoices/in_01JABCDEFGHIJKLMNOPQRSTU01/pay \
  -H "Authorization: Bearer inv_live_sk_1234abcd5678efgh" \
  -H "Idempotency-Key: 018e4c70-3f2a-7b8d-9c12-c3d4e5f60234" \
  -H "Content-Type: application/json" \
  -d '{
    "paid_on": "2025-06-28",
    "reference": "WIRE-2025-06-28-00113"
  }'
```

***

## Response Example

```json theme={null}
{
  "data": {
    "id": "in_01JABCDEFGHIJKLMNOPQRSTU01",
    "object": "invoice",
    "number": "INV/25-26/0042",
    "status": "paid",
    "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": 0,
    "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": "2025-06-28T00:00:00.000Z",
    "voided_at": null,
    "void_reason": null,
    "created": "2025-06-01T08:00:00.000Z",
    "updated": "2025-06-28T14:05:33.000Z"
  }
}
```

<Warning>
  **`Idempotency-Key` is required on this endpoint.** Recording a payment is not idempotent by default: a duplicate call without a key on an already-paid invoice returns `409 invalid_state`, but a race between two retries can trigger two `invoice.paid` webhook deliveries and any downstream logic that depends on them (e.g. releasing a digital product, updating accounting software). Always generate a fresh UUID per pay operation and retain it for retries.
</Warning>

<Note>
  The response does **not** include the `lines` field. If you need line item detail alongside the payment confirmation, follow this call with [GET /api/v1/invoices/:id](/api-reference/invoices/retrieve).
</Note>

<Note>
  A paid invoice **cannot be voided**. In accounting, reversing a paid invoice requires a credit note — a separate document with its own numbering. Credit notes are out of scope for v1 of this API.
</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 invoice number, unchanged from when it was finalized.
</ResponseField>

<ResponseField name="data.status" type="string">
  `"paid"` after a successful pay operation.
</ResponseField>

<ResponseField name="data.amount_due" type="integer">
  Always `0` after the invoice is paid.
</ResponseField>

<ResponseField name="data.paid_at" type="string">
  ISO 8601 datetime the payment was recorded. Reflects the `paid_on` date if you supplied one, otherwise the server timestamp at the time of the API call.
</ResponseField>

<ResponseField name="data.total" type="integer">
  The full invoice total in minor units. Unchanged by the pay operation.
</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 `payments:write` scope.                                           |
| `404`       | `not_found`                | No invoice with that ID exists, or it belongs to a different account.                      |
| `409`       | `invalid_state`            | Invoice is not in `open` or `overdue` status — it may be a draft, already paid, or voided. |
| `428`       | `idempotency_key_required` | `Idempotency-Key` header was not sent.                                                     |
| `429`       | `rate_limited`             | Per-credential rate limit exceeded. Check the `Retry-After` response header.               |
