Receivables
Invoices
Every charge is its own invoice, posted to the ledger immediately and never emailed by HelloLedger.
Create an invoice
Scope: invoices:write. HelloLedger posts the invoice to the ledger right away (debit A/R, credit each line's income account) and marks it sent. No email goes to the customer; your app stays in charge of communication.
curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_2026_10_rent_1001" \
-H "Authorization: Bearer $HL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_external_id": "stu_1001",
"date": "2026-10-01",
"due_date": "2026-10-05",
"memo": "October rent, unit 4B",
"lines": [
{
"item_id": "66f1a2b3c4d5e6f708091a2b",
"description": "October rent",
"quantity": "1",
"unit_price": "850.00",
"amount": "850.00"
}
]
}'{
"invoice_id": "66f3…",
"external_id": "chg_2026_10_rent_1001",
"version": 1,
"invoice_number": "1042",
"customer_id": "66f2…",
"customer_external_id": "stu_1001",
"date": "2026-10-01",
"due_date": "2026-10-05",
"status": "sent",
"total_amount": "850.00",
"amount_paid": "0.00",
"balance_due": "850.00",
"tax_amount": "0.00",
"currency": "USD",
"lines": [
{ "item_id": "66f1a2b3c4d5e6f708091a2b", "item_name": "Rent", "account_id": "66f1…", "description": "October rent", "quantity": "1.0", "unit_price": "850.00", "amount": "850.00", "taxable": false }
],
"result": "created"
}Fields
| Field | Type | Notes |
|---|---|---|
| customer_external_id | string, required | The customer's external ID. |
| customer | object | Optional customer fields. Creates the customer if it doesn't exist yet; ignored otherwise. |
| date | YYYY-MM-DD, required | Invoice date. Posting date for the ledger. |
| due_date | YYYY-MM-DD | Or use terms. |
| terms | due_on_receipt | net_15 | net_30 | net_45 | net_60 | Sets due_date when due_date is omitted. |
| invoice_number | string | Optional. HelloLedger assigns the next number when omitted. |
| memo | string | Optional. |
| tax_rate | decimal 0–100 | Sales tax percent applied to taxable lines. |
| lines | array, 1–100 | See below. |
Lines
Each line names exactly one of item_id (a product or service from GET /items, which brings its income account) or account_id (any account from GET /coa, for example a liability for deposits).
amount is the line total and is required. quantity and unit_price are shown on the invoice. taxable defaults to the item's setting, or false for account lines. Send money as strings like "850.00" to avoid floating-point surprises.
curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_2026_10_late_1001" \
-H "Authorization: Bearer $HL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_external_id": "stu_1001",
"customer": {
"name": "Jamie Rivera",
"email": "jamie@example.com"
},
"date": "2026-10-06",
"terms": "due_on_receipt",
"lines": [
{
"account_id": "66f1a2b3c4d5e6f708091b2c",
"description": "Late fee",
"amount": "25.00"
}
]
}'Changing an invoice
- Re-sending the same body returns result unchanged.
- A different body on an unpaid invoice replaces it: HelloLedger voids the posted version and posts a new one with the same invoice number. version goes up by one and result is replaced.
- If any payment or credit is applied, the change is rejected with 409 invoice_has_payments. Void the payment first.
Void an invoice
Reverses the ledger posting. A voided external ID stays voided; send a corrected charge under a new external ID.
curl -X POST "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_2026_10_late_1001/void" \
-H "Authorization: Bearer $HL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Late fee waived"
}'Read an invoice
Returns the current version with amount_paid and balance_due. (GET /invoices/{invoice_id} takes HelloLedger's ID instead and needs reports:read.)
curl "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_2026_10_rent_1001" \
-H "Authorization: Bearer $HL_API_KEY"