actualbudget/actual · error · APIError

Schedule ${id} not found

Error message

Schedule ${id} not found

What it means

api.updateSchedule() looks up the schedule row via an AQL query before mutating conditions. If no schedule with that id exists in the schedules table, the handler throws this APIError naming the missing id.

Source

Thrown at packages/loot-core/src/server/api.ts:952

  const partialSchedule = {
    name: internalSchedule.name,
    posts_transaction: internalSchedule.posts_transaction,
  };
  return handlers['schedule/create']({
    schedule: partialSchedule,
    conditions: internalSchedule._conditions,
  });
});

handlers['api/schedule-update'] = withMutation(async function ({
  id,
  fields,
  resetNextDate,
}) {
  checkFileOpen();
  const { data } = await aqlQuery(q('schedules').filter({ id }).select('*'));
  if (!data || data.length === 0) {
    throw APIError(`Schedule ${id} not found`);
  }

  const sched = data[0] as ScheduleEntity;
  let conditionsUpdated = false;
  // Find all indices to avoid direct assignment
  const payeeIndex = sched._conditions.findIndex(c => c.field === 'payee');
  const accountIndex = sched._conditions.findIndex(c => c.field === 'account');
  const dateIndex = sched._conditions.findIndex(c => c.field === 'date');
  const amountIndex = sched._conditions.findIndex(c => c.field === 'amount');

  for (const key in fields) {
    const typedKey = key as keyof APIScheduleEntity;
    const value = fields[typedKey];

    switch (typedKey) {
      case 'name': {
        const newName = String(value);
        const { data: existing } = await aqlQuery(

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the id by listing schedules and matching (via aqlQuery on q('schedules') or the UI)
  2. Re-fetch schedule ids after any budget import/reset — don't persist schedule ids across budget rebuilds
  3. Log the id being passed and confirm the producer of that id returns schedule entities

Example fix

// before
await api.updateSchedule(someRow.id, fields);
// after
const { data } = await api.aqlQuery(q('schedules').filter({ id: someRow.id }).select('*'));
if (data && data.length) await api.updateSchedule(someRow.id, fields);
Defensive patterns

Strategy: validation

Validate before calling

const { data } = await aqlQuery(q('schedules').filter({ id }).select('*'));
if (!data || data.length === 0) throw APIError(`Schedule ${id} not found`);

Type guard

function scheduleRowExists(res) {
  return Array.isArray(res?.data) && res.data.length > 0;
}

Try / catch

try {
  await updateSchedule(id, fields);
} catch (e) {
  if (e instanceof APIError && e.message.includes('not found')) {
    return { ok: false, reason: 'missing-schedule' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling api.updateSchedule(id, fields, resetNextDate) with an id that is not a schedule id (e.g. a transaction or account id), an id from a different budget, or an id of a schedule already deleted.

Common situations: Stale references after re-importing or recreating a budget; passing the payee id or transaction id instead of the schedule id; concurrent deletion of the schedule between fetch and update.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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