Tabba.
IntegrationerSådan virker TabbaFeatures

© 2026 Tabba. Alle rettigheder forbeholdes.

Udviklere
Udviklere
Udviklere
OverviewConnect claude.aiConnect Claude CodeConnect ChatGPTConnect CursorPlan and commitAuth and scopes

Andre spor

REST APIAPI reference

Auth and scopes

OAuth is the only MCP credential: the flow, the three scopes, the five roles, whose work an answer is about, and how access ends.

The MCP server takes one credential: an OAuth access token that Tabba's own authorization server issued to your client, after you approved it in your browser. There is nothing to paste and nothing to create by hand.

An API key is not a credential here. A tba_ key sent to /api/mcp gets the same 401 as no credential at all. Keys authenticate the REST API at https://www.tabba.io/api/v1: see Authentication.

Why the server insists on a person

A conversation is held with someone. "Which customers do I have?" is a question in the first person, and a credential that names only a company cannot answer it. So every MCP request has an acting user: the person who approved the client. That is what lets me resolve, what makes a read answer from your own work, and what puts a person in the activity log behind every commit.

The OAuth flow

Tabba is the resource server and its own authorization server, both on https://www.tabba.io. A client discovers everything it needs. There is no client id to register by hand.

  1. The client calls https://www.tabba.io/api/mcp with no credential and gets a 401 that names the resource's metadata (RFC 9728).
  2. It reads the discovery documents and registers itself at POST /api/oauth/register (RFC 7591). A public client gets no secret and uses PKCE.
  3. It sends your browser to GET /api/oauth/authorize. Tabba signs you in if you are not, and shows the consent page.
  4. You pick the company and approve. The client is sent back with an authorization code, good once and for sixty seconds.
  5. It exchanges the code at POST /api/oauth/token, with its PKCE verifier, for an access token and a refresh token.

You can watch step 1 from a terminal:

curl -i -X POST https://www.tabba.io/api/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://www.tabba.io/.well-known/oauth-protected-resource/api/mcp"

{"error":{"code":"UNAUTHORIZED","message":"Missing or invalid bearer credential for the MCP resource."}}

That header tells the client where to read the metadata, and the metadata names the authorization server:

https://www.tabba.io/.well-known/oauth-protected-resource/api/mcp
https://www.tabba.io/.well-known/oauth-authorization-server
https://www.tabba.io/.well-known/jwks.json

All three are public. Fetch them when a client will not connect. Between them they name the authorization, token and registration endpoints, the key set that access tokens are signed with, S256 as the only code challenge method, and the three scopes below.

What the authorization server requires of a client

For anyone writing a client, or working out why one is refused:

  • PKCE with S256, on every authorization request.
  • response_type=code. The grants are authorization_code and refresh_token. There is no client credentials grant.
  • A resource parameter equal to https://www.tabba.io/api/mcp. A request without it, or with another value, is refused with invalid_target. A token is issued for that resource and is refused anywhere else.
  • A registered redirect address, matched exactly. A registration takes one to ten: https anywhere, http on the loopback interface, or an app's own scheme, with no fragment.
  • Client authentication at the token endpoint is none for a public client, or client_secret_post.

Registration is open and grants nothing by itself. It is limited to 100 registrations an hour per address, and a registration nobody has used for ninety days is removed. A client that meets an unknown client_id registers again.

Both Accept types, or 406

Streamable HTTP has the client accept application/json and text/event-stream, though this server always answers in JSON. A request with a valid token and only one of them is answered 406. MCP clients send both. A hand-written request needs the header.

The consent page

The page names the client, lists what it asked to be able to do, and asks which company it reaches. Its answer is one of two buttons, Approve or Deny.

  • You pick one company. A connection reaches that company and no other. With several companies none is preselected. To reach another, connect the client again.
  • You are asked every time. No approval is remembered, and no client skips the page.
  • The scopes are the client's request. The page shows what the client asked for and takes one answer for all of it. It has no control for granting less than was asked. Where the request leaves something out, the page says so: "It cannot issue an invoice or a credit note."

