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

# Invoices

> Create, manage and collect payment on invoices, and produce a hosted checkout link.

Invoices are the core billing document in Hyparrow. You create an invoice with
line items, dates, tax and discount, then collect payment either by sharing a
**hosted checkout URL** with your customer or by recording payment yourself.

All endpoints on this page are **merchant endpoints** under the base URL
`https://api.hyparrow.cloud/api/v1` and require your API key credentials on
every request:

```
X-API-Key: <your-api-key>
X-API-Secret: <your-api-secret>
```

<Note>
  **Amounts are in kobo** (the minor unit). `₦1,000.00` is `100000`. Line-item
  `unitPrice` and the persisted totals all follow this convention.
</Note>

## Lifecycle

An invoice moves through a small set of statuses:

| Status     | Meaning                                                                           |
| ---------- | --------------------------------------------------------------------------------- |
| `draft`    | Created but not yet shared. Fully editable. Not yet reachable on a checkout link. |
| `pending`  | Published / sent. Reachable at the checkout URL and awaiting payment.             |
| `paid`     | Settled. Becomes read-only; a receipt is issued.                                  |
| `overdue`  | Past its `dueDate` and still unpaid.                                              |
| `canceled` | Voided. Cannot be paid; the checkout page returns "not found".                    |

A new invoice starts as `draft`. Calling
[`POST /invoices/{invoiceId}/send-email`](#email-an-invoice) auto-publishes a
draft to `pending` so the customer can reach it. Once a payment settles (via the
hosted checkout or [`POST /invoices/{invoiceId}/pay`](#record-a-payment)), the
invoice becomes `paid` and can no longer be edited.

## End-to-end flow

<Steps>
  <Step title="Create the invoice">
    `POST /invoices/` with a title, currency, customer details and at least one
    line item. Hyparrow assigns an `invoiceNumber` and computes the totals.
  </Step>

  <Step title="Share the checkout URL">
    The checkout URL is `CHECKOUT_BASE_URL/pay/{invoiceId}`. Send it to your
    customer directly, or call
    [`send-email`](#email-an-invoice) to email the invoice PDF with the link
    embedded. (Optionally generate a virtual account up front with
    [`generate-va`](#generate-a-virtual-account).)
  </Step>

  <Step title="Customer pays">
    On the hosted checkout page the customer picks a payment method (bank
    transfer / virtual account, USSD, card, OPay or crypto). See the
    [Checkout guide](/checkout) for the public API the page uses.
  </Step>

  <Step title="Status becomes paid">
    Poll the invoice (or the public [checkout status](/checkout#poll-status)
    endpoint) until `status` is `paid`. A receipt is generated automatically.
  </Step>

  <Step title="Callback fires">
    If you set `checkoutCallbackUrl` on the invoice, Hyparrow `POST`s a
    `checkout.payment.completed` event to it when the payment settles. See
    [the callback](/checkout#checkout-callback-webhook).
  </Step>
</Steps>

## Create an invoice

`POST /invoices/`

Creates an invoice with line items, optional invoice-level tax, discount,
shipping and customer details.

### Body

| Field                                                   | Type         | Notes                                                                                                                                                                        |
| ------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`                                                 | string       | **Required.**                                                                                                                                                                |
| `currency`                                              | string       | ISO-4217, 3 letters. Defaults to `NGN`.                                                                                                                                      |
| `companyName`                                           | string       | Sender name. Defaults to your account name.                                                                                                                                  |
| `email`                                                 | string       | Sender email. Defaults to your account email.                                                                                                                                |
| `customerName`                                          | string       | Defaults to `"Customer"`.                                                                                                                                                    |
| `customerEmail`                                         | string       | Defaults to your account email if omitted.                                                                                                                                   |
| `billingAddress`, `city`, `state`, `country`, `zipCode` | string       | Optional customer address.                                                                                                                                                   |
| `issueDate`, `dueDate`                                  | string       | `YYYY-MM-DD` or RFC3339. `dueDate` cannot be before `issueDate`.                                                                                                             |
| `taxType`                                               | enum         | `none`, `percentage` or `fixed`. Invoice-level tax **overrides** line-item tax.                                                                                              |
| `taxRate`                                               | number       | Used with `taxType`.                                                                                                                                                         |
| `discountType`                                          | enum         | `none`, `percentage` or `fixed`.                                                                                                                                             |
| `discount`                                              | number       | Used with `discountType`.                                                                                                                                                    |
| `shippingFee`                                           | number       | Added to the total.                                                                                                                                                          |
| `tax_direction`                                         | string       | `merchant` (default) or `customer`. When `customer`, the service fee and VAT are added on top of the total at checkout; when `merchant`, they are deducted from your payout. |
| `notes`                                                 | string       | Optional.                                                                                                                                                                    |
| `metadata`                                              | object       | Arbitrary JSON.                                                                                                                                                              |
| `checkoutCallbackUrl`                                   | string (URL) | Optional. Hyparrow `POST`s payment-status events here.                                                                                                                       |
| `lineItems`                                             | array        | **Required, min 1.** See below.                                                                                                                                              |

Each line item:

| Field                       | Type          | Notes                                                                                     |
| --------------------------- | ------------- | ----------------------------------------------------------------------------------------- |
| `productId`                 | string        | Optional. Links to a product; `description` and `unitPrice` are auto-filled.              |
| `description`               | string        | Required when `productId` is not provided.                                                |
| `quantity`                  | int           | **Required, > 0.**                                                                        |
| `unitPrice`                 | number        | Required (> 0) for freeform items; defaults to the product price when `productId` is set. |
| `taxType` / `taxRate`       | enum / number | Line-item tax — **only applied when invoice-level `taxType` is `none`**.                  |
| `discountType` / `discount` | enum / number | Line-item discount.                                                                       |

<Note>
  If a line item links a `productId` whose currency differs from the invoice
  currency, you must send an explicit (converted) `unitPrice`. Hyparrow refuses
  to silently reuse a price under a different currency label.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.hyparrow.cloud/api/v1/invoices/ \
    -H "X-API-Key: <your-api-key>" \
    -H "X-API-Secret: <your-api-secret>" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "Website redesign",
      "currency": "NGN",
      "customerName": "Ada Obi",
      "customerEmail": "ada@example.com",
      "dueDate": "2026-07-15",
      "tax_direction": "customer",
      "taxType": "percentage",
      "taxRate": 7.5,
      "checkoutCallbackUrl": "https://yourapp.com/webhooks/hyparrow",
      "lineItems": [
        { "description": "Design sprint", "quantity": 1, "unitPrice": 250000000 },
        { "description": "Revisions",     "quantity": 2, "unitPrice": 50000000 }
      ]
    }'
  ```
</CodeGroup>

### Response `201`

```json theme={null}
{
  "success": true,
  "message": "Invoice created successfully",
  "data": {
    "id": "8f1c2e34-5a6b-47c8-9d0e-112233445566",
    "invoiceNumber": "invoice-hpw-000000042",
    "title": "Website redesign",
    "currency": "NGN",
    "status": "draft",
    "customerName": "Ada Obi",
    "customerEmail": "ada@example.com",
    "subTotal": 350000000,
    "discountTotal": 0,
    "taxTotal": 26250000,
    "shippingFee": 0,
    "totalAmount": 376250000,
    "tax_direction": "customer",
    "checkoutCallbackUrl": "https://yourapp.com/webhooks/hyparrow",
    "lineItems": [ /* ... */ ]
  }
}
```

The checkout URL for this invoice is `CHECKOUT_BASE_URL/pay/8f1c2e34-...`. Note
that a freshly created invoice is `draft` — share it via
[`send-email`](#email-an-invoice) (which publishes it) or publish it explicitly
before sending the link.

## List invoices

`GET /invoices/`

Paginated list of your account's invoices.

| Query    | Default | Notes                                                        |
| -------- | ------- | ------------------------------------------------------------ |
| `page`   | `1`     |                                                              |
| `limit`  | `20`    |                                                              |
| `status` | —       | Filter by `draft`, `pending`, `paid`, `overdue`, `canceled`. |

```json theme={null}
{
  "success": true,
  "data": [ /* invoices */ ],
  "pagination": { "page": 1, "limit": 20, "total": 42, "totalPages": 3 }
}
```

## Search invoices

`GET /invoices/search`

| Query                | Notes              |
| -------------------- | ------------------ |
| `q`                  | Free-text query.   |
| `status`             | Status filter.     |
| `dateFrom`, `dateTo` | Date-range filter. |

## Get an invoice

`GET /invoices/{invoiceId}`

Returns a single invoice (with totals and line items) for your account. Returns
`404` if not found.

## Update an invoice

`PUT /invoices/{invoiceId}`

Updates editable fields — customer details, currency, dates, tax/discount,
`status`, `notes`, `metadata`. Send only the fields you want to change.

<Warning>
  **Paid invoices cannot be edited.** Updating an invoice whose status is `paid`
  returns `400` with `"paid invoices cannot be edited"`.
</Warning>

## Delete an invoice

`DELETE /invoices/{invoiceId}`

```json theme={null}
{ "success": true, "message": "Invoice deleted successfully" }
```

## Record a payment

`POST /invoices/{invoiceId}/pay`

Marks an invoice `paid` using a payment you collected yourself (e.g. cash, or a
transfer in your own wallet system). For customer-driven payments, use the
hosted checkout instead.

| Field           | Type   | Notes                                                          |
| --------------- | ------ | -------------------------------------------------------------- |
| `paymentMethod` | enum   | **Required.** `bank_transfer`, `card`, `cash` or `mobile_pay`. |
| `reference`     | string | Your own payment reference. Optional.                          |
| `payerName`     | string | Optional.                                                      |

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/invoices/{invoiceId}/pay \
  -H "X-API-Key: <your-api-key>" \
  -H "X-API-Secret: <your-api-secret>" \
  -H "Content-Type: application/json" \
  -d '{ "paymentMethod": "cash", "reference": "POS-9921", "payerName": "Ada Obi" }'
```

## Line items

Manage line items on an existing invoice. Totals are recalculated on every
change.

| Method & path                                          | Purpose             |
| ------------------------------------------------------ | ------------------- |
| `GET /invoices/{invoiceId}/line-items`                 | List line items.    |
| `POST /invoices/{invoiceId}/line-items`                | Add a line item.    |
| `PUT /invoices/{invoiceId}/line-items/{lineItemId}`    | Update a line item. |
| `DELETE /invoices/{invoiceId}/line-items/{lineItemId}` | Remove a line item. |

Add a line item:

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/invoices/{invoiceId}/line-items \
  -H "X-API-Key: <your-api-key>" \
  -H "X-API-Secret: <your-api-secret>" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Hosting (1 yr)", "quantity": 1, "unitPrice": 12000000 }'
```

The body matches the create-invoice line item shape: provide either a
`productId` (auto-fills `description`/`unitPrice`) or a freeform
`description` + `unitPrice` (> 0).

## Generate a virtual account

`POST /invoices/{invoiceId}/generate-va`

Creates a temporary **virtual account** (a dedicated bank account number) the
customer can transfer the exact invoice amount to. A transfer that matches the
amount settles the invoice automatically.

| Field           | Type | Notes                                  |
| --------------- | ---- | -------------------------------------- |
| `expiryMinutes` | int  | **Required.** One of `10`, `15`, `30`. |

```json theme={null}
{
  "success": true,
  "message": "Virtual account generated — pay the exact amount before it expires",
  "data": {
    "accountNumber": "1234567890",
    "accountName": "HYPARROW/Website redesign",
    "bankName": "Wema Bank",
    "bankCode": "035",
    "expiresAt": "2026-06-30T20:15:00Z"
  }
}
```

<Note>
  Virtual accounts are an **NGN-only** rail. They are not available for
  non-NGN invoices.
</Note>

## Get the active virtual account

`GET /invoices/{invoiceId}/va`

Returns the current active, non-expired virtual account for the invoice, or
`404` if none exists.

## Receipt & PDFs

Once an invoice is `paid`, a receipt becomes available.

| Method & path                                | Returns                                           |
| -------------------------------------------- | ------------------------------------------------- |
| `GET /invoices/{invoiceId}/receipt`          | Receipt JSON (paid invoices only).                |
| `GET /invoices/{invoiceId}/download`         | The **invoice** as a PDF (`application/pdf`).     |
| `GET /invoices/{invoiceId}/download-receipt` | The branded **receipt** PDF (paid invoices only). |

The PDF endpoints stream a file with a `Content-Disposition: attachment`
header — request them with your API key headers and save the binary body.

## Email an invoice

`POST /invoices/{invoiceId}/send-email`

Generates the invoice PDF and emails it to the customer, with the hosted
checkout link embedded. This is the usual way to "send" an invoice.

* A `draft` invoice is **auto-published to `pending`** so the checkout link
  works.
* For a `paid` invoice this sends the **receipt** instead.
* A duplicate send is rejected (`"Invoice has already been mailed"` /
  `"Receipt has already been mailed"`).
* Fails with `400` if the invoice has no `customerEmail`.

```json theme={null}
{ "success": true, "message": "Invoice emailed to ada@example.com" }
```

## Next steps

* [Checkout guide](/checkout) — the public API the hosted payment page uses, and
  the callback webhook.
* [Sandbox](/sandbox) — test the full flow with simulated money.
