Troubleshooting
Common failures and their causes. Each entry is symptom → cause → fix.
Routing, authentication and permission errors
Every /api/... route returns 404 {"message":"Not found"}
The route's owner extension is not registered. Routes are registered by extensions during license validation: profile and list come from the intrasheets extension, prompt/chats/usage from the agent, the core worksheet routes from api. If one is missing from the extensions: { ... } object in your server setup, its routes are not registered while everything else keeps working, so a missing extension can be mistaken for an authentication problem.
server({
// every extension must be listed, or its routes are never registered
extensions: { api, intrasheets, agent, openai, mcp },
});
Also check the license: extensions register after license validation, so an invalid JSS_CLIENT/JSS_LICENSE leaves the API empty.
Login loops back to the sign-in page ("my token is not accepted")
Two distinct causes, in order of likelihood:
- A post-login API call is failing. Front ends typically exchange the OAuth code, then call an API route (e.g.
POST /api/profile) before storing the token; if that call fails (see the 404 entry above, or the API server is not running), the token is never stored and the app returns to login, the same symptom as a rejected token. Check the browser's network tab for the failing request. - The OAuth callback URL was reused. The
codeis single-use and thestatemust match what the OIDC client stored in the browser before redirecting. Reloading or bookmarking a/auth?code=...&state=...URL always fails, so start again from the application root.
403 Forbidden
- One of your hooks (
beforeConnect/beforeLoad/beforeChange) returnedfalse. Log the guid and token subject inside the hook to see which. - Owner-only operations (
setConfig,setUsers, snapshots, user management,history) return 403 for editors, by design. - Formify routes return 403 for worksheets that don't declare a
formifyconfiguration, since enabling the form on the worksheet is what publishes the endpoint.
Request format and persistence issues
POST /api/create fails or ignores the config
The config field must be a single JSON-encoded string. Bracket-notation form fields work everywhere else, but not here. See HTTP conventions. A related symptom is a SyntaxError: "[object Object]" is not valid JSON in the server log.
Values disappear or shift after a server restart
Row data was written as a numeric-keyed object ({ "0": "a", "1": "b" }) instead of an array. Object-shaped rows do not survive the persistence round-trip. Always send row data as arrays (data[0]=a&data[1]=b builds an array automatically in form fields).
Clients suddenly reload the document (forceRefresh)
This is the recovery mechanism, not an error in itself. Your adapter's change handler failed to persist an operation; the server forces every client to reload rather than let screens drift from storage. Check the server log for the underlying database error, and see the failure semantics.
MongoDB: document exceeds maximum allowed BSON size
The document exceeds MongoDB's 16 MB per-document limit, usually caused by embedded base64 images. Store images on S3 and persist URLs, or switch to the PostgreSQL adapter if your documents are genuinely that large.
Connection, proxy and real-time sync issues
Two users see different content in the same document
They are connected to different server instances. This is a load-balancer misconfiguration: per-user sticky sessions (cookie/IP affinity) keep a user on a node, but a document's peers must all reach the same node. Route by the document guid instead:
upstream sheets {
hash $arg_guid consistent;
...
}
The same applies to REST and /mcp traffic. Full model in Scaling.
Browser CORS errors
Set your real origins in the server config (config: { cors: { origin: [...] } }); the examples ship with origin: "*" which some setups (credentials mode) reject. If a proxy sits in front, make sure it forwards OPTIONS preflights to the server rather than answering them itself.
WebSocket fails to connect (REST works)
The proxy is not upgrading connections. Nginx needs the upgrade headers on the socket.io location. See Nginx. If REST also fails auth-wise, check beforeConnect: it runs on every socket handshake.
MCP client errors: "session not found" / requests rejected
The MCP transport is stateful: only an initialize request may open a session, and the returned mcp-session-id header must accompany every subsequent call. Idle sessions close after 30 minutes, so reinitialize rather than retrying a dead session id. See MCP Server.
Where to look when nothing matches
- The server log. Adapter and hook errors are printed with their stack.
- The browser network tab. The failing request, its status and body are usually more specific than the UI symptom.
- FAQ and the architecture page. Many symptoms follow from the documented single-instance-per-document model.