Spending you record yourself, posted the moment you send it.
One endpoint, with the fields it takes and the fields it gives back.
POST/expenses
Records and immediately posts a manual expense.
- 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. - Send exactly one of
vendorNameorvendorId. - A
vendorNamethat matches no existing vendor exactly creates a new one, so prefervendorIdwhen you have it. The match is case sensitive. amountCentsmust be greater than zero.- The account you send is posted as it is. Nothing here is categorized by AI, and nothing lands in the review queue.
Needs the expenses:write permission. Only a Read and write key has it.
Body fields
entityId
Typestring
RequiredOptional
Details
- a uuid
date
Typestring
RequiredRequired
Details
- a date like 2026-09-20
amountCents
Typestring
RequiredRequired
Details
- digits only, whole cents
vendorName
Typestring
RequiredOptional
Details
- up to 200 characters
vendorId
Typestring
RequiredOptional
Details
- a uuid
accountId
Typestring
RequiredRequired
Details
- a uuid
memo
Typestring
RequiredOptional
Details
- up to 2000 characters
| Field | Type | Required | Details |
|---|---|---|---|
| entityId | string | Optional |
|
| date | string | Required |
|
| amountCents | string | Required |
|
| vendorName | string | Optional |
|
| vendorId | string | Optional |
|
| accountId | string | Required |
|
| memo | string | Optional |
|
Request
curl -X POST "https://www.numm.io/api/v1/expenses" \
-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",
"date": "2026-02-01",
"amountCents": "1500",
"vendorId": "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f1004",
"accountId": "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f1003"
}'Response 201
{
"expenseId": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f100a",
"transactionId": "0190a1b2-c3d4-7e5f-9a9b-0c1d2e3f1006"
}Response fields
expenseId
Typestring
PresentAlways
Details
- a uuid
transactionId
Typestring or null
PresentAlways
Details
- a uuid
| Field | Type | Present | Details |
|---|---|---|---|
| expenseId | string | Always |
|
| transactionId | string or null | 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
entity_not_allowed
Status403
MeaningYour key is limited to certain businesses, and this is not one of them.
not_found
Status404
MeaningThere is no such record, or none your key can reach.
period_locked
Status409
MeaningThe date falls inside a closed period, so nothing can post to it.
read_only_billing_state
Status402
MeaningYour subscription is past due, so your books are read only for now.
conflict
Status409
MeaningThe request clashed with something that already happened.
| 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 | 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:
invalid_key
Status401
MeaningYour key is missing, malformed, revoked, or expired.
insufficient_scope
Status403
MeaningYour key does not carry the scope this endpoint needs.
rate_limited
Status429
MeaningYou've made too many requests for this key. The retry-after header says how long to wait.
validation_failed
Status422
MeaningSomething in the path, the query, or the body did not pass validation.
internal_error
Status500
MeaningSomething went wrong on our side. Try again.
| 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. |