Skip to content

Authentication and keeping keys safe

How a key is sent, what each access level opens, and where a key must never go.

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.

Request
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 /businesses Lists the businesses in your account that this key may use.
  • GET /accounts Lists the accounts you can post to, each with its place in the chart of accounts.
  • GET /customers Lists the customers in your account, the billing contacts invoices go to.
  • GET /vendors Lists the vendors in your account, who expenses are paid to.
  • GET /transactions Lists 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 /invoices Lists 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-queue Returns pending AI-categorized expenses awaiting human review.
  • GET /today Returns the Today homepage for one business: the headline and its ranked signal cards.
  • GET /reports/profit-and-loss Returns the profit and loss statement for one business over a date range.
  • GET /reports/balance-sheet Returns the balance sheet for one business as of a date.
  • GET /reports/trial-balance Lists every account's balance as of a date; accountants ask for this.
  • GET /reports/ar-aging Returns unpaid customer invoices bucketed by how overdue they are.
  • GET /reports/ap-aging Returns what one business owes, bucketed by how long it has been outstanding.

A Read and write key can call those, and these 7:

  • POST /customers Creates a customer, a billing contact invoices go to.
  • POST /expenses Records and immediately posts a manual expense.
  • POST /invoices Creates a draft invoice for a customer. Nothing is sent and nothing is emailed.
  • POST /invoices/{id}/send Finalizes a draft invoice and emails it to the customer.
  • POST /invoices/{id}/remind Sends a payment reminder email for one sent (or overdue) invoice.
  • POST /invoices/{id}/payments Records a payment received against a sent invoice: check, cash, wire, or other.
  • POST /review-queue/{id}/accept Posts 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:

Response 401
{
  "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-Origin header, 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.
Authentication and keeping keys safe — Nummio API