HeyPuter/puter · error · HttpError
app_or_api_token_required
app_or_api_token_required
Error message
This API cannot be called with an account session token. Use an app or worker token, or create an API token from the dashboard (Account → API Token).
What it means
Raised by the `noUserSession` / `assertNotUserSession` gate: the actor is a bare browser session token (no app, no access token, and the session is not a `kind='worker'` session). Routes opt into this gate to forbid the 'root' session credential from doubling as an API/AI credential, pushing callers toward a delegated, revocable token instead.
Source
Thrown at src/backend/core/http/middleware/gates.ts:177
* This gate only rejects the bare-session shape. Which delegated credentials
* are acceptable is decided by the gates it composes with (`requireUserActor`
*
* - `allowFullAccessToken` to also keep apps out, `requireNonAccessTokenGate` for
* scoped tokens, etc.).
*/
export const assertNotUserSession = (
actor: Pick<Actor, 'app' | 'accessToken' | 'session'> | null | undefined,
): void => {
if (!actor) return; // anonymous requests are the auth gate's problem
if (actor.app || actor.accessToken) return;
// User-scoped workers (deployed with no app binding) authenticate with
// a session-TYPE token whose session row is `kind='worker'` — a managed,
// revocable deployment credential, not a browser sign-in. Workers are
// never treated as root tokens: this gate is an annoyance for
// sign-up-and-scrape abuse, and someone who deploys a worker to reach
// an API has already left that path.
if (actor.session?.kind === 'worker') return;
throw new HttpError(
403,
'This API cannot be called with an account session token. ' +
'Use an app or worker token, or create an API token from the ' +
'dashboard (Account → API Token).',
{ legacyCode: 'app_or_api_token_required' },
);
};
/** Route-option form of {@link assertNotUserSession} (`noUserSession: true`). */
export const noUserSessionGate = (): RequestHandler => {
return (req, _res, next) => {
const actor = req.actor;
if (!actor) {
next(rejectAuth(req));
return;
}
try {
assertNotUserSession(actor);View on GitHub (pinned to 908ec23eda)
Solutions
- Create an API token from the dashboard (Account → API Token) and send it as the auth credential.
- Or register an app and call the route through its app token.
- Or deploy a worker and use its worker session token (kind='worker'), which is explicitly allowed.
- Stop reusing the browser session token for API/AI calls.
Example fix
// before
fetch('/drivers/call', { headers: { Cookie: sessionCookie } });
// after
fetch('/drivers/call', { headers: { Authorization: `Bearer ${apiToken}` } }); Defensive patterns
Strategy: validation
Validate before calling
// Before calling a noUserSession route, ensure you're using a delegated credential:
if (!apiToken && !appToken && !workerToken) {
throw new Error('Route requires an app/API/worker token, not a browser session.');
} Prevention
- Mint an API token from the dashboard for scripts and servers.
- Never copy the browser session cookie into a server-side call.
- Tag browser-session-only vs. delegated-token routes in your client wrappers.
When it happens
Trigger: Calling an AI/API route (registered with `noUserSession: true`) while authenticated only via a browser session cookie or a token copied from `/login` — no app token, no access token, no worker session.
Common situations: A dev script that grabbed the session cookie to call the AI API; a puter.js token reused server-side; trying to drive an API endpoint with the sign-in token instead of minting an API token.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/5ec7bb039c4baef8.
Report an issue: GitHub.