Only a company that is connected to its accounting system, and that you can open in Tabba, is offered.

Token lifetimes

TokenLivesNotes
Access token10 minutesSent with each request. It names you, the company and the scope.
Refresh token30 daysSpent by being used: each refresh issues a successor and retires the one presented.

The client refreshes on its own. A client that has not refreshed for thirty days holds no live token and asks you to approve it again.

A refresh token is used once

A spent refresh token that is presented again ends the whole connection, for every holder of its tokens: it means a copy is in someone else's hands. Two refreshes sent at the same moment with one token are treated the same way. If a client keeps asking you to approve again, that is the first thing to suspect.

There is no path without a browser

The consent page takes a signed-in browser session. CI, a scheduled job, a container and a server-side agent cannot complete it, and no grant skips it.

SurfaceWhere the work runs
MCP, over OAuthA person's own client
REST API, on /api/v1CI, a scheduled job, a queue worker, your own backend

The REST API reaches the same Sales data with the same error codes. It has no consent step because it has no person: it authenticates with a key an admin creates, bound to one company.

The three scopes

A token holds one of three scopes, each containing the one before it. The labels are the ones the consent page shows.

ScopeOn the consent pageWhat it allows
mcp:readReadSee invoices, credit notes, customers, products, recurring series, registered work and earned revenue.
mcp:writeDraft and registerDraft and edit invoices, customers, products and recurring series, and register work. Nothing is booked, and nothing is sent to a customer.
mcp:issueIssueIssue invoices and credit notes: they are booked in your accounting system and sent to your customers.

A client that names no scope in its request asks for what it registered for, and a client that registered without naming scopes is registered for all three. A scope Tabba does not know is dropped, not refused.

mcp:write covers every plan that only drafts, and commit. What takes mcp:issue is decided by what a plan leads to, not by the tool that made it: see Plan and commit.

Your role

You hold one of five roles in each company, and the role is per company: you can be an admin in one and an auditor in another. On this server the five act as three:

Your role in the companyActs asReadsWrites
Organisation admin, admin, bookkeeperAn adminThe whole companyEvery plan
AuditorAn auditorThe whole company, with every read tool an admin hasNothing: no plan tool and no commit
Standard userA standard userTheir own workTheir own registered work

Two gates decide the catalogue

The tool list is built for each request. A tool is registered only when the token holds its scope and your role admits it: a plan and commit are never an auditor's, and a tool that is an admin's is offered to an admin, or, when it only reads, to an auditor too. A tool that is not registered is not in tools/list and cannot be called, so there is nothing for an agent to attempt.

Token scopeAn admin is offeredAn auditor is offeredA standard user is offered
mcp:readAll 17 read toolsAll 17 read tools13 read tools: all but list_products, get_product, list_users and get_accruals
mcp:writeAlso commit, plan_register_work, plan_upsert_customer, plan_upsert_product, plan_create_invoice, plan_delete_invoice, plan_create_series, plan_cancel_series and plan_setup_from_contractNothing moreAlso plan_register_work and commit
mcp:issueAlso plan_issue_invoice, plan_issue_occurrence and plan_create_credit_noteNothing moreNothing more

Your role is not part of the token. It is read from Tabba on every request, so a role that changed is in force on the next call, and the catalogue of two people in one company differs. A client should hold a tool list against the connection, not against the server.

Whose work an answer is about

The scope of the token says what a connection may do. A second thing, the view, says whose work a read answers from. It is asked with an argument that is also called scope, on get_my_work and on the four lists whose rows can be related to a person: list_customers, list_invoices, list_series and list_credit_notes.

