actualbudget/actual · error · FileUploadError

unauthorized

unauthorized

Error message

unauthorized

What it means

FileUploadError('unauthorized') is thrown at the start of upload() when there is no user-token stored in asyncStorage. Without a login token the library refuses to attempt the upload to the sync server. It is a client-side pre-check, distinct from a server-side 401.

Source

Thrown at packages/loot-core/src/server/cloud-storage.ts:292

      await fs.removeFile(dbFile);
    }
    if (await fs.exists(metaFile)) {
      await fs.removeFile(metaFile);
    }
  } else {
    await fs.mkdir(budgetDir);
  }

  await fs.writeFile(fs.join(budgetDir, 'db.sqlite'), dbContent);
  await fs.writeFile(fs.join(budgetDir, 'metadata.json'), JSON.stringify(meta));

  return { id: meta.id };
}

export async function upload() {
  const userToken = await asyncStorage.getItem('user-token');
  if (!userToken) {
    throw FileUploadError('unauthorized');
  }

  const exported = await exportBuffer();
  if (exported == null) {
    return;
  }
  const zipContent = exported.data;

  const {
    id,
    groupId,
    budgetName,
    cloudFileId: originalCloudFileId,
    encryptKeyId,
  } = prefs.getPrefs();
  let cloudFileId = originalCloudFileId;
  let uploadContent = zipContent;
  let uploadMeta = null;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Sign in to the sync server (or call api.login/ bootstrap so user-token is stored) before uploading
  2. Verify the app is configured with the correct server URL and that the server is running
  3. If the token is stale, sign out and sign back in to obtain a fresh user-token
  4. In headless/API scripts, call await api.login(password) (or init with config) before any upload

Example fix

// before
await upload();
// after
const token = await asyncStorage.getItem('user-token');
if (!token) { await signIn(password); }
await upload();
Defensive patterns

Strategy: validation

Validate before calling

async function canUpload() {
  const token = await asyncStorage.getItem('user-token');
  if (!token) throw new Error('Not signed in: no user-token stored');
  return token;
}

Type guard

function hasUserToken(prefs) {
  return typeof prefs?.['user-token'] === 'string' && prefs['user-token'].length > 0;
}

Try / catch

try {
  await upload();
} catch (e) {
  if (e instanceof FileUploadError && e.reason === 'unauthorized') {
    await signIn(password); // obtain fresh user-token
    await upload();
  } else throw e;
}

Prevention

When it happens

Trigger: calling upload(), uploadBudget(), duplicateBudget(), createBudget(), loadBackup(), possiblyUpload()-triggered uploads, or importActual() while the user is not signed in to a sync server (or the token was never set / was cleared).

Common situations: running in local-only (no server) mode and trying to use cloud features, token invalidated by server restart with different key, expired login after long offline period, or headless/API usage where login() was never called.

Understand the failure class

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/eb53453a54cc4da0. Report an issue: GitHub.