HeyPuter/puter · error · HttpError
token_missing
token_missing
Error message
Missing `token`
What it means
Thrown by the WISP relay-token verify endpoint (POST /wisp/relay-token/verify on the api.* subdomain) when req.body.token is falsy or not a string. It is the input gate before the token is JWT-verified by TokenService. Returns 400 with legacyCode token_missing.
Source
Thrown at src/backend/controllers/wisp/WispController.ts:110
} else {
const token = this.services.token.sign(
'wisp',
{
$: 'token:wisp',
$v: '0.0.0',
guest: true,
},
{ expiresIn: '1d' },
);
res.json({ token, server: wispCfg.server ?? null });
}
};
/** POST /wisp/relay-token/verify — verify a relay token and apply policy. */
#verify = async (req: Request, res: Response): Promise<void> => {
const bodyToken = req.body?.token;
if (!bodyToken || typeof bodyToken !== 'string') {
throw new HttpError(400, 'Missing `token`', {
legacyCode: 'token_missing',
});
}
let decoded: Record<string, unknown>;
try {
decoded = this.services.token.verify<Record<string, unknown>>(
'wisp',
bodyToken,
);
if (decoded.$ !== 'token:wisp')
throw new HttpError(403, 'wrong token type', {
legacyCode: 'invalid_token',
});
} catch {
throw new HttpError(403, 'Forbidden', {
legacyCode: 'invalid_token',
});View on GitHub (pinned to 908ec23eda)
Solutions
- Send a JSON body { "token": "<jwt-string>" } with Content-Type: application/json.
- Obtain the token first from POST /wisp/relay-token/create and pass its response.token into verify.
- Confirm the token variable is defined and is a string before the request (guard against undefined).
- Verify the HTTP client is sending the body as JSON, not form-encoded.
Example fix
// before
await fetch('/wisp/relay-token/verify', {
method: 'POST',
body: JSON.stringify({}), // token omitted
});
// after
const { token } = await (await fetch('/wisp/relay-token/create', { method: 'POST' })).json();
await fetch('/wisp/relay-token/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
}); Defensive patterns
Strategy: validation
Validate before calling
function validTokenBody(body) {
return body != null && typeof body.token === 'string' && body.token.length > 0;
}
if (!validTokenBody(req.body)) {
throw new Error('Request body must include a string `token`');
} Type guard
function isTokenBody(body) {
return body != null && typeof body === 'object' && typeof body.token === 'string';
} Try / catch
try {
await fetch('/wisp/relay-token/verify', { method: 'POST', body: JSON.stringify({ token }) });
} catch (e) {
if (e.status === 400 && /token/.test(e.message)) {
const created = await fetch('/wisp/relay-token/create', { method: 'POST' });
token = (await created.json()).token;
await fetch('/wisp/relay-token/verify', { method: 'POST', body: JSON.stringify({ token }) });
} else throw e;
} Prevention
- Always mint a token from /wisp/relay-token/create first and pass response.token into verify.
- Send Content-Type: application/json so Express parses the body into req.body.
- Guard against undefined token variables before constructing the request.
- Do not send the token as a query param or header — it must be in the JSON body.
When it happens
Trigger: A POST to /wisp/relay-token/verify whose JSON body omits the token field, sets it to null/empty, sends a non-string type (number/object), or whose body parser failed to populate req.body (wrong Content-Type).
Common situations: Client forgot to include the token in the JSON body; sent it as a query param or header instead of the body; wrong Content-Type (not application/json) so Express did not parse the body; token variable undefined due to a prior step failing; rate limit (300/min per IP) masking the real cause.
Related errors
- token_missing
- invalid_token
- ${key} must be an integer between ${min} and ${max}
- App owner not found
- Permission denied
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/c138177ee571374d.
Report an issue: GitHub.