heygen-com/hyperframes · error · FigmaClientError
BAD_TOKEN
BAD_TOKEN
Error message
figma rejected the token (401) — it is expired or revoked. Re-mint at figma.com/settings → Security, then update FIGMA_TOKEN.
What it means
Thrown by throwForStatus (code BAD_TOKEN, status 401) when figma's REST API returns HTTP 401 on a GET. A 401 means the X-Figma-Token was syntactically a token but figma no longer accepts it — it has expired or been revoked. The error points the user back to the PAT settings page to re-mint. (Note: figma also returns 403 with body 'Invalid token' for some bad-token cases on file endpoints — those are handled separately by forbiddenError at client.ts:237 and surface the same BAD_TOKEN code.)
Source
Thrown at packages/core/src/figma/client.ts:273
403,
opts.endpoint,
);
const scopeLine = opts.scopeHint
? `This endpoint needs the "${opts.scopeHint}" scope — add it at figma.com/settings → Security → Personal access tokens.`
: "The token is missing a read scope, or your account can't view this file. Check File content: Read-only + File metadata: Read-only at figma.com/settings → Security.";
return new FigmaClientError(
"FORBIDDEN",
`figma denied access (403). ${scopeLine} Also confirm the file is visible to your account.`,
403,
opts.endpoint,
);
}
/** Throw the typed error for a non-ok response (no-op when res.ok). */
async function throwForStatus(res: Response, path: string, opts: GetOptions): Promise<void> {
if (res.ok) return;
if (res.status === 401)
throw new FigmaClientError(
"BAD_TOKEN",
"figma rejected the token (401) — it is expired or revoked. Re-mint at figma.com/settings → Security, then update FIGMA_TOKEN.",
401,
opts.endpoint,
);
if (res.status === 403) throw forbiddenError(await readFigmaErrorMessage(res), opts);
if (res.status === 429)
throw new FigmaClientError(
"RATE_LIMITED",
`figma rate limit hit (429) and still limited after ${maxRetries} retries — wait a minute and re-run, or import fewer nodes per call.`,
429,
opts.endpoint,
);
throw new FigmaClientError(
"HTTP_ERROR",
`figma request failed: HTTP ${res.status} ${path}`,
res.status,
opts.endpoint,View on GitHub (pinned to c2996c8626)
Solutions
- Open figma.com/settings -> Security -> Personal access tokens and generate a new token.
- Update FIGMA_TOKEN in your shell (export FIGMA_TOKEN=...) and in the project .env / CI secret.
- Re-run the command that failed — the new token takes effect on the next createFigmaClient call.
- If it persists, confirm the token's scopes include File content: Read-only.
Example fix
// before — stale token in env export FIGMA_TOKEN="figd_old_revoked_value" // after — freshly minted token export FIGMA_TOKEN="figd_newly_generated_value"
Defensive patterns
Strategy: try-catch
Type guard
import { FigmaClientError } from '.../figma/client';
export function isBadToken(err: unknown): err is FigmaClientError {
return err instanceof FigmaClientError && (err.code === 'BAD_TOKEN');
} Try / catch
try {
await client.nodeTree(ref);
} catch (err) {
if (isBadToken(err)) {
// prompt the user to re-mint the PAT, update FIGMA_TOKEN, retry once
} else throw err;
} Prevention
- Treat figma PATs as rotating credentials — set a calendar reminder before expiry.
- When a token is regenerated, update every environment (shell, .env, CI) in lockstep.
- Wrap figma calls in a helper that catches BAD_TOKEN and surfaces a re-setup prompt rather than a raw stack trace.
When it happens
Trigger: Any client call (renderNode, nodeTree, styles, variables, fileVersion, imageFills) after the PAT was deleted or expired in figma; using a token from a different figma account than the one that owns the file (can also 403); a token that was regenerated in the UI but FIGMA_TOKEN in the shell still holds the old value.
Common situations: Token expired (figma PATs can have expirations or be revoked); the user regenerated a token and forgot to update the .env/shell; token pasted with trailing whitespace stripped wrongly (less likely since createFigmaClient trims); switching figma accounts mid-project.
Related errors
- NO_TOKEN
- HTTP_ERROR
- OpenRouter request failed with HTTP ${res.status}
- Aborted.
- ref "${refInput}" has no node id — share a link with ?node-i
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/4d321cd4fef42796.
Report an issue: GitHub.