actualbudget/actual · error
Failed to get server config.
Error message
Failed to get server config.
What it means
setSecret stores a named secret (e.g. bank-sync credentials) on the sync server. It reads the global server config via getServer(); when the app is running without a configured server URL, getServer() returns null and this error is thrown because there is no server to store the secret on.
Source
Thrown at packages/loot-core/src/server/accounts/app.ts:735
async function setSecret({
name,
value,
fileId = null,
}: {
name: string;
value: string | null;
fileId?: string | null;
}) {
const userToken = await asyncStorage.getItem('user-token');
if (!userToken) {
return { error: 'unauthorized' };
}
const serverConfig = getServer();
if (!serverConfig) {
throw new Error('Failed to get server config.');
}
const headers = {
'X-ACTUAL-TOKEN': userToken,
...(fileId ? { 'X-Actual-File-Id': fileId } : {}),
};
try {
if (value === null) {
return await del(
serverConfig.BASE_SERVER + '/secret/' + name,
{},
headers,
);
}
return await post(
serverConfig.BASE_SERVER + '/secret',View on GitHub (pinned to d4334cb6e6)
Solutions
- Configure a sync server URL: set it in the app's advanced settings or call actual.init({ serverURL: 'https://your-server' }) in the API.
- If self-hosting, start the sync server and point the client at it before using secrets/bank sync.
- Check that nothing called setServer(null) or reset the server URL after initialization.
- If you intentionally run local-only, avoid secret-set/bank-sync APIs — they require a server.
Example fix
// before
await actual.init({ dataDir: './data' });
await actual.q('messages').insert(...); // then later setSecret fails
// after
await actual.init({ dataDir: './data', serverURL: 'https://actual.example.com' });
await actual.login('user@example.com', 'password');
// now setSecret works Defensive patterns
Strategy: validation
Validate before calling
// read the same config the app uses
import { getServer } from './server-config';
if (!getServer()) {
throw new Error('No sync server configured; set a server URL before storing secrets.');
} Type guard
function hasServerConfig(c: ReturnType<typeof getServer>): c is NonNullable<ReturnType<typeof getServer>> {
return c != null && typeof c.BASE_SERVER === 'string';
} Try / catch
try {
await send('secret-set', { name, value });
} catch (e) {
if (e.message === 'Failed to get server config.') {
// local-only mode: surface a 'server required' state instead of crashing
reportServerRequired(name);
} else {
throw e;
}
} Prevention
- Always pass serverURL to actual.init when using server-backed features.
- Check server connection status in the UI before exposing secret/bank-sync settings.
- Treat local-only mode as a first-class state: hide or disable server-only handlers.
- Log the configured server URL at startup to catch empty-config issues early.
When it happens
Trigger: Calling the 'secret-set' handler (setSecret) while the client is in local-only mode ('Don't use a server') or before setServer(url) has been called, so the module-level config in server-config.ts is still null.
Common situations: Using a local-only budget but attempting to configure bank sync (GoCardless/SimpleFin/Pluggy.ai/Akahu) which requires a server; forgetting to run actual.init with serverUrl in the API package; the server URL was reset via setServer(null); standalone/desktop app started without a server URL.
Related errors
- No sync server configured.
- No sync server configured.
- No sync server set
- NOT_CONFIGURED
- No id returned from download.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/ad81ececdbedb174.
Report an issue: GitHub.