scopeOn a listOn get_my_work
mine (the default)The rows your own places reach: the invoices you hold a place on a line of, the recurring series you are on, and the customers those bill.Your own open lines.
relatedThe same rows as mine.Also the colleagues on the work you share.
allThe whole company. An admin's and an auditor's.Every person's open lines. An admin's and an auditor's.

mine is the default because a question asked in the first person is a question about your own work. get_my_work answers that question in one call: the lines you hold a place on, on invoices that still ask for something, grouped by customer or recurring series and ranked by what needs attention. Each line carries the line_id a work registration takes.

The scope enum a tool advertises is built from your role: mine and related for a standard user, all three for an admin and an auditor. A request for a view your role does not reach is refused with SCOPE_NOT_PERMITTED.

Every scoped answer opens with a line that says which view produced it. An admin and an auditor are also told how many rows it left out:

Scope "mine", reached through your own places: 12 customers in scope, all shown; 228 beyond this scope. Widen with scope:"all" for the whole company.

A standard user is told that there is no wider list, and is given no count of the rest:

Scope "mine", reached through your own places: 12 customers in scope, all shown. This is the widest list you may ask for; a company-wide answer needs an admin.

Read that line

A narrowed answer that does not say it was narrowed is worse than a broad one, because nothing signals what is missing. The line is written for the agent to relay. If yours summarises the rows and drops it, you are reading a narrowed answer with nothing to say so.

What a standard user reads

A standard user reads within their own work on every tool, not only on the lists:

  • A single record outside it is answered NOT_FOUND, whether it was named by id, by ext: or by name, and it is never offered as a candidate. A record that does not exist is answered in the same words, so the answer does not say which of the company's records exist.
  • get_recognition for an invoice within their work is theirs to read. For a whole customer it is answered ADMIN_REQUIRED.
  • get_user answers for themselves. Naming anyone else needs an admin.
  • The product catalog, the list of people and the month-end accrual entries are an admin's, and those tools are not in a standard user's list.

Roles and writes

A standard user's only plan tool is plan_register_work, and it registers their own work: naming a colleague is refused with REGISTER_FOR_OTHERS_FORBIDDEN. An admin may name a colleague in user. Every other plan is an admin's, and the role is asked again when the plan is committed. An auditor makes no plan, and a person made an auditor since their plan was made cannot commit it: commit is no longer offered to them, and where it is reached it answers READ_ONLY_ROLE. me is a valid reference wherever a person is named.

The company of a connection

A token carries one company. No tool takes a company as an argument, so nothing an agent passes can move a request to another one. If you work in several companies, that is one connection each.

Revoking access

Your connections are listed in Tabba under the heading Claude and other AI clients: on your profile settings, and for an admin also under Settings, Developers. Each row is one client acting as you on one company.

Revoke access ends every connection of yours at once. There is no control that ends a single one, and an admin cannot see or end a colleague's. After a revoke:

  • A client is refused at its next refresh. A client that is working at that moment can go on for up to 10 minutes, until its access token runs out.
  • To use a client again, connect it and approve it again.
  • The company's API keys are untouched. Those are the company's, and an admin's to revoke.

Removing the server from your client does not revoke anything in Tabba. Do both.

A connection also ends without you:

What happenedWhen it takes effect
Your role in the company changedThe next request runs with the new role.
You lost access to the company, or were removed from itThe next request is refused with 401.
The company was disconnected from its accounting systemThe next request is refused. The company's connections end, and its plans are deleted.
A spent refresh token was presented againThe connection ends for every holder.

Plan and commit

Why no Tabba tool writes on its own, what a plan and its token are, and what the server holds a commit to.

På denne side

Why the server insists on a personThe OAuth flowWhat the authorization server requires of a clientThe consent pageToken lifetimesThere is no path without a browserThe three scopesYour roleTwo gates decide the catalogueWhose work an answer is aboutWhat a standard user readsRoles and writesThe company of a connectionRevoking access

Denne side

Vis som MarkdownÅbn i Claude