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

  1. Read the thrown PostError message (description/reason) and the logged 'API call failed' output to identify the server-side reason.
  2. If the reason is token-related (e.g. token-expired), sign in again to obtain a fresh user-token.
  3. Verify the fileId/cloudFileId used in the request exists on the server.
  4. If the message is 'unknown', check sync server logs for the corresponding request to find the underlying error.
  5. 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

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


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