Troubleshooting
Start with two checks on your deployed app. They tell you which part is broken:
curl https://<your-app>/api/frankie/health
curl -i -X POST https://<your-app>/api/frankie/token # signed out: expect 401
The health route (added by the Next.js install) reports booleans only. Look for "publishableKey": true and "jwtSecret": true, and check that "auth" isn't "unconfigured". If a step is missing altogether, go through the Go live checklist.
The chat doesn't appear
The widget doesn't appear
Work down this list:
-
Are you signed in? The Product agent shows only to signed-in users. That's by design, not a bug: a signed-out visitor never sees it. The widget checks again every few seconds and when the tab regains focus, so it appears soon after you sign in.
-
Does the token route answer with a token? In production, the generated widget stays hidden unless
POST /api/frankie/tokenreturns a token. It doesn't show an error. Check the status:501: sign-in isn't wired yet. See The install agent couldn't wire sign-in.503:FRANKIE_JWT_SECRETis missing on the server. See below.401while you're signed in: the route can't find your session. See below.500: your session lookup threw. The server log has the error, prefixed[frankie].
In development (
npm run dev), a501or503still shows the widget, with a message saying what to fix. The full-page chat (/chat, or/frankie/chatif/chatwas taken) always shows the message. -
Is the key set in this environment, and did you rebuild?
NEXT_PUBLIC_FRANKIE_KEYis built into the page at build time. Setting it without redeploying does nothing, and so does setting it only for Production while you test a Preview./api/frankie/healthshows"publishableKey": falsewhen it's missing. -
Is the site allowed? See Origin is not allowed.
-
Does the page belong to another agent? The Product agent never shows on a page that another agent's page patterns claim, or over the full-page chat.
The install agent couldn't wire sign-in
The token route answers 501 ("Chat sign-in is not wired up yet") and the install pull request has a Needs you section. The install agent couldn't tell how your app identifies the signed-in user, so instead of guessing (or inventing a new auth system), it wrote the token route with a placeholder that answers 501 until it's connected. In production that means no widget at all.
What Needs you tells you. Each item names a file. For sign-in, it says what the agent looked for (Auth.js / NextAuth, Clerk, Supabase, a Firebase session cookie, a getCurrentUser export in your auth modules…), what it found, and the file and function to fill in: usually the token route itself, at a line marked TODO. The pull request's description also says Sign-in: not wired. On Next.js the same list is in FRANKIE_SETUP.md.
What to fill in. One lookup that returns the signed-in user from the session your app already uses, or nothing when nobody is signed in:
- Return the user's stable id (it becomes the Frankie user id), and their name and email if you have them.
- Return nothing (
null) only when nobody is signed in: the route then answers401. Let real errors throw, so they answer500. - Remove the placeholder that answers
501. On Next.js, that's the stubgetCurrentUserandAUTH_NOT_CONFIGUREDinapp/api/frankie/token/route.ts. You can instead exportgetCurrentUserfromlib/frankie-auth.tsand import it into the route:
export async function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> {
// Return the signed-in user from your own session, or null.
}
The fastest way is often to let your AI coding assistant do it. Check out the pull request's branch, open the assistant at the repository root, and ask:
The Frankie token route returns 501 because it isn't connected to our sign-in.
Read the "Needs you" note in the pull request, then fill in the user lookup in
that file using the session code this app already uses. Return null only when
nobody is signed in. Don't add a new auth system.
Commit the change to the pull request's branch, then check: signed out, POST /api/frankie/token answers 401; signed in, it returns { "token": "…" }. See Chat as the signed-in user.
Token route answers 503: "Chat is not configured on the server yet"
FRANKIE_JWT_SECRET is missing from the server that runs /api/frankie/token. Copy it from Overview → Keys & settings → Server secret → Reveal current secret, set it on that server for every environment (Preview too), and redeploy.
In production, this shows up as no widget at all. Only development and the full-page chat show the message.
Token route answers 401 while you're signed in
The route found no user. In production, that hides the widget. On /chat you see "Sign in to this app to use chat." What to check depends on how your app signs people in:
- Auth.js / NextAuth: the session has no
user.idand no session token was found. With database sessions, add a session callback that setssession.user.id. Make sureAUTH_SECRET(orNEXTAUTH_SECRET) is set on the server. - Clerk:
clerkMiddlewaremust run on/api/frankie/token. Its default matcher covers/api, but check it if you changed it. - Supabase: set
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEYon the server, and make sure the user signed in through Supabase. - Firebase: the session cookie named by
SESSION_COOKIEmust be present on that request. - Your own
getCurrentUser: it should returnnullonly when nobody is signed in. Check that it reads the same cookie or session that the rest of your app uses. - Separate backend (for example Vite with Express): the request has to reach your backend with the user's cookies. Proxy
/api/frankie/tokenthrough your dev server, or call an absolute URL withcredentials: "include"and set up CORS with credentials.
The chat appears but fails
"Could not verify the user token"
Frankie couldn't verify the token your server signed, so the secret doesn't match this workspace. This usually happens when the secret was rotated (Danger zone → Rotate secret) or copied from a different workspace. Copy the current one from Overview → Keys & settings → Reveal current secret, update it on every server, and redeploy.
After a rotation, signed-in chat is broken until every server has the new value.
"Missing or invalid publishable key."
The key the browser sent is empty or wrong. Because NEXT_PUBLIC_FRANKIE_KEY is built into the page, it was empty or different when that deployment was built. Set the key from Overview → Keys & settings → Publishable key for that environment, and rebuild.
"This assistant requires a signed-in user."
The chat sent no user token to a workspace where visitors must sign in. In a hand-written setup, pass getUserToken to FrankieProvider (see Install by hand). Leaving it out only works for workspaces created with Let visitors chat without signing in.
"Origin … is not allowed for this tenant."
The page's address isn't in Overview → Keys & settings → Allowed sites. Add it and choose Save sites. You can enter localhost:3000 or app.acme.com, and the scheme is added for you. What must match exactly is the host and port:
- Preview deployments get a new address each time, and wildcards aren't supported. Add each preview address you test on, or test on a stable preview domain.
www.acme.comandacme.comare different sites. Add both if you serve both.localhostand127.0.0.1are different, and so are different dev ports, unless Allow localhost is on: then every localhost and127.0.0.1port is allowed. Workspaces created before that setting existed have it off; turn it on under Allowed sites.- An empty list blocks every site (other than localhost with Allow localhost on).
If actions call an API on a different address from your pages, that API needs CORS that allows credentials from your site, and the provider needs api={{ baseUrl, credentials: "include" }}. See Provider.
Every chat shows "Signed-in user"
Your token endpoint isn't sending a name or email. Return them from getCurrentUser.
The agent can't do anything
The agent says it can't do something
Either no action is allowed, or your API refused that user.
- No actions allowed. Everything starts off. In Product agent → What may it do in your app?, switch on the actions, then choose Save allowed actions.
- No API connected. After an AI assistant install, Frankie doesn't see your OpenAPI spec until you add it under Product agent → Connect your API (by URL, or with Paste JSON instead) or connect the repository on Install.
- Your API refused it. The conversation's steps show which call failed and why.
An action fails with 401 while you're signed in
The chat works but your API refuses the user. Usually the secret that signs the token isn't the one that verifies it.
- Same secret on both sides.
FRANKIE_JWT_SECRETmust be identical on the backend that serves/api/frankie/token(the signer) and on any API that verifies Frankie's user token. With a frontend and a separate API, these are two deployments, often two secrets stores. Compare fingerprints: new installs'/api/frankie/healthreportsjwtSecretFingerprint(the first 8 hex characters of the secret's SHA-256); the install's readiness card flags a mismatch with your workspace's secret. Compute the same on your API; they must match. - The API can read the secret. If the API reads it from a secrets manager, its runtime identity needs access (on Cloud Run with Secret Manager: Secret Manager Secret Accessor for the service's service account). Redeploy after changing it.
- The token reaches the API. In User's browser mode, the page must send it, with
api.headersor cookies. Send the user's sign-in in this header only applies to Frankie's server mode. - The API says why. Return a reason (
invalid_token,expired_token,missing_token) with every401, so the conversation's steps show which check failed.
See A frontend and a separate API service and the 401 checklist.
Actions return 404, or the calls hit your app instead of your API
Browser-run actions call a base URL plus the operation's path. If that base URL is your app's own address but the routes live on a separate API, every call returns your app's 404. The conversation's steps show the URL that was called.
- Set where the API is. Put the API's public URL in the spec's
servers[0], or in the connection's API base URL under Product agent → How Frankie calls this API. Browser calls use the connection's base URL first, then the SDK'sapi.baseUrl(when it isn't the page's own origin), then the spec'sservers[0], then the page's origin. - Check the connected spec is the user-facing one. A backend spec of internal or admin routes lists paths your public API doesn't serve, or that need a service key.
- Allow your app's origin in the API's CORS, with credentials for cookies, or the
Authorizationheader for tokens.
See Setup requirements by architecture.
Installing
The install asks which app (monorepos)
If the repository has more than one app, Connect GitHub asks which one to install into. Pick the app your users sign in to, the one with its own package.json, not an example or demo app. With your AI coding assistant, name the app in the prompt, for example "install into apps/web".
The install agent says the SDK is React-only
@frankieai/react needs a React frontend. On Vue, Svelte, Angular or server-rendered HTML, the install agent changes nothing and says so. You can mount a small React island for the widget on the signed-in pages, or contact Frankie for help.
CI fails on the lockfile after a dashboard pull request
The install agent can't run your package manager, so its pull request doesn't update the lockfile, and npm ci fails. Check out the branch, update the lockfile with the command for your package manager, and push:
| Package manager | Command |
|---|---|
| npm | npm install --package-lock-only |
| pnpm | pnpm install --lockfile-only |
| Yarn | yarn install |
| Bun | bun install |
"Nothing to change" when installing an agent
Your app already has FrankieAgents.tsx. To turn on the agent, set its key variable in your deployment.
Nothing changed after installing
The pull request has to be merged and deployed, and the variables set before that build. Check that the pull request is merged, that the latest deployment includes it, and that /api/frankie/health answers on the deployed site.
Looks
"Match your site" shows the wrong colors
Add data-frankie-accent to your primary button so the widget knows your accent, and set fallback colors under Widget for pages it can't read. See Widget and theme.
Still stuck?
Open the dashboard and use Ask Frankie. It can look at your workspace with you. Or ask the assistant in the corner of this page.