Invoices you bill customers for, from draft to paid.
6 endpoints, each with the fields it takes and the fields it gives back.
GET/invoices
Lists customer invoices for one business, newest first.
- Leave
entityIdout only when this key can reach exactly one business. When it can reach several, a call that omits it is refused; callGET /businessesfor the ids. dateFromanddateTomatch the issue date and include both ends.- To read the next page, pass the previous response's
nextCursor. Anullthere means you already have everything. overdueOnlyis applied to the page you asked for, not to the whole list, so a page can come back with fewer rows thanlimit, or none, whilenextCursoris still set.- An invoice's figures are in the invoice's own currency, which may not be US dollars, and the
currencybeside each figure says which. Every other amount on this surface is US dollars.
Needs the invoices:read permission. A Read only key has it.
Query parameters
- a uuid
- one of: draft, sent, viewed, partially_paid, paid, overdue, void
- a uuid
- a date like 2026-09-20
- a date like 2026-09-20
- one of: true, false
- an opaque value from a previous response
- 1 to 100, default 50
| Field | Type | Required | Details |
|---|---|---|---|
| entityId | string | Optional |
|
| status | string | Optional |
|
| customerId | string | Optional |
|
| dateFrom | string | Optional |
|
| dateTo | string | Optional |
|
| overdueOnly | string | Optional |
|
| cursor | string | Optional |
|
| limit | integer | Optional |
|
curl "https://www.numm.io/api/v1/invoices?entityId=0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f1002&status=sent" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY"{
"invoices": [
{
"id": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007",
"number": "ACME-0001",
"status": "sent",
"customer": "Acme Co",
"issueDate": "2026-06-01",
"dueDate": "2026-06-15",
"total": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"paid": {
"cents": "0",
"formatted": "$0.00",
"currency": "USD"
},
"balance": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"currency": "USD"
}
],
"nextCursor": null
}Response fields
- a uuid
- one of: draft, sent, viewed, partially_paid, paid, overdue, void
- a date like 2026-09-20
- a date like 2026-09-20
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- one of: USD, CAD, EUR, GBP, MXN
- an opaque value from a previous response
| Field | Type | Present | Details |
|---|---|---|---|
| invoices | array of object | Always | |
| invoices[].id | string | Always |
|
| invoices[].number | string | Always | |
| invoices[].status | string | Always |
|
| invoices[].customer | string | Always | |
| invoices[].issueDate | string | Always |
|
| invoices[].dueDate | string | Always |
|
| invoices[].total | money | Always |
|
| invoices[].paid | money | Always |
|
| invoices[].balance | money | Always |
|
| invoices[].currency | string | Always |
|
| nextCursor | string or null | Always |
|
Errors
| Status | Code | Meaning |
|---|---|---|
| 403 | entity_not_allowed | Your key is limited to certain businesses, and this is not one of them. |
| 404 | not_found | There is no such record, or none your key can reach. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |
GET/invoices/{id}
Returns full detail for one invoice, including its lines, payments, and pay link.
- An invoice's figures are in the invoice's own currency, which may not be US dollars, and the
currencybeside each figure says which. Every other amount on this surface is US dollars. payUrlis the customer's own pay page and needs no sign-in. Anyone with the link can open the invoice and pay it, so treat it like the invoice email itself.
Needs the invoices:read permission. A Read only key has it.
Path parameters
- a uuid
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | Required |
|
curl "https://www.numm.io/api/v1/invoices/0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY"{
"id": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007",
"number": "ACME-0001",
"status": "sent",
"customer": {
"id": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1005",
"name": "Acme Co"
},
"issueDate": "2026-06-01",
"dueDate": "2026-06-15",
"terms": "Net 15",
"currency": "USD",
"subtotal": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"tax": {
"cents": "0",
"formatted": "$0.00",
"currency": "USD"
},
"total": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"paid": {
"cents": "0",
"formatted": "$0.00",
"currency": "USD"
},
"balance": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"memo": null,
"lines": [
{
"description": "Consulting",
"quantity": "1",
"unitPrice": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"amount": {
"cents": "10000",
"formatted": "$100.00",
"currency": "USD"
},
"account": "Consulting Revenue"
}
],
"payments": [],
"payUrl": "https://www.numm.io/pay/abc123"
}Response fields
- a uuid
- one of: draft, sent, viewed, partially_paid, paid, overdue, void
- a uuid
- a date like 2026-09-20
- a date like 2026-09-20
- one of: USD, CAD, EUR, GBP, MXN
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
- one of: stripe, check, cash, wire, other
- a date like 2026-09-20
| Field | Type | Present | Details |
|---|---|---|---|
| id | string | Always |
|
| number | string | Always | |
| status | string | Always |
|
| customer | object | Always | |
| customer.id | string | Always |
|
| customer.name | string | Always | |
| issueDate | string | Always |
|
| dueDate | string | Always |
|
| terms | string or null | Always | |
| currency | string | Always |
|
| subtotal | money | Always |
|
| tax | money | Always |
|
| total | money | Always |
|
| paid | money | Always |
|
| balance | money | Always |
|
| memo | string or null | Always | |
| lines | array of object | Always | |
| lines[].description | string | Always | |
| lines[].quantity | string | Always | |
| lines[].unitPrice | money | Always |
|
| lines[].amount | money | Always |
|
| lines[].account | string | Always | |
| payments | array of object | Always | |
| payments[].amount | money | Always |
|
| payments[].method | string | Always |
|
| payments[].paidAt | string | Always |
|
| payUrl | string | Always |
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | There is no such record, or none your key can reach. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |
POST/invoices
Creates a draft invoice for a customer. Nothing is sent and nothing is emailed.
- Leave
entityIdout only when this key can reach exactly one business. When it can reach several, a call that omits it is refused; callGET /businessesfor the ids. unitPriceCentsis whole cents, andquantityallows up to six decimal places. Each line's quantity must be greater than zero.issueDatedefaults to the current date in UTC.dueDatedefaults toissueDateplus the payment terms, taken from the customer, then the business, then your account. AdueDatebeforeissueDateis refused.- Every line amount, the subtotal, and the total must each stay under ten trillion dollars.
currencyis the invoice's own currency, and defaults to US dollars when you leave it out. It is not taken from the customer, even when the customer is set to another currency, which is how the app's own invoice form behaves too.unitPriceCents, and every figure the invoice reads back with, are cents of the invoice's own currency. Your books stay in US dollars: the conversion happens when the invoice is sent.- Sending an invoice in another currency needs an exchange rate for its issue date. Until one has been fetched,
POST /invoices/{id}/sendis refused withvalidation_failedand the draft is left as it is.
Needs the invoices:write permission. Only a Read and write key has it.
Body fields
- a uuid
- a uuid
- a date like 2026-09-20
- 0 to 365
- a date like 2026-09-20
- up to 2000 characters
- a number, up to 6 decimal places
- digits only, whole cents
- a uuid
- one of: USD, CAD, EUR, GBP, MXN
- a percentage from 0 to 100, up to 6 decimal places
- up to 2000 characters
| Field | Type | Required | Details |
|---|---|---|---|
| entityId | string | Optional |
|
| customerId | string | Required |
|
| issueDate | string | Optional |
|
| paymentTermsDays | integer | Optional |
|
| dueDate | string | Optional |
|
| lines | array of object | Required | |
| lines[].description | string | Required |
|
| lines[].quantity | string | Required |
|
| lines[].unitPriceCents | string | Required |
|
| lines[].accountId | string | Required |
|
| currency | string | Optional |
|
| taxRatePercent | string | Optional |
|
| memo | string | Optional |
|
curl -X POST "https://www.numm.io/api/v1/invoices" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3fee00" \
-d '{
"entityId": "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f1002",
"customerId": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1005",
"lines": [
{
"description": "Consulting",
"quantity": "1",
"unitPriceCents": "10000",
"accountId": "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f1003"
}
]
}'{
"invoiceId": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007",
"invoiceNumber": "ACME-0001",
"webUrl": "https://www.numm.io/app/invoices/0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007"
}Response fields
- a uuid
| Field | Type | Present | Details |
|---|---|---|---|
| invoiceId | string | Always |
|
| invoiceNumber | string | Always | |
| webUrl | string | Always |
This call takes an optional Idempotency-Key header. Repeat the same request with the same key within 24 hours and you get the first response back, with an idempotent-replayed: true header, and the work does not run twice. The same key with a different request is refused. See Idempotency and safe retries.
Errors
| Status | Code | Meaning |
|---|---|---|
| 403 | entity_not_allowed | Your key is limited to certain businesses, and this is not one of them. |
| 404 | not_found | There is no such record, or none your key can reach. |
| 409 | conflict | The request clashed with something that already happened. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |
POST/invoices/{id}/send
Finalizes a draft invoice and emails it to the customer.
- Finalizing posts the invoice to the books and emails the customer before this call returns.
- There is no API call that undoes it.
- An invoice drafted in a currency other than US dollars needs an exchange rate for its issue date. Without one this call is refused with
validation_failed, and the draft is left as it was. Try again once the day's rate has been fetched.
Needs the invoices:write permission. Only a Read and write key has it.
Path parameters
- a uuid
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | Required |
|
No request body.
curl -X POST "https://www.numm.io/api/v1/invoices/0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007/send" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY" \
-H "Idempotency-Key: 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3fee00"{
"ok": true
}Response fields
- always true
| Field | Type | Present | Details |
|---|---|---|---|
| ok | boolean | Always |
|
This call takes an optional Idempotency-Key header. Repeat the same request with the same key within 24 hours and you get the first response back, with an idempotent-replayed: true header, and the work does not run twice. The same key with a different request is refused. See Idempotency and safe retries.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | There is no such record, or none your key can reach. |
| 409 | period_locked | The date falls inside a closed period, so nothing can post to it. |
| 402 | read_only_billing_state | Your subscription is past due, so your books are read only for now. |
| 409 | conflict | The request clashed with something that already happened. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |
POST/invoices/{id}/remind
Sends a payment reminder email for one sent (or overdue) invoice.
- The invoice has to be sent, viewed, or overdue, and the customer has to have an email address.
- A second reminder for the same invoice within 24 hours is refused with
conflict.
Needs the invoices:write permission. Only a Read and write key has it.
Path parameters
- a uuid
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | Required |
|
No request body.
curl -X POST "https://www.numm.io/api/v1/invoices/0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007/remind" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY" \
-H "Idempotency-Key: 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3fee00"{
"ok": true
}Response fields
- always true
| Field | Type | Present | Details |
|---|---|---|---|
| ok | boolean | Always |
|
This call takes an optional Idempotency-Key header. Repeat the same request with the same key within 24 hours and you get the first response back, with an idempotent-replayed: true header, and the work does not run twice. The same key with a different request is refused. See Idempotency and safe retries.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | There is no such record, or none your key can reach. |
| 409 | conflict | The request clashed with something that already happened. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |
POST/invoices/{id}/payments
Records a payment received against a sent invoice: check, cash, wire, or other.
amountCentsmust be greater than zero, and may not exceed the remaining balance.- The invoice has to have been sent already, and must not be fully paid.
methoddefaults toother, andpaidAtdefaults to the current date in UTC.methodcovers the payments you take yourself. Card payments arrive through Stripe and post on their own.- The balance in the response leaves out any payment that was refunded.
amountCentsand thebalancein the response are in the invoice's own currency, which may not be US dollars. Every other amount on this surface is US dollars.
Needs the invoices:write permission. Only a Read and write key has it.
Path parameters
- a uuid
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | Required |
|
Body fields
- digits only, whole cents
- a date like 2026-09-20
- one of: check, cash, wire, other
- up to 2000 characters
| Field | Type | Required | Details |
|---|---|---|---|
| amountCents | string | Required |
|
| paidAt | string | Optional |
|
| method | string | Optional |
|
| memo | string | Optional |
|
curl -X POST "https://www.numm.io/api/v1/invoices/0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1007/payments" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3fee00" \
-d '{
"amountCents": "4000",
"method": "check"
}'{
"paymentId": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1009",
"status": "partially_paid",
"balance": {
"cents": "6000",
"formatted": "$60.00",
"currency": "USD"
}
}Response fields
- a uuid
- one of: draft, sent, viewed, partially_paid, paid, overdue, void
- an object:
cents(whole cents as a digit string, in the currency beside it),formatted(a display string, not something to parse),currency(a three-letter code)
| Field | Type | Present | Details |
|---|---|---|---|
| paymentId | string | Always |
|
| status | string | Always |
|
| balance | money | Always |
|
This call takes an optional Idempotency-Key header. Repeat the same request with the same key within 24 hours and you get the first response back, with an idempotent-replayed: true header, and the work does not run twice. The same key with a different request is refused. See Idempotency and safe retries.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | There is no such record, or none your key can reach. |
| 409 | period_locked | The date falls inside a closed period, so nothing can post to it. |
| 402 | read_only_billing_state | Your subscription is past due, so your books are read only for now. |
| 409 | conflict | The request clashed with something that already happened. |
Every endpoint can also return:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key | Your key is missing, malformed, revoked, or expired. |
| 403 | insufficient_scope | Your key does not carry the scope this endpoint needs. |
| 429 | rate_limited | You've made too many requests for this key. The retry-after header says how long to wait. |
| 422 | validation_failed | Something in the path, the query, or the body did not pass validation. |
| 500 | internal_error | Something went wrong on our side. Try again. |