API
Language
Ask for a seat

Closed beta. Nothing here is callable yet. These docs describe the day-one contract, and names marked proposed may change before the beta opens. Ask for a seat

Docs · Getting started

Authentication

The API uses the OAuth 2.0 client credentials grant. The account owner creates a client in the Novo app; your code exchanges it for a short-lived token and sends that token on every call.

Clients

  • Only the account owner can create a client, and only in the Novo app, after a verification step. The same verification applies when a client is rotated or reactivated.
  • One business per client. A client is bound to the owner and to one business. It can’t reach any other business.
  • The secret is shown once. Novo stores only a hash of it. Store it like a password, and send it only to the token endpoint, never on an API call.
  • Secrets expire, both after a fixed period and after a period without use. The periods are proposed and may change during the beta.

Scopes

The owner picks a client’s scopes when creating it. They’re fixed for the life of the client: to change them, create a new client. Anything a scope doesn’t grant is refused.

accounts:read
Balances and account details, numbers masked to the last 4 digits
transactions:read
Posted, pending and card transactions
bookkeeping:read
Bookkeeping transactions
cards:read
Card list with the last 4 digits and status
cards:freeze
Freeze a card. Freeze only: unfreezing stays in the Novo app

Access tokens

Exchange the client id and secret at /v1/oauth/token with grant_type=client_credentials. HTTP Basic is the default; sending both as form fields also works.

Request
curl -X POST "https://$NOVO_API_HOST/v1/oauth/token" \
  -u "nvo_client_example:$CLIENT_SECRET" \
  -d "grant_type=client_credentials"
  • The token is opaque: it isn’t a JWT, so don’t try to decode it.
  • It lasts expires_in seconds: 900, or 15 minutes proposed. Cache it and reuse it until then. When a call returns token-expired, get a new one.
  • It carries the client’s scopes. The scope field in the request is reserved and ignored.
  • Send it as Authorization: Bearer <token>.

The token endpoint answers with OAuth 2.0 errors rather than problem details: one of invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope. Any problem with the client itself gets the same invalid_client answer, whatever the cause.

Revocation

The owner can revoke a client in the Novo app at any time. Revocation takes effect within 30 seconds proposed, plus any call already in flight. A revoked or suspended client, or one Novo has switched off, gets 401 client-disabled.

If the owner this client is bound to loses the owner role, or their profile is locked or no longer in good standing, calls stop. You get 403 owner-standing-lost first; once the client is revoked, 401 client-disabled. A current owner creates a new client in the app.

Giving access to an AI agent

Keep the client secret in your secret store and use it only to call the token endpoint. The agent gets the short-lived access token, never the secret: a leaked token is at most 15 minutes of the client’s scopes, while a leaked secret is the whole client until you rotate it proposed. Give the agent a client with only the scopes it needs: a client without cards:freeze can only read. See untrusted text for what an agent should never take as an instruction.

Locking a key to where it runs Planned

You can lock a key to the tools and machines you’ve approved. If it’s used from anywhere else, it stops and waits for your yes in the Novo app.

Your integration holds a key pair where it runs and signs each request with it (OAuth 2.0 DPoP, RFC 9449). Novo never sees the private key; your tool sends only the public key. Once a key is locked, a stolen client secret or access token gets nothing on its own. A locked client gets token_type DPoP instead of Bearer, and sends a fresh proof with every request.

  • Pair the first installation soon after you create the key (within 15 minutes) by matching a short code your tool prints in the Novo app.
  • A request from any other installation is held, one at a time, for 15 minutes until you approve it in the app. Declining also lets you revoke the key.
  • Remove an installation in the app and its tokens stop working within about 30 seconds.
  • The API can’t tell apps apart by name. Give each tool its own client with only the scopes it needs, and lock each one to where it runs.
  • Locking needs your tool to hold a key pair where it runs. A hosted service that can’t do that can’t be locked; give it its own client with only the scopes it needs.

What it doesn’t stop:

  • Someone who takes the client secret and the private key from the same machine. They are the client until you remove that source in the app.
  • A compromised or prompt-injected process using the key where it lives. Scopes and per-action approval are the protection there.