actualbudget/actual · error · PostError
responseData.description || responseData.reason || 'unknown'
Error message
responseData.description || responseData.reason || 'unknown'
What it means
When the sync server returns HTTP 200 but the JSON payload has status !== 'ok', `post` throws a PostError whose message is the server-provided `description` or `reason` (falling back to 'unknown'). This is an application-level failure reported by the server itself; the full request data and response are logged for diagnosis.
Source
Thrown at packages/loot-core/src/server/post.ts:115
try {
responseData = JSON.parse(text);
} catch {
// Something seriously went wrong. TODO handle errors
throw new PostError('parse-json', { meta: text });
}
if (responseData.status !== 'ok') {
logger.log(
'API call failed: ' +
url +
'\nData: ' +
JSON.stringify(data, null, 2) +
'\nResponse: ' +
JSON.stringify(res, null, 2),
);
throw new PostError(
responseData.description || responseData.reason || 'unknown',
);
}
return responseData.data;
}
export async function del(url, data, headers = {}, timeout = null) {
let text;
let res;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const signal = timeout ? controller.signal : null;
res = await fetch(url, {
method: 'DELETE',
body: JSON.stringify(data),View on GitHub (pinned to d4334cb6e6)
Solutions
- Read the thrown PostError message (description/reason) and the logged 'API call failed' output to identify the server-side reason.
- If the reason is token-related (e.g. token-expired), sign in again to obtain a fresh user-token.
- Verify the fileId/cloudFileId used in the request exists on the server.
- If the message is 'unknown', check sync server logs for the corresponding request to find the underlying error.
- Upgrade both client and sync server to compatible versions.
Example fix
// before: stale token kept after server was reset
await post(server + '/update-user-filename', { token: oldToken, ... });
// after: re-login first
const token = await signInAndGetToken(password); // fresh user-token
await post(server + '/update-user-filename', { token, ... }); Defensive patterns
Strategy: try-catch
Validate before calling
const token = await asyncStorage.getItem('user-token');
if (!token) throw new Error('Not signed in: no user-token available for server call'); Type guard
function isServerRejection(e: unknown): e is { reason: string; details?: unknown } {
return typeof e === 'object' && e !== null && 'reason' in e && typeof (e as any).reason === 'string' && (e as any).reason !== 'ok';
} Try / catch
try {
await post(server + '/update-user-filename', { token, fileId, name });
} catch (e) {
if (isPostError(e) && /token|unauthorized/i.test(e.message)) {
await reauthenticate(); // get a fresh user-token and retry once
} else {
throw e;
}
} Prevention
- Re-authenticate whenever a token-expired reason is returned instead of reusing stale tokens.
- Verify fileIds exist on the server before update/delete operations.
- Keep client and sync server versions aligned.
- Check server logs whenever the error message is 'unknown'.
When it happens
Trigger: checkKey, resetSyncState, removeFile, or update-user-filename calls (post) where the server responds {status: <not 'ok'>, description/reason: ...} — e.g. invalid user token, unknown fileId, or server-side validation rejecting the request.
Common situations: Expired or missing user-token after server restart or logout; budget name update targeting a cloudFileId the server doesn't recognize ('file-not-found'); mismatched server version that rejects a request field; 'unknown' when the server sends a non-ok status with neither description nor reason.
Related errors
- unknown
- response.reason || response.error || fallbackMessage
- openid-grant-failed
- token-expired
- network-failure
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/7585827a24184579.
Report an issue: GitHub.