Access tokens
Shipped
Access tokens let scripts, the CLI, and tools reach your Turbobase instance without a browser session. Administrators manage them at /settings/tokens. A token belongs to a Turbobase account, so that page is not offered to administrators who opened the app from inside NetSuite.
Two kinds of token
Section titled “Two kinds of token”| Kind | Prefix | What it’s for | Owner |
|---|---|---|---|
| Personal access token (PAT) | tb_pat_ |
A per-admin bearer for the CLI, the Extensions API, remote authoring, and — when you tick for it — reading and writing your data from a program | The admin who minted it |
| AI / catalog token | tb_cat_ |
A per-instance, schema-read bearer for the catalog and types/docs surfaces | The instance (no owning user) |
A PAT is minted by an administrator and tied to that admin. A catalog token is instance-scoped with no owning user — it carries read access to your schema catalog only.
Scopes
Section titled “Scopes”Each token carries a scope set, and routes check the scope they require against it. Tick only what the token needs — every scope is revocable on its own by minting a replacement without it.
Build & deploy
| Scope | What it grants |
|---|---|
extensions:deploy |
Deploy apps, hooks, conduits and processes, author custom tables from a manifest, toggle instrumentation, and file draft knowledge-base pages. When the token’s owner is an administrator, also see and cancel process runs and start one (turbobase processes invoke) — the run writes as that owner. A PAT’s default scope. |
catalog:read |
Read the schema catalog — tables, columns, types — so one PAT can drive both the Extensions API and codegen. Schema only, never your data. |
mcp:authoring |
Connect an MCP client to the authoring endpoint (below); lets it scaffold, test and deploy hooks, conduits and processes. |
analyzer:run |
Run the Customization Analyzer — extract, explain, plan improvements — drive managed-bundle operations, and read solutions. |
releases:write |
Push, pull, deploy, roll back and inspect releases. |
solutions:write |
Write a realized solution back to the instance. Reading one uses analyzer:run instead. |
Publish — these change what other people and other agents are served, so neither is granted by default:
| Scope | What it grants |
|---|---|
skills:publish |
List agent skills and their releases, and publish, unpublish, roll back and delete them — changing what every AI answer on the instance is told. Every publish records who did it. Reads need it too: skills list and skills releases are refused without it. |
docs:publish |
List knowledge-base pages and their release versions, and publish, unpublish, roll back and delete them — changing what every member reads and what AI answers are grounded on. Reads need it too: kb list and kb versions are refused without it. Filing drafts with kb push does not need it. |
Your data — four scopes reach the rows themselves, and they are the only ones that do. They come in two pairs, one per door, because reading your business’s data and changing it are two decisions and so are the two ways in:
| Scope | What it grants |
|---|---|
mcp:data |
Read your instance’s rows through the data MCP — list_records, get_record, search_fields — under the row-level permissions of the administrator who minted the token. A token carrying this permission and no other cannot write, whatever the instance’s MCP write setting says, and it reaches nothing else: the rest of the product still needs a sign-in. Never granted by default; tick it deliberately. |
mcp:write |
Create, change and delete those same rows, through the data MCP’s write_record and delete_record — the same business rules, guardrails, permissions and onward sync a change made in the app goes through. Granted alongside mcp:data, never instead of it: a request for it on its own is refused at mint and at edit. The instance’s mcp.server.writes_enabled setting still has to be on, and an administrator who has switched MCP writes off keeps that answer whatever any token carries. Never granted by default. |
rest:read |
Read your instance’s rows over the ordinary web interface, at POST /v1/batch — the same batched read an app’s own client makes, under the row-level permissions of the administrator who minted the token. This is the permission for a program that is not an AI client: a label printer, a warehouse script, an overnight integration. Never granted by default. |
rest:write |
Create, change and delete those same rows over that interface, at POST /v1/data/<recordType> and the routes beside it — through the same write boundary your apps call, so the same business rules, guardrails, permissions and onward sync apply. Granted alongside rest:read, never instead of it. Never granted by default. |
Neither pair implies the other. Ticking the MCP pair does not open the web interface to the token, and ticking the REST pair does not connect it to the data MCP. An administrator who armed an AI client has not thereby armed an unattended script.
A catalog token carries catalog:read only — you can’t smuggle a deploy scope onto it, and all four data scopes are refused there too: a catalog token has no owning user, so there is no identity to resolve rows under, or to write as.
What the data scopes reach, and what they cannot
Section titled “What the data scopes reach, and what they cannot”Ticking a read permission on the mint dialog is the decision that hands a standing credential access to your business’s data, and the write permission beside it is the one that lets it change that data. Both are worth being precise about. It is also the tick that sets the clock: a token minted with either read permission defaults to 7 days rather than 90, and the lifetime field on the dialog changes as you tick, so the number you see is the number the token gets.
This section is about the MCP pair. What the REST permissions reach is the same account of the other pair.
What the read permission can do. Present the token as a bearer at the data MCP endpoint and read rows, as the administrator who created the token. The same row-level permissions apply as when that person reads in the browser — the token is not a lesser identity, and it is not a greater one either.
What the read permission cannot do. Change anything. With only mcp:data ticked, the MCP’s mutation tools are not advertised to the token, and a call that names one anyway is refused before the instance’s write setting is even read. Turning writes on for your instance does not change that; ticking the second box is what does. The token also reaches no other data surface — /rest/v1 and the rest of the product still require a browser session.
What the write permission adds. write_record and delete_record, and nothing else. They are the same two tools a person’s own MCP connection gets, calling the same write boundary your apps call — so a write from a token runs your before_save hooks, your write guardrails, the writer’s own write:<record> capability and row-level permissions, and is queued onward to the connected system exactly as a change made in the app is. A token can never exceed the person who minted it: if that administrator could not make the change in the browser, neither can the token.
Two things still have to be true for a write to land. The token has to carry mcp:write, and mcp.server.writes_enabled has to be on for the instance. They are separate answers on purpose and are refused separately, so a refusal tells you which one to fix.
It is two things for the other credential too, and the second one is different. A person’s own connected client is not governed by a token’s permissions: it carries its own permission to change records, granted by the person who connected it and revocable by them from Account → Connected apps. So a token’s write authority is an administrator’s decision on the token’s row, a connection’s is its owner’s decision on their account page, and the instance setting sits above both.
Why it is safe to mint one now, when it was not before. Until 1.3.x there was a scope called data:read, and it let a PAT be traded at POST /auth/pat-exchange for a session. That session was the token owner’s ordinary one, so for an admin owner it carried write as well — a long-lived token sitting in a project file amounted to standing write access to production data. Both the scope and the endpoint were removed rather than renamed, and neither is coming back.
mcp:data is a different shape, on purpose. A session is still minted for the token’s owner, because that is what makes row-level permissions fire correctly — but it is minted inside each request, is never returned to the caller or set as a cookie, expires in five minutes, and only ever reaches the MCP’s own bounded tool surface. That is still true of mcp:write: what the second permission widens is which of those tools the token may call, not what the credential is. Nothing hands your client a session it can keep.
Treat a token carrying either like a password. Keep it where you keep one, and revoke it the moment it is no longer needed. A token that reaches data is marked as such in the list at /settings/tokens — and one that can change data is marked differently from one that can only read it — so you can pick them out without opening any.
Reading rows without a token is unchanged and is still the right default for a person:
- In an app — the signed-in browser session, through PostgREST as usual.
- From an editor, a script or an agent — connect the data MCP as yourself. You sign in once over OAuth and your client gets the same read tools, under your own identity. Connect it from Integrations settings; revoke it from the Connected apps tab of your account settings.
Searching your customizations needs no token either. catalog:read below mints a token for the authoring endpoint, but an administrator connected to the data MCP gets search_catalog and explain_object over that OAuth sign-in — nothing to mint, nothing to store in a project file. Mint catalog:read when a tool or a script has to reach the catalog on its own, without a person signed in.
What the REST permissions reach, and what they cannot
Section titled “What the REST permissions reach, and what they cannot”rest:read and rest:write are for a program you run that is not an AI client — a label printer claiming jobs off a queue, a warehouse script, an overnight integration. Before they existed the only door an access token opened was the data MCP, so an unattended program had to speak that protocol or ask a person to sign in.
What the read permission can do. Present the token as a bearer at POST /v1/batch and read rows, as the administrator who created the token. That endpoint is the batched read an app’s own client already makes: you send one or more PostgREST query strings and get their rows back, with counts, limits, offsets and embeds — so a program gets the same read grammar an app has, and exactly the rows that administrator would see in the browser.
curl -s https://acme.turbobase.app/v1/batch \ -H "Authorization: Bearer $TURBOBASE_TOKEN" \ -H 'content-type: application/json' \ -d '{"queries":["print_job?select=external_id,status&status=eq.queued&limit=10"]}'It also fetches a file’s bytes, at GET /v1/data/<recordType>/<externalId>/content — the one route under /v1/data the read permission opens rather than the write one, because reading a file is a read of a row the program can already see. The same capability and row-level permissions apply as in the browser, so a container the token’s owner cannot read stays unreadable. Name the file’s connection with ?connection=<connection_id>, which is the connection_id on its row: without it the owner’s default connection is assumed, and on an instance with two containers of the same kind connected that is the wrong one. Range: bytes=… is answered 206, and the response carries the file’s etag for an If-None-Match revisit.
A program gets the 401 envelope, never a redirect. An image, a video or a PDF asked for by a browser is served inline, and a browser with no session is sent to sign in and back to the file. That branch turns on the Accept header asking for HTML, so a program sending application/json, */* or an image type gets {"error":"session_missing","returnTo":…} with a 401 exactly as before. A refused token is never a sign-in page. Add ?download=1 if you want the attachment disposition whatever the type.
What the read permission cannot do. Change anything, and reach /rest/v1 directly. That prefix is served straight by PostgREST, which knows nothing of access tokens and takes a browser session only — so a program reads through the batch endpoint above instead. The token also reaches nothing outside the data surface: the console’s own screens, every credential and session route, and everything that already has a permission of its own are all closed to it.
What the write permission adds. Writing at POST /v1/data/<recordType>, the file upload beside it, and DELETE /v1/data/<recordType>. These are the same routes your apps write through, so a write from a token runs your before_save hooks, your write guardrails, the writer’s own write:<record> capability and row-level permissions, honours expectedSyncVersion so two programs cannot both claim the same row, and is queued onward to the connected system exactly as a change made in the app is. A token can never exceed the person who minted it.
The session a token gets is one it cannot keep. A request carrying either permission is turned into a five-minute session for the token’s owner inside that request — which is what makes row-level permissions fire correctly — and that session is never returned to you, never set as a cookie, and gone when the response is written. The same shape the data MCP has used since 1.3.x, and deliberately not the retired POST /auth/pat-exchange, which handed the caller a session it could hold.
Revocation takes effect within thirty seconds. Revoking a token, narrowing its permissions, demoting its owner or suspending them all apply with nothing to restart. Most requests pick the change up at once, and thirty seconds is the longest a withdrawn permission can still be honoured on these two routes — the token’s own row is re-read at least that often, which is what keeps a program polling every few seconds from paying for a full sign-in on every call.
A request refused for the permission is a 403 naming what is missing ({"error":"insufficient_scope","reason":"missing_scope","required_scope":"rest:write"}), so a program can tell “tick another box” apart from “this credential is not accepted here”, which is a 401.
The authoring MCP endpoint
Section titled “The authoring MCP endpoint”A PAT carrying mcp:authoring is the credential for the remote authoring MCP server at /mcp/authoring — the HTTP face of the same tools turbobase mcp serves locally over stdio. The client sends the PAT as a bearer token on every request; requests are rate-limited per token. The scope gate is strict in both directions: a deploy-only PAT (extensions:deploy without mcp:authoring) is rejected here, and a deploy over MCP still requires the explicit approve: true confirmation — the scope alone never deploys anything.
Two budgets apply, and the first one does not know who you are. Before the token is read at all, the endpoint allows 60 requests a minute from one address — the same number as the per-token budget, so a single client pacing itself against its token allowance never meets it. It is there to bound an unauthenticated caller, which costs the instance a lookup per attempt. The per-token budget then applies on top, at 60 a minute. Either refusal is a 429; the per-address one carries the rate limiter’s own body ({"statusCode":429,"error":"Too Many Requests"}) and the per-token one carries {"error":"rate_limited","reason":"too_many_requests"}, so you can tell which fired. If several clients share one outbound address — a CI runner, an office NAT, a container host — they share the per-address budget, which is what to pace against. And on an instance whose network has not been configured to pass the caller’s address through, every client shares one budget whatever address it calls from, so if you run more than one authoring client against the same instance, pace them against 60 a minute between them rather than 60 each. Your per-token budget is unaffected either way.
Authorization stays live
Section titled “Authorization stays live”A token does not freeze its permissions at mint time. For a PAT, authorization is re-derived from the owning user’s live role on every request: the instant an administrator is demoted, their PATs stop being able to deploy — no revocation needed. Suspending the owning user denies the PAT too. On the two data routes the role, the suspension and the owner’s own permissions are re-read at least every thirty seconds rather than on every single request, which is the bound described above.
Stored as a hash, shown once
Section titled “Stored as a hash, shown once”Only the token’s secret matters, and it’s never stored. Turbobase keeps just a SHA-256 hash of each token; the plaintext is returned exactly once at mint time and never again. Copy it then — if you lose it, mint a new one. Verification fails closed on every failure mode (missing, malformed, unknown, expired, revoked, suspended, or a transient database error), so a token is never accepted by accident.
Expiry, permission changes, rotation, revocation and deletion
Section titled “Expiry, permission changes, rotation, revocation and deletion”Five things can happen to a token, and /settings/tokens offers each of them on the token’s own row.
-
Expiry. A PAT defaults to a 90-day lifetime — unless you tick
mcp:dataorrest:readwhen you mint it, which defaults it to 7 days instead, because a credential that can read your business’s data should not also be the longest-lived one you hold. The write permission beside each inherits the same 7 days, since it is only ever granted alongside its read partner. Either way it is a default and not a cap: type any lifetime from 1 to 365 days in the mint dialog and that is what you get. Catalog tokens don’t expire — you rotate them instead. -
Permission changes. Edit changes what a live token is allowed to do without changing its secret, so nothing has to be re-pasted wherever the token is stored. The set you save replaces the old one in full — anything you untick is dropped — and it takes effect on the token’s next request: narrowing a token is a partial revocation with nothing to restart, inside the thirty-second bound the data routes carry. Every change is recorded in the audit log with both the old permissions and the new ones, and an edit that would leave no permissions at all is refused — revoke the token instead.
Adding
mcp:dataorrest:readhere does not change when the token expires. The shorter default belongs to the mint: a token you already hold keeps the lifetime it was given, whatever you tick. Editing permissions changes what the token may do and nothing else. If you want an existing long-lived token on the shorter window instead, mint a replacement with the permission and revoke the old one. -
Rotation. Rotate issues a fresh secret for the same token and retires the old one in one step: a replacement carrying the same name, type, permissions and lifetime in days is created, its plaintext is shown once, and the original is revoked. The replacement gets a full new window — a token rotated the day before it lapsed is not born expiring. The new secret is minted before the old one is retired, so if the replacement cannot be created nothing is revoked and the token you have keeps working.
-
Revocation. An administrator can revoke any live token at any time. Revocation is a soft flip — the row stays, so the list still accounts for the token — and is idempotent and immediate, inside the same thirty-second bound on the data routes.
-
Deletion. Once a token is revoked or expired, Delete removes its row from the list for good. A live token has no Delete: revoke it first, so deleting is never the act that takes a working credential away.
Deleting a token does not erase its history. The audit log is a separate record with no link back to the token’s row, so long after that row is gone an access review can still answer who changed the token’s permissions, who rotated it, who revoked it and who deleted it — and the deletion carries the token’s name, type and permissions with it, so the trail says which token went rather than only that one did. The mint is the one act that is not on that record: creating a token here writes no audit row, so a token’s trail starts at the first thing done to it afterwards.
The two ways to reach the data MCP
Section titled “The two ways to reach the data MCP”The inbound data MCP server takes two credentials, and which one to use depends on who is at the other end.
- A person connects their own client over OAuth from Integrations settings and signs in. Reads run as that person. The resulting connections are listed on the Connected apps tab of their own account settings, where each member revokes their own — they are personal grants, so nobody else manages them for them.
- A script, an agent or anything unattended presents a PAT carrying
mcp:data, plusmcp:writeif it has to change anything. Reads and writes run as the administrator who minted the token. This is the credential to reach for when there is nobody to complete a sign-in, or when a session expiring part-way through a job would strand the work.
Both are bounded by the same row-level permissions, and for either of them changing data needs the instance’s MCP write setting on. Beyond that the second answer differs by credential: a token needs mcp:write on its own row, granted by an administrator, and a person’s connection needs the write permission its owner gave it on the approval screen or from Connected apps. Neither is granted by default.
Related
Section titled “Related”- Auth levels — every way to authenticate to your instance
- Users and permissions — the roles a PAT’s authorization re-derives from