calcom/cal.diy · error · NotFoundException

Calendar credentials not found

Error message

Calendar credentials not found

What it means

Thrown by CalendarsService.checkCalendarCredentials (calendars.service.ts:199) as NotFoundException (HTTP 404) when calendarsRepository.getCalendarCredentials(credentialId, userId) returns null — the credentialId does not exist or does not belong to the user. Used as the guard at the start of the disconnect endpoint.

Source

Thrown at apps/api/v2/src/platform/calendars/services/calendars.service.ts:199

    }

    const { client_id, client_secret } = this.oAuthCalendarResponseSchema.parse(app.keys);

    if (!client_id) {
      throw new NotFoundException();
    }

    if (!client_secret) {
      throw new NotFoundException();
    }

    return { client_id, client_secret };
  }

  async checkCalendarCredentials(credentialId: number, userId: number) {
    const credential = await this.calendarsRepository.getCalendarCredentials(credentialId, userId);
    if (!credential) {
      throw new NotFoundException("Calendar credentials not found");
    }
  }

  async createAndLinkCalendarEntry(
    userId: number,
    externalId: string,
    key: Prisma.InputJsonValue,
    calendarType: keyof typeof APPS_TYPE_ID_MAPPING,
    credentialId?: number | null
  ) {
    const credential = await this.credentialsRepository.upsertUserAppCredential(
      calendarType,
      key,
      userId,
      credentialId
    );

    await this.selectedCalendarsRepository.upsertSelectedCalendar(

View on GitHub (pinned to 176037d0af)

Solutions

  1. Use a credentialId sourced from GET /v2/calendars for the current user.
  2. Treat a 404 on disconnect as 'already disconnected' (idempotent) in the client rather than an error.
  3. Guard the UI: disable the disconnect button once the credential is no longer listed.

Example fix

// before: client treats 404 as fatal
try { await api.post('/v2/calendars/google_calendar/disconnect', { id: credId }); }
catch (e) { throw e; }

// after: treat 404 as already-disconnected
try { await api.post('/v2/calendars/google_calendar/disconnect', { id: credId }); }
catch (e) {
  if (e.response?.status !== 404) throw e;
  // already gone — refresh the list
  await refreshCalendars();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify ownership before disconnect
const { data } = await api.get('/v2/calendars');
const owned = data.connectedCalendars.some(c => c.credentialId === credentialId);
if (!owned) {
  // already disconnected — nothing to do
  return { status: 'already_disconnected' };
}
await api.post('/v2/calendars/google_calendar/disconnect', { id: credentialId });

Type guard

function isOwnedCredential(list, credentialId) {
  return Array.isArray(list) && list.some(c => c?.credentialId === credentialId);
}

Try / catch

try {
  await api.post('/v2/calendars/google_calendar/disconnect', { id: credentialId });
} catch (e) {
  if (e.response?.status === 404) {
    // idempotent: already gone
    return { status: 'already_disconnected' };
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /v2/calendars/{calendar}/disconnect with a credentialId already deleted or owned by another user; double-submit of disconnect; client passing a stale id.

Common situations: User clicks disconnect twice; client holds a stale id after a previous disconnect; cross-user id passed accidentally.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/0a1852363991cf58. Report an issue: GitHub.