calcom/cal.diy · error · NotFoundException

Deleted link not found

Error message

Deleted link not found

What it means

Thrown by PrivateLinksService.deletePrivateLink when repo.delete returns count === 0, meaning no row matched the (eventTypeId, linkId) pair. This is a clean not-found signal: the delete affected nothing, so the link either never existed, was already deleted, or belongs to a different event type.

Source

Thrown at apps/api/v2/src/platform/event-types-private-links/services/private-links.service.ts:109

        usageCount: updated.usageCount ?? 0,
      };
      return this.outputService.transformToOutput(mapped);
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes("not found")) {
          throw new NotFoundException(error.message);
        }
        throw new BadRequestException(error.message);
      }
      throw new BadRequestException("Failed to update private link");
    }
  }

  async deletePrivateLink(eventTypeId: number, linkId: string): Promise<void> {
    try {
      const { count } = await this.repo.delete(eventTypeId, linkId);
      if (count === 0) {
        throw new NotFoundException("Deleted link not found");
      }
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes("not found")) {
          throw new NotFoundException(error.message);
        }
        throw new BadRequestException(error.message);
      }
      throw new BadRequestException("Failed to delete private link");
    }
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Treat a 404 on DELETE as success if your operation is idempotent (the link is gone either way).
  2. Refresh the link list via GET before showing a delete button so the user cannot target a stale linkId.
  3. Confirm the eventTypeId in the URL path is the same one used when the link was created.
  4. If you need strict idempotency, catch the 404 and return a no-content success to your caller.

Example fix

// before
await api.delete(`/v2/event-types/${eventTypeId}/private-links/${linkId}`);
// after - treat already-gone as success
try {
  await api.delete(`/v2/event-types/${eventTypeId}/private-links/${linkId}`);
} catch (e) {
  if (e.response?.status !== 404) throw e;
  // link already deleted; idempotent success
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the link exists before DELETE
const links = (await api.get(`/v2/event-types/${eventTypeId}/private-links`)).data ?? [];
if (!links.some((l) => l.id === linkId)) {
  // already gone — treat as success
  return;
}

Type guard

function isPrivateLinkList(v: unknown): v is Array<{ id: string }> {
  return Array.isArray(v) && v.every((x) => typeof x === 'object' && x !== null && typeof (x as any).id === 'string');
}

Try / catch

try {
  await api.delete(`/v2/event-types/${eventTypeId}/private-links/${linkId}`);
} catch (e) {
  if (e.response?.status === 404) return; // idempotent success
  throw e;
}

Prevention

When it happens

Trigger: DELETE /v2/event-types/:eventTypeId/private-links/:linkId where the linkId does not exist for that eventTypeId; the link was already deleted in a prior request; the eventTypeId in the path does not own the linkId.

Common situations: Idempotent delete retries (second DELETE after the first succeeded); linkId copied from a different event type; expired links auto-pruned by a background job between the GET and DELETE; frontend caching a stale link list.

Related errors


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