SuperBooksDevelopers

Authentication

API keys for your own team, OAuth with PKCE and dynamic client registration for integrators, and how scopes gate the tool surface.

Every request to https://api.superbooks.io/mcp carries a credential. There are two kinds, and which one you want depends on who the code runs for.

Use whenSetup cost
API keyYour code acts on behalf of your own team — a script, a backend job, your own AI client.Mint one in the app.
OAuthYou are building a product that other SuperBooks teams sign in to.Register a client, run the authorization-code flow.

API keys

Minting a key

API keys are created in the SuperBooks app, at Settings → Developer (app.superbooks.io/settings/developer). Choose a scope preset when you create the key:

PresetGrants
AllThe apis.all meta-scope — every resource, read and write.
Read OnlyThe apis.read meta-scope — every resource, read only.
RestrictedOnly the individual scopes you tick.

The key is displayed once, at creation. It is stored hashed, so it cannot be shown again — if you lose it, delete the key and mint another. Keys begin with sb_.

An API key carries the full authority of its scopes over your team's financial data. Keep it in a secret store or an environment variable — never in source control, and never in client-side code, where anyone can read it.

Sending a key

Send it as a bearer token:

POST /mcp HTTP/1.1
Host: api.superbooks.io
Authorization: Bearer sb_your_api_key_here
Content-Type: application/json
Accept: application/json, text/event-stream

x-api-key is accepted as an alias, for clients that will not let you set an Authorization header:

x-api-key: sb_your_api_key_here

When a credential is missing or invalid

The endpoint answers 401 and points at its OAuth metadata, which is how MCP clients discover the sign-in flow:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.superbooks.io/.well-known/oauth-protected-resource"

Scopes

The scope catalogue is fine-grained — <resource>.<permission> — plus two meta-scopes that expand to a bundle:

bank-accounts.read     bank-accounts.write
customers.read         customers.write
documents.read         documents.write
inbox.read             inbox.write
invoices.read          invoices.write
notifications.read     notifications.write
reports.read
search.read
tags.read              tags.write
teams.read             teams.write
tracker-entries.read   tracker-entries.write
tracker-projects.read  tracker-projects.write
transactions.read      transactions.write
users.read             users.write

apis.read   → every .read scope
apis.all    → every scope, read and write

The authoritative list is served live at /.well-known/oauth-authorization-server under scopes_supported.

That catalogue is the whole API-key and OAuth vocabulary, which is not the same set as the MCP tool domains. Two mismatches are worth knowing before you go looking for a scope that does not exist:

  • There is no categories scope, even though categories is an MCP domain with four tools. Access to it comes from the tier your credential unlocks, described below — not from a scope named after it.
  • users and notifications are real scopes but have no MCP tools at all.

So do not try to derive the tool surface from the scope list, or the scope list from the tool surface. They are related but neither is a subset of the other.

How scopes gate the tool surface

This is the part worth reading carefully, because the two vocabularies do not line up one-to-one. The scope catalogue is per-resource, but the MCP tool filter is coarse: it sorts every tool into one of three tiers — read, write, or destructive — and decides which tiers your credential unlocks.

Your credential holdsTools you see
Any .read scope, or apis.readThe read tier
Any .write scope, or apis.allThe read and write tiers
apis.all, and the team has enabled destructive AI toolsAll three tiers

The consequence is important enough to state bluntly: fine-grained scopes do not survive this mapping. Because the filter is tier-based, a restricted key holding a single write scope such as tags.write unlocks the entire write tier — including unrelated tools like invoices_send. It does not confine the key to tag tools.

Scope your keys accordingly. Treat any write scope as "write across the whole surface", and prefer Read Only for anything that does not need to make changes. The one boundary that is genuinely tight is the destructive gate below, which no fine-grained scope can open.

Destructive tools need two gates

Eight of the 45 tools are marked destructive: the seven *_delete tools, plus invoices_void. Voiding is the odd one out — it is a soft cancel that moves the invoice to canceled and preserves the audit trail rather than removing anything — so read "destructive" as takes something away from the team rather than strictly deletes a row. They stay hidden from tools/list, and refuse to run, unless both of these are true:

  1. The credential holds apis.all (a read-only or restricted key never qualifies, no matter which scopes it lists), and
  2. The team has turned on destructive AI tools, in the app under Settings → AI.

Two independent gates is deliberate: a leaked key on its own cannot reach a destructive tool on a team that has not opted in. Each destructive tool is labelled in the API reference.

OAuth for integrators

If other teams sign in to your product, use OAuth. The server implements authorization code with PKCE, refresh tokens, dynamic client registration, and the discovery documents MCP clients look for — which together are what let a client like Claude offer "Sign in with SuperBooks" without you preregistering anything.

Discovery

Two documents describe the server. Both are public and unauthenticated:

DocumentURL
Authorization server metadata (RFC 8414)https://api.superbooks.io/.well-known/oauth-authorization-server
Protected resource metadata (RFC 9728)https://api.superbooks.io/.well-known/oauth-protected-resource

The authorization server document advertises:

{
  "issuer": "https://api.superbooks.io",
  "authorization_endpoint": "https://api.superbooks.io/oauth/authorize",
  "token_endpoint": "https://api.superbooks.io/api/oauth/token",
  "registration_endpoint": "https://api.superbooks.io/api/oauth/register",
  "revocation_endpoint": "https://api.superbooks.io/api/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "none"]
}

PKCE with S256 is the only challenge method — plain is not accepted.

Dynamic client registration

You can register a client at runtime, with no manual onboarding (RFC 7591):

curl -sS https://api.superbooks.io/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My Integration",
    "redirect_uris": ["https://example.com/callback"]
  }'
{
  "client_id": "sb_client_xxxxxxxxxxxxxxxxxxxxxxxx",
  "client_name": "My Integration",
  "redirect_uris": ["https://example.com/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Registration returns 201. What it validates:

  • client_name is required, 1–100 characters.
  • redirect_uris is required, 1–10 entries.
  • Each redirect URI must be https, or a loopback address for local development (localhost, 127.0.0.1, [::1]). Wildcards are rejected.
  • client_uri and logo_uri are optional, up to 500 characters.

Clients registered this way are public clients: token_endpoint_auth_method is none, so there is no client secret and PKCE carries the security of the exchange. Registration grants nothing on its own — no team's data is reachable until a user completes the consent screen.

Failures come back in the RFC 7591 shape:

{
  "error": "invalid_redirect_uri",
  "error_description": "Invalid redirect URI: http://example.com/callback"
}

Registration is rate limited to 5 requests per hour per IP address. See Rate limits.

The authorization flow

  1. Generate a PKCE verifier and its S256 challenge.
  2. Send the user to https://api.superbooks.io/oauth/authorize with response_type=code, your client_id, redirect_uri, the scope list you need, state, code_challenge, and code_challenge_method=S256.
  3. The user signs in and approves the scopes on the SuperBooks consent screen.
  4. You receive code on your redirect URI. Verify state matches.
  5. Exchange the code at https://api.superbooks.io/api/oauth/token with grant_type=authorization_code, the code, your redirect_uri, client_id, and the code_verifier.
  6. Use the returned access token as a bearer token, exactly like an API key.
  7. Refresh with grant_type=refresh_token when it expires; revoke at https://api.superbooks.io/api/oauth/revoke.

Ask only for the scopes you actually use — the consent screen shows the user precisely what you requested, and a narrow list is approved more readily than a broad one.

On this page