Skip to main content
Preview. There are two ways to authenticate against POST /mcp, and which one you use is decided by the client, not by you. That table is a guide, not a gate. Nothing in the server asks which client is calling: a client that sends a bearer token is served, a client that discovers the OAuth flow is served, and one nobody has heard of is served the same way. Any MCP client has the connection facts on their own. Both arrive at the same place: a request pinned to one workspace, carrying a set of scopes, checked by the same code that guards /api/v2/. There is no third kind of principal and no MCP-only permission model.

Bearer

Send the key as an ordinary bearer token. Nothing else is required.
Keys are minted in the dashboard under Developers → API keys, are shown once, store only a hash, carry an optional expiry, record their last use, and can be revoked individually. They are the same credential /api/v2/ takes — see Authentication and scopes for the full registry.
Backend and local-editor use only. A tgcc_ key is a workspace credential: it identifies the workspace, and its scopes are the whole of what limits the damage if it leaks. Never put one in a browser, a mobile client, or a committed config file.

OAuth 2.1

The chat connector UIs will not take a pasted token; they run a flow. The server is both the resource server and the authorization server, and advertises everything a client needs to find its way: Three properties are not optional and are worth knowing because they are what make a leaked token useless somewhere else:
Not plain, and not absent. An authorization code without a matching verifier is refused.
The resource indicator you consented for is baked into the token. The server checks that the audience is us and rejects anything else — an MCP server must not accept or pass on a token minted for somebody else, and this one does not.
So a client can tell which authorization server actually answered, rather than assuming.

The scope split

This is the part that decides what a model can do with your workspace, and it has two levels.
1

mcp:connect reaches the transport

Without it, POST /mcp is 403. With it, and nothing else, a client completes the handshake and lists every tool.
2

Each tool demands its own scope

Checked per call, inside the dispatcher, against the same registry /api/v2/ uses. agents:read to list agents, documents:create to upload one, extractions:create to start an extraction.
mcp:connect alone lists the tools and calls none of them. That is the design, not a misconfiguration. Connecting is one decision — an endpoint is pasted into somebody else’s product, and from then on a model chooses when to call it. What that model may then do is a second decision, and a key minted for a nightly export should not become an agent’s hands because a URL was shared.
Scopes never imply one another. There is no hierarchy and no wildcard: a key that may read agents cannot run one, and a key that may upload a document cannot extract from it.

What a failure looks like

Auth failures are HTTP statuses; tool failures are not. The line is drawn where the tool starts.
A 403 names every scope the operation needs, in one challenge. Emitting them one at a time would force a round trip per scope and teach integrators to ask for far more than they need.
Anything that happens inside a tool — out of credit, not found, a bad argument — comes back as HTTP 200 with a tool result marked isError: true and readable text. A JSON-RPC error member is reserved for a request that never reached a tool at all. The reason is the same one that governs the platform as a client: a tool that cannot answer must return something the model can say out loud.

Credits and the billing gate

What a call costs, and what an empty wallet returns.