Chat as the signed-in user
This is part of step 2 of the Go live checklist. Both installs, Connect GitHub and your AI coding assistant, write the endpoint and wire it to your app's existing sign-in.
For the Product agent to act for someone, Frankie must know who they are. Your server answers POST /api/frankie/token with a short-lived token signed with FRANKIE_JWT_SECRET. The install puts this route in your existing backend, whatever its language, and looks up the user with the session code your app already has: Auth.js / NextAuth (v5 and v4), Clerk, Firebase session cookies, Supabase, or your own session helper.
When the install can't tell how your app identifies the signed-in user, it doesn't guess. The route answers 501 until you fill in the lookup, and the pull request's Needs you section names the file. On Next.js, the lookup is one function you can export from lib/frankie-auth.ts and import 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.
}
See The install agent couldn't wire sign-in for what to fill in, and a prompt that lets your AI coding assistant do it.
What the endpoint answers, and what the chat shows:
| Status | Means | The full-page chat says |
|---|---|---|
200 { "token": … } | Signed in. | Chats as that user. |
401 | Nobody is signed in. Only this status means "signed out". | Sign in to this app to use chat. |
501 | getCurrentUser isn't connected yet. | Chat sign-in is not wired up yet: connect getCurrentUser in api/frankie/token. |
503 | FRANKIE_JWT_SECRET is missing on the server. | Chat is not configured on the server yet (FRANKIE_JWT_SECRET is missing). |
500 | Your session lookup threw. The error is in the server log. | Chat sign-in failed on the server. Check the server logs for /api/frankie/token. |
These messages come from the Next.js install's mount. The floating Product widget behaves differently: in production it hides on any answer other than a token, so a 501, 503 or 500 there means no widget at all. In development, a 501 or 503 still shows it with the message. A hand-written setup shows the message your own getUserToken throws. See Troubleshooting.
With Auth.js, the endpoint uses session.user.id, or the session token's subject when your session callback doesn't add an id. With database sessions, add a session callback that sets session.user.id.
Include name and email when you have them: they're how your team recognises who a conversation is with. Tokens must expire; the generated endpoint uses five minutes.
Writing the endpoint yourself
If you'd rather write the route yourself, any server can mint the token:
import { SignJWT } from "jose";
export async function POST() {
const user = await getCurrentUser(); // your existing session
if (!user) return new Response(null, { status: 401 }); // only for "signed out"; let real errors answer 500
const token = await new SignJWT({ name: user.name, email: user.email })
.setProtectedHeader({ alg: "HS256" })
.setSubject(user.id) // required: becomes the Frankie user id
.setIssuedAt()
.setExpirationTime("5m")
.sign(new TextEncoder().encode(process.env.FRANKIE_JWT_SECRET!));
return Response.json({ token });
}
See Authentication for the full handshake, JWKS and claims.
Public pages
Public pages don't need this. The Support and Docs agents are open to visitors by default (Visitors can chat without signing in in their tabs; only a workspace admin can change it), and still recognise anyone who is signed in.
If your users never sign in, choose Let visitors chat without signing in (under More options) when you create the workspace. You can only choose this at creation: to change it later, create a new workspace. The install then leaves out the token endpoint (the dashboard's setup prompt has an anonymous version), and you won't need FRANKIE_JWT_SECRET. Anonymous visitors can ask questions and read, but an agent won't make changes on anyone's account.