{"record":{"id":"49fc02ebeaf48aac","repo":"Mintplex-Labs/anything-llm","slug":"device-not-approved","errorCode":null,"errorMessage":"Device not approved","messagePattern":"Device not approved","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/endpoints/mobile/middleware/index.js","lineNumber":25,"sourceCode":" * exists in the database and is approved.\n * @param {import(\"express\").Request} request\n * @param {import(\"express\").Response} response\n * @param {import(\"express\").NextFunction} next\n */\nasync function validDeviceToken(request, response, next) {\n  try {\n    const token = request.header(\"x-anythingllm-mobile-device-token\");\n    if (!token)\n      return response.status(400).json({ error: \"Device token is required\" });\n\n    const device = await MobileDevice.get(\n      { token: String(token) },\n      { user: true }\n    );\n    if (!device)\n      return response.status(400).json({ error: \"Device not found\" });\n    if (!device.approved)\n      return response.status(400).json({ error: \"Device not approved\" });\n\n    // If the device is associated with a user then we can associate it with the locals\n    // so we can reuse it later.\n    if (device.user) {\n      if (device.user.suspended)\n        return response.status(400).json({ error: \"User is suspended.\" });\n      response.locals.user = device.user;\n    }\n\n    delete device.user;\n    response.locals.device = device;\n    next();\n  } catch (error) {\n    console.error(\"validDeviceToken\", error);\n    response.status(500).json({ error: \"Invalid middleware response\" });\n  }\n}\n","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/endpoints/mobile/middleware/index.js#L7-L43","documentation":"validDeviceToken found the device by token but its approved column is false. Devices created by POST /mobile/register are not auto-approved; an admin must POST /api/mobile/update/:id with { approved: true } (approved is the only writable field). Until then every device-authenticated call returns 400 { error: 'Device not approved' }.","triggerScenarios":"The app registers, saves its token, and immediately calls further /api/mobile/* endpoints before an admin approves it; or the admin sets approved back to false to revoke live access while keeping the row.","commonSituations":"Race between registration and approval during demos/tests; approval step missed because the flow wasn't obvious; revoke-by-unapprove used instead of delete.","solutions":["Have an admin approve: POST /api/mobile/update/<device id> with body {\"approved\":true}","In the mobile client, treat 'Device not approved' as a distinct 'waiting for approval' state and poll gently — do not re-register on this error","If approval was wrongly revoked, set approved:true again via the same update endpoint"],"exampleFix":"// before — client retries instantly in a loop\nwhile (true) await api.command('workspaces');\n\n// after — recognize pending state and back off\nconst res = await api.command('workspaces');\nif ((await res.json()).error === 'Device not approved') {\n  setStatus('awaiting_admin_approval');\n  await sleep(5000); // poll politely\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await api.command('workspaces');\n} catch (e) {\n  if (e.status === 400 && e.body?.error === 'Device not approved') {\n    scheduleApprovalPoll(5000); // retry with backoff until admin approves\n    return;\n  }\n  throw e;\n}","preventionTips":["Design the app with an explicit 'awaiting admin approval' state","Poll sparingly (seconds, not milliseconds) to avoid hammering the server","Do not re-register on this error — the existing token is fine once approved"],"tags":["mobile","auth","approval","device-management","onboarding"],"backgroundTag":"device-not-approved","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}