Skip to content

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.


  • Create and revoke API tokens — requires the tokens:manage action, 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.

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>

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.

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.

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.

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 an apiTokenId), 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.
  • 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.
Terminal window
curl -H "Authorization: Bearer crm_..." \
https://<your-api-host>/api/tables

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: date string, 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 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.

External app User's browser Tessule 1. Register client POST /oauth/register 2. Start authorization GET /oauth/authorize 3. Sign in + consent: scopes, workspace, Approve or Deny 4. Code delivered to the app's redirect URI 5. Exchange code + PKCE POST /oauth/token 6. Access + refresh token access 30 min, refresh 14 d 7. Call the API / MCP
The OAuth authorization-code flow. The user's password never reaches the external app; the app only ever holds tokens the user explicitly approved.

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], or localhost). 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_id that 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 a software_id buys an attacker nothing.

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.

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.

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.


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 429 when 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 429 from an auth endpoint, back off and retry later.

Treat any 429 as retryable-later, and prefer batching over polling loops.


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.

Open the app