Search code examples
google-apigoogle-calendar-api

Google Calendar 403 Quota Errors when trying to insert, update events even with plenty of headroom within the quota


First things first - yes, this issue is very simillar to others in the past, but the behaviour is the same, and no mentioned "fixes" work.

Issue

What are we trying to do?

I want to include this in case I am doing something completely off - we want to move certain events to a different calendar according to our client's account configuration.

  • All our clients also use Google Workspace, but the end users (people who would meet with our clients - need to be notified about the event) may not be using Google, so we need to send invitations.

  • The initial solution involved a call to the update method that would add the destination calendar as an attendee & remove the "source" calendar, then a call to the move method to actually move the event. In some cases (I'd say < 25-30%, this worked)

  • I have now tried this - to create a new event with all the wanted (everyone originally on it, minus the source and plus the destination) attendees on it, then delete the original event.

What's happening

  • When I try to use the API Explorer on a personal account, both solutions work really well.

  • Whenever we deploy them... the result is.. this issue.

Now, I should say that we do this calendar management through 2 of our accounts (say worker1@our-domain.com and worker2@our-domain.com), that are logged in into our management platform via OAuth with all calendar scopes!

My first thought was that maybe we're reaching the "Queries per minute per user", but... we're nowhere close to it. The maximum we've done (because I put a lot - and I mean a lot - of throttling to make sure we aren't overusing anything) in the past 2 days is under 50 Queries per minute.

Other details

  • We have paid over $100 to Google Cloud, our domain is verified, but the 60-day period has not passed. But honestly... this does not make sense seeing as free tier users can do a lot more than we can.

  • Even if we get the basic update + move back up and running, this is unacceptable as we're working on new features that depend on the ability to create new events.

  • I have also opened a ticked on the Google Issue Tracker here, but decided to also post on SO to check with the community.

Code

  • This snippet and the actual method in our codebase essentially try to do what a "move" does, because that was my last attempt to get this to work - seeing as calendar.events.update would also result in the exact 403 error.
// This is the code sequence part of the method that is supposed to handle the creation of
// this new event.
//
// backOff is the "backOff" func. exported by the "exponential-backoff" module.
// "originalEvent" is an event we're using for some data

// ...

// Keep all attendees except the organizer
const newAttendees =
  originalEvent.attendees?.filter((at) => at.email !== sourceEmail) ?? [];

// Add the new organizer to the attendees list
newAttendees.push({
  email: destinationCalendar,
  responseStatus: "needsAction",
});

// ...

const movedEventContents: GCalEvent = {
  summary: newSummary, // Defined outside of this snippet
  description: newDescription, // Defined outside of this snippet
  start: originalEvent.start,
  end: originalEvent.end,
  attendees: newAttendees,
  // ...
};

let createdEvent: GCalEvent;

try {
  const createRes = await backOff(
    () => __createEvent(calendar, destinationCalendar, movedEventContents), // See impl. below
    BACKOFF_OPTIONS, // The options are: { jitter: "full", startingDelay: 2000, maxDelay: 125000 }
  );

  createdEvent = createRes.data;
} catch (err) {
  Log.rawError(err);
  // Eventually, we will always get here, with the infamous 403 "Quota" error (view below as well)
  return { ok: false };
}

// If this were successful, next up "originalEvent" would get deleted, but we never get there anyway

// Wrapper for calendar.events.insert. See usage in code block above.
const __createEvent = (calendar: calendar_v3.Calendar, calendarId: string, eventData: GCalEvent) => {
  return calendar.events.insert({ calendarId, requestBody: eventData, sendUpdates: "all" });
};

Results

  • It seems that in 100% of the cases we end up with a 403 Quota Exceeded error:
{
    domain: 'usageLimits',
    reason: 'quotaExceeded',
    message: 'Calendar usage limits exceeded.'
}

Solution

  • It seems like this is the intended behaviour. When moving/creating/etc events, you are not supposed to send notification emails as that will trigger spam protection, thus the "rate limiting" on certain methods.

    The solution is to set sendUpdates to none on the affected methods, and handle sending emails using a 3rd party service/etc.