Authentication and keeping keys safe
How a key is sent, what each access level opens, and where a key must never go.
Sending the key
Put the key in an Authorization header as a bearer token. Nothing else authenticates a request. Cookies and a signed-in browser session are never read on this surface, so nothing riding a user's browser can act on your books.
curl "https://www.numm.io/api/v1/businesses" \
-H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY"Read only and Read and write
A key is created at one of two access levels. Read only opens every GET. Read and write opens those plus the writes. There is no level in between, and a key's level cannot be changed later: create a new key instead.
A Read only key can call these 15:
GET /businessesLists the businesses in your account that this key may use.GET /accountsLists the accounts you can post to, each with its place in the chart of accounts.GET /customersLists the customers in your account, the billing contacts invoices go to.GET /vendorsLists the vendors in your account, who expenses are paid to.GET /transactionsLists expenses and bank-feed transactions for one business, newest first.GET /transactions/{id}Returns full detail for one transaction, including how it was recorded in the books.GET /invoicesLists customer invoices for one business, newest first.GET /invoices/{id}Returns full detail for one invoice, including its lines, payments, and pay link.GET /review-queueReturns pending AI-categorized expenses awaiting human review.GET /todayReturns the Today homepage for one business: the headline and its ranked signal cards.GET /reports/profit-and-lossReturns the profit and loss statement for one business over a date range.GET /reports/balance-sheetReturns the balance sheet for one business as of a date.GET /reports/trial-balanceLists every account's balance as of a date; accountants ask for this.GET /reports/ar-agingReturns unpaid customer invoices bucketed by how overdue they are.GET /reports/ap-agingReturns what one business owes, bucketed by how long it has been outstanding.
A Read and write key can call those, and these 7:
POST /customersCreates a customer, a billing contact invoices go to.POST /expensesRecords and immediately posts a manual expense.POST /invoicesCreates a draft invoice for a customer. Nothing is sent and nothing is emailed.POST /invoices/{id}/sendFinalizes a draft invoice and emails it to the customer.POST /invoices/{id}/remindSends a payment reminder email for one sent (or overdue) invoice.POST /invoices/{id}/paymentsRecords a payment received against a sent invoice: check, cash, wire, or other.POST /review-queue/{id}/acceptPosts a pending review-queue expense, under the proposed account or one you name.
Calling a write with a Read only key answers 403 insufficient_scope. Every write a key makes is written to the audit log under the key's name.
A key limited to certain businesses
When you create a key you choose whether it can work in every business in your account or only in the ones you pick. Sending an entityId it was not given answers 403 entity_not_allowed.
Asking for a single row that lives in a business the key was not given is different: it answers 404 not_found, the same answer as an id that never existed. A limited key learns nothing about what it cannot reach.
Customers and vendors are the exception. They belong to your account rather than to one business, so a limited key still sees all of them.
Expiry
A key can be given an expiry date when you create it. It stops working at the end of that day, UTC. From then on every request with it answers 401 invalid_key.
Revoking and rotating
Revoke a key from Settings → API keys. It stops working immediately: the next request with it is refused. A request already running finishes. The key cannot be brought back, but it stays in your history, marked revoked, so what it did is still on record.
To rotate, create the new key first, move your software over to it, confirm it is working, and only then revoke the old one. Revoking first means an outage.
What an invalid key looks like
A missing key, a malformed key, a revoked key and an expired key all answer the same way, and so does a key belonging to an account that does not have the API turned on. The status is 401 and the body says only this:
{
"error": {
"code": "invalid_key",
"message": "That API key isn't valid. Create one in Settings → API keys.",
"requestId": "api:unauthenticated:0190a1b2-c3d4-7e5f-8a9b-0c1d2e3fee00"
}
}The answer is deliberately the same in every case. If a key stopped working and you do not know why, Settings → API keys shows whether it is active, expired or revoked.
Keeping a key safe
- Give each piece of software its own key. Share one between two and revoking either breaks both, and the audit log cannot tell you which of them did something.
- Never put a key in a browser or a mobile app. Anyone with the device can read it. No response from this API carries an
Access-Control-Allow-Originheader, so a page on another origin cannot read one anyway. That is by design, not an oversight: call the API from your own server. - Never commit a key to a repository. The
nmo_live_prefix is chosen so a secret scanner can recognise a leaked key, but do not rely on one: keep the key in an environment variable or a secret store. - Never paste a key into a shared document, a chat, or a support ticket. We never need it.
- Revoke anything you are not using. Settings → API keys flags a key that has gone unused.