Google keeps deleted events, and a read still returns them
Updated
TL;DR
When you read a deleted Google Calendar event by its ID, Google returns 200 with the whole event and "status": "cancelled", not a 404. An update to it succeeds too, renaming an event nobody can see. Treat status "cancelled" as deleted on every single-event read, but keep cancelled entries in incremental syncs, where they are how Google reports deletions.
What we saw
While testing Pyas against real Gmail and Google Workspace accounts on October 27, 2026, we deleted an event and then read it back by its ID.
Observed on a Gmail and a Workspace account, 2026-10-27
GET /calendar/v3/calendars/primary/events/{eventId} for the deleted event answered 200, with the full event: its title, times and attendees, and "status": "cancelled".
PATCH on the same event also answered 200 and changed its title. The event stayed deleted and invisible in Google Calendar.
A 404 or 410 is what most code expects for a deleted resource. Google answers with the event instead, so code that only checks the HTTP status treats a deleted event as live, shows it, and lets users edit it.
Why Google does this
Google Calendar keeps deleted events as cancelled entries so that incremental syncs can report deletions. When you list events with a sync token, a deleted event comes back as a short entry with "status": "cancelled", and that is the only way your copy learns it is gone. Reading the event by ID returns more than that short entry: the whole event, still marked cancelled.
What to do
- On every single-event read (get, and the read before an update, a response to an invitation or a move), treat
status: "cancelled"as not found. - In incremental syncs, keep the cancelled entries: they are deletions to apply, not events to skip.
- Cancelled instances of a recurring series are how Google marks a deleted occurrence. Reading the series still works; the cancelled instance is an exception to it.
How Pyas handles it
Every read of one Google event in Pyas (get, update, respond, move, attachments) treats a cancelled event as deleted, so the API answers 404 with RESOURCE_NOT_FOUND, the same as on Outlook. Lists and syncs keep the cancelled entries, so your event.deleted webhooks still arrive.