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
- Sign in to the sync server (or call api.login/ bootstrap so user-token is stored) before uploading
- Verify the app is configured with the correct server URL and that the server is running
- If the token is stale, sign out and sign back in to obtain a fresh user-token
- 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
- Always login/bootstrap before cloud operations in scripts
- Handle 'unauthorized' globally by re-authenticating once and retrying
- Check the configured server URL matches a running sync server
- Don't clear asyncStorage/user-token while a sync is pending
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authentication required. Set --password/--session-token, ACT
- Authentication required. Provide --password or --session-tok
- API request redirected
- login: User token not set
- Forbidden
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/eb53453a54cc4da0.
Report an issue: GitHub.