API access & tokens
Everything the Tessule app does goes through its REST API, and that same API is open to your own scripts, integrations, and external applications. This guide covers the two kinds of credential you can mint — personal API tokens for scripts and server-to-server integrations, and OAuth clients for external applications that act on a user’s behalf — plus the per-workspace OpenAPI document that describes your tables as typed endpoints, and the rate limits that apply.
If you only remember one thing: there is no separate “API permission system”. Every credential resolves to a principal in the same roles model the app uses — an API token is a principal with member-level baseline permissions, and an OAuth token acts as the user who approved it, further capped by the scopes they approved. A credential can never do more than the permission model allows.
Who can do what
Section titled “Who can do what”- Create and revoke API tokens — requires the
tokens:manageaction, granted to admins and members by default. Readers deliberately cannot create tokens: a token acts with member-level (write-capable) permissions, so minting one is a write-capability grant. - Assign extra roles to an API token — requires
permissions:manage(admin-only), the same action that governs the role editor. - Approve an OAuth client — any signed-in user can approve access for themselves, to workspaces they are a member of. The resulting token is bounded by their own permissions.
- Enable AI record writes over MCP — an organization owner/admin decision; see AI assistants & MCP.
Three ways into the API
Section titled “Three ways into the API”| Credential | Best for | Acts as |
|---|---|---|
| Personal API token | Scripts, CI, backups, server-to-server integrations | Its own principal: member-level baseline, plus any roles assigned to the token |
| OAuth access token | External apps and AI clients acting for a user | The approving user, capped by the approved scopes |
| Your session | The app itself, and quick manual experiments | You |
All three are sent the same way, as a bearer token:
Authorization: Bearer <token>Personal API tokens
Section titled “Personal API tokens”Create and manage tokens from the user menu → API Tokens
(/settings/api-tokens in the app). Programmatically: GET /api-tokens
lists yours, POST /api-tokens creates one, and
DELETE /api-tokens/{tokenId} revokes one.
Creating a token
Section titled “Creating a token”A token has a name (e.g. “CI pipeline”, “backup script”) and an optional expiry date — leave it blank for a token that never expires.
The token list shows each token’s short ID, creation date, last used date (handy for spotting abandoned credentials), and expiry.
A token is scoped to one workspace
Section titled “A token is scoped to one workspace”Every token is bound — cryptographically, inside the token itself — to the workspace you were signed into when you created it. It can never read or write any other workspace. To integrate with two workspaces, create a token in each.
What a token is allowed to do
Section titled “What a token is allowed to do”This is the part that surprises people:
- A token does not inherit your role. Every API token starts from the member baseline — even when its creator is an admin. An admin’s token cannot manage members, roles, or apply schema plans.
- Tokens can be widened with roles. A token is a first-class principal in
the roles model: an admin can assign custom roles to it with
POST /roles/{roleId}/assignments(passing anapiTokenId), and its effective permissions become the union of the member baseline and every assigned role. There is currently no UI for assigning a role to a token — it is API-only — but token assignments do show up in the role’s assignment list in Workspace Settings → Roles. - Record visibility applies as you. On private tables, the token sees the records its creator owns or has been granted — “own” means owned by you.
Revocation, expiry, and lifecycle
Section titled “Revocation, expiry, and lifecycle”- Revoking is immediate. Every request verifies the token against its stored record; a revoked token fails on the next call. Anything using it stops working at once.
- Expiry is checked on every request — an expired token behaves exactly like a revoked one.
- Losing workspace access kills your tokens. When your workspace membership is removed (or you are removed from the organization), the API tokens you held in that workspace are deleted with it. They do not revive if you are re-added — you mint new ones.
A quick test
Section titled “A quick test”curl -H "Authorization: Bearer crm_..." \ https://<your-api-host>/api/tablesThe per-workspace OpenAPI spec
Section titled “The per-workspace OpenAPI spec”Your workspace publishes a live OpenAPI 3.0 document describing its own
API surface: download it from Workspace Settings → API Spec, or fetch it
with GET /workspace/openapi.
What’s in it:
- Your tables as typed endpoints. Every table you have defined becomes a
set of concrete list/get/create/update/patch/delete operations, with request
and response schemas generated from your actual fields — a picklist becomes
a string with its options, a date field a
format: datestring, and so on. Field descriptions carry through. - The shared resources — dashboards, charts, saved queries, views, imports, members, roles and the rest — spliced in from the canonical API description so they can never drift from what the server actually serves.
Because it is generated from the live schema, re-download it after schema changes. Import it into Postman, Insomnia, or any OpenAPI-compatible client or code generator, and you get typed access to your own data model.
The general (workspace-independent) API reference lives at the API reference.
OAuth clients for external applications
Section titled “OAuth clients for external applications”OAuth is how an external application — an AI client over MCP, or any other integration acting on a user’s behalf — gets access without ever seeing the user’s password. Tessule implements the standard authorization code flow with PKCE, plus dynamic client registration.
Registering a client
Section titled “Registering a client”POST /oauth/register implements RFC 7591 dynamic client registration —
an MCP or desktop client can register itself with a name and its redirect
URIs, no operator involvement. Because registration is unauthenticated, two
protections apply:
- Every self-registered redirect URI must be an http loopback address
(
127.0.0.1,[::1], orlocalhost). An authorization code can then only ever be delivered to the user’s own machine, even if an attacker registered the client. A hosted application with an https callback must be provisioned by an operator instead. - Re-registration never overwrites. Registering a
software_idthat already exists returns the stored client — name and grants stand, and only additional loopback redirect URIs are added (a native client takes a fresh ephemeral port each run). Knowing asoftware_idbuys an attacker nothing.
What the user sees: the consent screen
Section titled “What the user sees: the consent screen”GET /oauth/authorize sends the user’s browser to Tessule, where they sign
in (if needed) and land on an explicit consent screen — access is never
auto-approved. The screen shows:
- Which application is asking (its registered name).
- The requested scopes, each with a human-readable description sourced from the server’s own scope catalog — the screen can never overstate or understate what a scope allows. Unknown or unissued scopes are dropped before display, so what you approve is exactly what the token carries.
- A workspace picker — the grant is for one workspace. (When the client asked for a specific workspace via an RFC 8707 resource indicator — as workspace-scoped MCP connections do — the workspace is shown read-only and cannot be changed.)
- Token lifetimes, so the user knows how long access lasts.
Approve, and the browser delivers a one-time authorization code to the client’s redirect URI. Deny, and nothing is issued.
Scopes
Section titled “Scopes”Three scopes are issued today, designed around MCP/AI access:
| Scope | Consent label | Allows |
|---|---|---|
mcp:read |
View workspace structure and data | List tables, view schemas, read and search records |
mcp:query |
Read data and run queries | Everything above, plus read-only SQL queries |
mcp:write |
Create, update, and delete records, and change table structure | Everything above, plus record writes and additive schema changes |
Scopes are ceilings, never grants. The token’s effective permissions are
the intersection of the approved scopes with the user’s own role-based
permissions. A reader who approves mcp:write still cannot write — their
role never had the action. And no scope covers member management, permissions,
or dashboard administration, whatever the user’s own role allows in the app.
Tokens and refresh
Section titled “Tokens and refresh”POST /oauth/token exchanges the code (with PKCE verification) for an
access token valid for 30 minutes and a refresh token valid for 14
days. Codes are single-use and expire after 10 minutes.
The refresh grant re-verifies workspace membership and re-derives the
user’s role from the database on every refresh — a user who has been
removed from the workspace gets invalid_grant, and a role change propagates
within one access-token lifetime. Revoking someone’s workspace access
therefore cuts off their connected apps within 30 minutes at most.
Rate limits and quotas
Section titled “Rate limits and quotas”Two layers exist, and only one of them is something you’ll normally meet:
-
Durable daily quotas on the expensive read endpoints, counted per workspace per UTC day and returned as
429when exhausted:POST /query(ad-hoc SQL) — default ceiling 20,000 executions/day. Asynchronous CSV exports count against the same quota, so exporting can’t buy extra executions.GET /search(workspace-wide search) — default ceiling 50,000/day.
These ceilings are entitlements: plans and per-organization overrides can raise them, workspace admins can tighten them per resource, and the value actually enforced is the lowest tier in force. Admins can see every counter, its effective ceiling, which tier set it, and when the window resets in the workspace’s limits view (
GET /workspace/limits/usage). -
Protective limits (per-IP flood protection at the edge, and tight brakes on authentication endpoints). Well-behaved API clients won’t encounter these; if you do get a
429from an auth endpoint, back off and retry later.
Treat any 429 as retryable-later, and prefer batching over polling loops.
Inbound webhooks
Section titled “Inbound webhooks”Flows can start from an inbound webhook. In Automations, create a flow with When a webhook URL is called, then choose either URL token only or URL token + API key. Publishing mints an unguessable URL and reveals it exactly once, so copy it immediately. The API-key option additionally requires a workspace API token as a bearer credential.
Webhook flows can use the payload in later conditions and steps, including record writes, email, HTTP requests, and sandboxed code. Keep the hook URL secret: with URL token only, possession of that URL is sufficient to start a run.
API and MCP traffic is not a side door: requests made with tokens pass through the same audit logging as the app, and mutations show up in the workspace audit trail attributed to the token’s owner.
Q: My token gets 401 Unauthorized — why?
In order of likelihood: the token was revoked; it expired; or your membership
in the token’s workspace ended (which deletes the token). Also check you’re
sending Authorization: Bearer crm_... — the whole value, including the
crm_ prefix.
Q: I’m an admin, but my token can’t manage members or apply schema plans.
Working as designed: tokens start from the member baseline regardless of
who created them. If a token genuinely needs more, an admin can assign it a
custom role granting exactly the extra actions it needs (API-only today, via
the role-assignment endpoint with an apiTokenId).
Q: Can I create a read-only token?
Not directly — the member baseline includes write actions, and there is no
way to narrow a token below it today (roles only ever add). The one genuinely
read-only credential is an OAuth grant approved with only the mcp:read
scope: scopes cap permissions, so such a token cannot write no matter whose
it is.
Q: One token for all my workspaces?
No. Tokens are hard-bound to one workspace; create one per workspace.
Q: Where do I find the API’s shapes and endpoints?
The API reference for the general API, and
GET /workspace/openapi (or Workspace Settings → API Spec) for a
document that includes your tables as typed endpoints.