actualbudget/actual · error
since-required
since-required
Error message
since-required
What it means
The sync protocol requires a 'since' timestamp cursor to know from which point to return changes. If the decoded SyncRequest has no since value, the server responds 422 with details 'since-required' and reason 'unprocessable-entity'.
Source
Thrown at packages/sync-server/src/app-sync.ts:156
app.post('/sync', async (req, res): Promise<void> => {
let requestPb;
try {
requestPb = fromBinary(SyncRequestSchema, req.body);
} catch (e) {
console.log('Error parsing sync request', e);
res.status(500);
res.send({ status: 'error', reason: 'internal-error' });
return;
}
const fileId = requestPb.fileId || null;
const groupId = requestPb.groupId || null;
const keyId = requestPb.keyId || null;
const since = requestPb.since || null;
const messages = requestPb.messages;
if (!since) {
res.status(422).send({
details: 'since-required',
reason: 'unprocessable-entity',
status: 'error',
});
return;
}
const filesService = new FilesService(getAccountDb());
const currentFile = verifyFileExists(
fileId,
filesService,
res,
'file-not-found',
);
if (!currentFile) {
return;View on GitHub (pinned to d4334cb6e6)
Solutions
- Set the since field on the SyncRequest to the last received sync timestamp (use '0' for an initial full sync).
- Re-generate the client protobuf code from the current schema so since is populated.
- If using the official client/API, upgrade it — it sets since automatically.
Example fix
// before
const request = { fileId, groupId, messages };
// after
const request = { fileId, groupId, messages, since: lastSyncedTimestamp || '0' }; Defensive patterns
Strategy: validation
Validate before calling
if (!request.since || request.since === '0' && !allowFullSync) throw new Error('since is required; pass the last sync timestamp or 0 for initial sync'); Type guard
const hasSince = (r) => typeof r.since === 'string' && r.since.length > 0;
Try / catch
try {
const res = await postSync(request);
if (res.status === 422 && (await res.json()).details === 'since-required') {
throw new Error('add a since cursor to the SyncRequest');
}
return res;
} catch (e) { throw e; } Prevention
- Persist the last sync timestamp after every successful sync
- Use since='0' deliberately for full initial syncs
- Regenerate protobuf types after protocol upgrades
When it happens
Trigger: POSTing a well-formed SyncRequest protobuf whose since field is 0/null/absent — e.g. hand-built sync requests or clients that omitted the field after a protocol change.
Common situations: Custom API integrations constructing SyncRequest manually; clients upgraded against an older schema where since was optional; resetting local state and forgetting to seed the cursor (use '0' or the epoch value for a full initial sync).
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- --table is required when the input file lacks a "table" fiel
- `date` is required when adding a transaction
- `payeeName` is required when adding a transaction
- Filter name is required
- Report name is required
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/b24261430220dda6.
Report an issue: GitHub.