{"record":{"id":"9443b027f4449fa4","repo":"Mintplex-Labs/anything-llm","slug":"invalid-or-expired-registration-token","errorCode":null,"errorMessage":"Invalid or expired registration token","messagePattern":"Invalid or expired registration token","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/endpoints/mobile/middleware/index.js","lineNumber":64,"sourceCode":" * and associates the user with the token (if valid). Temporary token is consumed\n * and cannot be used again after this middleware is called.\n * @param {*} request\n * @param {*} response\n * @param {*} next\n */\nasync function validRegistrationToken(request, response, next) {\n  try {\n    const authHeader = request.header(\"Authorization\");\n    const tempToken = authHeader ? authHeader.split(\" \")[1] : null;\n    if (!tempToken)\n      return response\n        .status(400)\n        .json({ error: \"Registration token is required\" });\n\n    const tempTokenData = MobileDevice.tempToken(tempToken);\n    if (!tempTokenData)\n      return response\n        .status(400)\n        .json({ error: \"Invalid or expired registration token\" });\n\n    // If in multi-user mode, we need to validate the user id\n    // associated exists, is not banned and then associate with locals so we can reuse it later.\n    // If not in multi-user mode then simply having a valid token is enough.\n    const multiUserMode = await SystemSettings.isMultiUserMode();\n    if (multiUserMode) {\n      if (!tempTokenData.userId)\n        return response\n          .status(400)\n          .json({ error: \"User id not found in registration token\" });\n      const user = await User.get({ id: Number(tempTokenData.userId) });\n      if (!user) return response.status(400).json({ error: \"User not found\" });\n      if (user.suspended)\n        return response\n          .status(400)\n          .json({ error: \"User is suspended - cannot register device\" });\n      response.locals.user = user;","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/endpoints/mobile/middleware/index.js#L46-L82","documentation":"validRegistrationToken calls MobileDevice.tempToken(token), which checks an in-memory Map (TemporaryMobileDeviceRequests). It returns null — yielding 400 { error: 'Invalid or expired registration token' } — when the token is unknown, expired (tokens live 3 minutes: expiresAt = createdAt + 3*60_000), or already consumed: a finally block deletes the entry on every lookup, so each temp token works exactly once. The Map also dies with the process on restart.","triggerScenarios":"Scanning the QR or copying the connect-info URL and registering more than 3 minutes later; retrying /mobile/register after a prior attempt (the first call consumed the token); server restart or hot-reload between connect-info and register; token typo; load balancer routing register to a different instance than the one holding the Map.","commonSituations":"Manual typing of the pairing URL in dev; nodemon/PM2 restart wiping in-memory state; user rescans a new QR but the app caches the old URL; horizontally scaled deployments.","solutions":["Fetch fresh connect-info (GET /api/mobile/connect-info) or rescan the QR and register immediately, within 3 minutes","Never retry register with the same temp token — always obtain a new one first","Pin connect-info and register to the same server instance; temp tokens are in-process memory, not shared state"],"exampleFix":"// before — reusing a stale token\nawait register(tempToken); // 400 invalid or expired\nawait register(tempToken); // retry, still fails\n\n// after — refresh token, then register once\nconst info = await (await fetch('/api/mobile/connect-info')).json();\nconst fresh = new URL(info.connectionUrl).searchParams.get('t');\nawait register(fresh);","handlingStrategy":"retry","validationCode":"const TOKEN_TTL_MS = 3 * 60_000;\nif (Date.now() - tokenFetchedAt > TOKEN_TTL_MS - 5_000) {\n  tempToken = await fetchFreshConnectInfo(); // avoid expired-token 400\n}","typeGuard":null,"tryCatchPattern":"try {\n  await register(tempToken);\n} catch (e) {\n  if (e.status === 400 && e.body?.error?.includes('Invalid or expired registration token')) {\n    const fresh = await fetchFreshConnectInfo(); // one retry with a new token\n    return register(fresh);\n  }\n  throw e;\n}","preventionTips":["Register immediately after obtaining connect-info — the window is 3 minutes","Never retry with the same temp token; it is single-use","Keep connect-info and register on the same server process; tokens are in-memory only"],"tags":["mobile","registration","token-expiry","one-time-token","in-memory-state"],"backgroundTag":"registration-token-expired","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}