Agent access and scoped tokens
An agent access token represents one user in one workspace. That association selects identity and row authority; it is not an independently configurable OAuth-style list of action scopes.
On every request, project auth reloads the token’s current user/workspace membership and builds abilities from its current roles. Removing the membership or changing its roles therefore changes later requests. The browser session’s active workspace is irrelevant to a bearer token.
Decide whether the call needs a token
Section titled “Decide whether the call needs a token”sapporta endpoints list and sapporta endpoints show read the application
contract at /api/openapi.json. The generated .env.development sets
SAPPORTA_OPENAPI_POLICY=public, so both work against a local development
server with no credential, and their output carries each route’s parameters,
request body, and response schemas.
The commands that read or write workspace data need a token: rows, tables,
sql, and api. The CLI takes the token from --api-token, then from
SAPPORTA_API_TOKEN, then from the project’s .env.agent, described below. A
deployment leaves SAPPORTA_OPENAPI_POLICY unset, which keeps the contract
behind sign-in, so endpoint discovery there needs a token as well.
Only a signed-in person creates a token. A freshly scaffolded project has no user until someone signs up, and the data commands are unavailable until then.
Create the token in the intended workspace
Section titled “Create the token in the intended workspace”An agent access token carries one user and workspace boundary into the CLI, so
the setup begins in the browser rather than the terminal. Open
/account/profile in an interactive browser session, switch to the workspace
the caller should represent, and create an agent access token. Give it a name
that identifies the caller and purpose, and add an expiry when the access is
temporary.
After creation, the dialog offers two ways to finish. If a coding agent will work from this checkout, open the agent at the project root and choose Copy setup prompt. The prompt already contains the application’s CLI base URL and the one-time token, so paste it into that trusted agent before closing the dialog. If you prefer to configure the environment yourself, choose Copy token and use the manual variables below. Neither route is automatic, and the plaintext token is not available again after the dialog closes.
Later token-list requests return metadata such as the token’s name, workspace, creation time, expiry, last use, and revocation time. The project database stores a hash of the secret rather than the raw token.
The read, create, and delete abilities on agent_access_token authorize
these interactive token-management actions. Bearer credentials may call ordinary
app APIs, but token-management endpoints reject them with 403 forbidden.
Hand the setup prompt to one trusted agent
Section titled “Hand the setup prompt to one trusted agent”The setup prompt has the agent read the project’s agent docs first, such as
AGENTS.md, for an authenticated Sapporta command and token location they
already give. When the docs give one, the agent sets up for that command and
leaves the docs alone. Otherwise it uses the CLI a workspace package already
provides: pnpm exec finds the sapporta bin only from inside the package that
declares sapporta or @sapporta/server, for example
pnpm --filter ./packages/api exec sapporta. It installs the CLI only when no
package provides it.
To supply SAPPORTA_API_URL and SAPPORTA_API_TOKEN, the agent reuses the
project’s existing directory environment tooling, such as mise, direnv, or a
dotenv runner, unless the project’s docs already name where the token goes. If
the project has no such tool, the fallback is a private, gitignored local
wrapper rather than a newly installed environment manager. When a project’s
agent docs name .env.agent as the token’s location, the agent writes the token
there instead, and the CLI reads it with no environment tooling.
When the project’s docs give no authenticated command, the agent adds the exact
invocation to AGENTS.md; it does not edit agent docs that already give one.
Either way it proves the connection with a read-only command. In a sandboxed
agent, that final check may require explicit network permission for the
application URL.
Against a local development server, endpoints list succeeds without a
credential and therefore reports reachability rather than the token. Read the
auth context to confirm the token itself:
pnpm exec sapporta api get '/api/auth-context'The generated setup prompt contains the raw credential by design, so treat the
whole prompt as a secret-bearing handoff. Paste it only into the intended agent
session. The resulting AGENTS.md instruction may name an environment tool or
wrapper, but it must never contain the token itself. Keep the credential out of
source files, committed configuration, screenshots, shell history, and later
task prompts.
Configure the CLI manually
Section titled “Configure the CLI manually”The CLI reads SAPPORTA_API_URL and SAPPORTA_API_TOKEN. The URL is the
deployment base before /api. The generated prompt includes the base URL
derived from the running application; the agent then places it in the project’s
private environment tooling. A local development server needs no URL, because
the CLI reads the project’s own SAPPORTA_API_PORT from .env.development.
export SAPPORTA_API_URL="https://tasks.example.com"read -s SAPPORTA_API_TOKENexport SAPPORTA_API_TOKEN
pnpm exec sapporta endpoints show "GET /api/reports/project-progress"pnpm exec sapporta rows list tasks --output jsonKeep the token in the project’s .env.agent
Section titled “Keep the token in the project’s .env.agent”A project can keep its agents’ token in .env.agent, a dotenv file in the
project root:
SAPPORTA_API_TOKEN=spat_<token-id>_<secret>The CLI reads the file when neither --api-token nor SAPPORTA_API_TOKEN sets
a token, from any directory inside the project. It reads only
SAPPORTA_API_TOKEN from the file, and a blank value counts as no token.
pnpm dev loads only .env.development, so the API process’s environment never
holds the agents’ token.
The file is a credential:
- The generated
.gitignoreignores.env*, so the file is not committed. - The generated
.dockerignoreignores.env*at the project root, so the file is not sent to a Docker build. A project scaffolded earlier keeps its own.dockerignore; replace its.envlines with.env*. - Make the file readable only by its owner, for example with
chmod 600 .env.agent.
The token in .env.agent is sent only to the project’s own API: the URL the CLI
reads from .env.development, or the built-in default. When --api-url or
SAPPORTA_API_URL chooses the URL, the CLI withholds the file’s token and an
authentication failure says so. To reach another deployment, supply that
deployment’s token with SAPPORTA_API_TOKEN.
If .env.agent exists but cannot be read, for example because another user owns
it, every CLI command stops with an error that names the file.
An application that provisions its agents’ token writes the file with
writeProjectApiToken(projectRoot, token) from @sapporta/server and reads it
back with readProjectApiToken(projectRoot). The writer creates an owner-only
file and renames it into place. It throws on a token that would not read back as
written, such as a blank one or one that contains # or quotes.
Call the API over HTTP
Section titled “Call the API over HTTP”If you call HTTP directly instead, send a normal bearer header:
GET /api/tables/tasks HTTP/1.1Host: tasks.example.comAuthorization: Bearer spat_<token-id>_<secret>The token-bound workspace supplies request authority. A query parameter, request
body, or workspace_id field cannot switch or widen it.
Discover, operate, and read back
Section titled “Discover, operate, and read back”Protected apps require credentials to retrieve their OpenAPI/endpoint inventory. The inventory proves which operation is mounted and describes its HTTP shape; it does not encode application abilities or row policy.
Choose the narrowest mounted operation that owns the change, make the request, and then read the affected record through an authoritative scoped operation. A successful transport response alone does not prove that the intended row changed.
Prove isolation and lifecycle
Section titled “Prove isolation and lifecycle”Use a small fixture with one record in Workspace A and a different record in Workspace B. A Workspace A token should see only Workspace A rows. Automation for Workspace B gets a separate token created while Workspace B is active.
Then exercise each recovery branch:
| Condition | Stable response | Next step |
|---|---|---|
| Revoked token | 401 token_revoked | Replace or remove the credential |
| Expired token | 401 token_expired | Create a new time-bounded token |
| Membership removed | 403 workspace_required | Restore membership or choose another workspace |
| Bearer calls token-management route | 403 forbidden | Use an interactive session |
| Revoke target belongs to another workspace/user | 404 not_found | Recheck the active browser workspace and token ID |
Status and code are the contract. Message wording is illustrative.
Revoking the credential invalidates the next API request. Removing a local
environment variable or deleting .env.agent does not revoke it:
unset SAPPORTA_API_TOKENrm .env.agent