{"record":{"id":"285405f6d5a22082","repo":"transloadit/uppy","slug":"missing-access-token","errorCode":null,"errorMessage":"Missing access_token","messagePattern":"Missing access_token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/companion/src/server/provider/dropbox/index.ts","lineNumber":375,"sourceCode":"    clientSecret: string | undefined\n    refreshToken: string\n  }): Promise<{ accessToken: string }> {\n    return this.#withErrorHandling(\n      'provider.dropbox.token.refresh.error',\n      async () => {\n        const tokenRes = await getOauthClient()\n          .post('token', {\n            form: {\n              refresh_token: refreshToken,\n              grant_type: 'refresh_token',\n              client_id: clientId,\n              client_secret: clientSecret,\n            },\n          })\n          .json<{ access_token?: unknown }>()\n        const accessToken = tokenRes.access_token\n        if (typeof accessToken !== 'string' || accessToken.length === 0) {\n          throw new Error('Missing access_token')\n        }\n        return { accessToken }\n      },\n    )\n  }\n\n  async #withErrorHandling<T>(tag: string, fn: () => Promise<T>): Promise<T> {\n    return withProviderErrorHandling({\n      fn,\n      tag,\n      providerName: Dropbox.oauthProvider,\n      isAuthError: (response) => response.statusCode === 401,\n      getJsonErrorMessage: (body) => {\n        if (!isRecord(body)) return undefined\n        const summary = body['error_summary']\n        return typeof summary === 'string' ? summary : undefined\n      },\n    })","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/provider/dropbox/index.ts#L357-L393","documentation":"When a Dropbox access token expires, Companion refreshes it by POSTing grant_type=refresh_token to Dropbox's token endpoint. If the JSON response does not contain a non-empty access_token string, this error is thrown, indicating the refresh failed in an unexpected way (Dropbox usually returns an error status, which got.stream/retry would surface separately).","triggerScenarios":"Dropbox's OAuth token endpoint returning 200 with a body lacking access_token (malformed/changed response), a refresh token that was revoked or expired so Dropbox returns an error payload, or clock/env issues causing Dropbox to respond with an auth error body.","commonSituations":"User revoked the app's access in Dropbox settings, the app's refresh token was invalidated by switching app keys, Dropbox API behavior change, or long-lived offline tokens expiring past the 90-day inactivity window.","solutions":["Have the user disconnect and reconnect Dropbox so a fresh refresh token is issued","Verify the Dropbox app key/secret configured in Companion match the app that issued the refresh token","Check Companion logs for the underlying Dropbox response around the refresh call","If it persists, inspect the raw token endpoint response (Dropbox may have changed the payload shape)"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"function isMissingTokenError(err: unknown): boolean {\n  return err instanceof Error && err.message === 'Missing access_token'\n}","tryCatchPattern":"try {\n  await provider.download({ id })\n} catch (err) {\n  if (isMissingTokenError(err)) {\n    await provider.logout() // clears stale refresh token\n    redirectUserToReconnectProvider()\n    return\n  }\n  throw err\n}","preventionTips":["Implement session-expiry handling that silently re-authenticates users","Rotate app keys and refresh tokens together, never separately","Log provider auth failures per user to detect revoked grants early"],"tags":["companion","dropbox","oauth","refresh-token"],"backgroundTag":"oauth-refresh-token-invalid","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}