google-calendar
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDrive Google Calendar via . The user's OAuth bearer token
is in ; every call needs it as
. At minimum the token
carries plus the identity scopes
(); if the user opted in to write at install
time it also carries the broader scope (read + write).
curl + jq$GOOGLE_CALENDAR_TOKENAuthorization: Bearer $GOOGLE_CALENDAR_TOKENcalendar.readonlyopenid email profilecalendarThe Calendar API returns standard JSON; failures surface as
— show that
error verbatim. means the token expired (re-install). on a write means the user only granted
— ask them to re-install the connector with the
read+write box checked.
{"error": {"code": 401|403|..., "message": "..."}}401403 insufficientPermissionscalendar.readonlyAlways start with to learn which calendars
the account can see (the user's primary plus any subscribed / shared
ones), AND with so you render times in
the user's local zone instead of UTC.
users/me/calendarListusers/me/settings/timezoneBefore any destructive write (creating, moving, or cancelling an
event that has attendees) show the exact event details and ask the
user to confirm. When attendees are involved, also confirm whether
they want Google to email the attendees — that's controlled by the
query parameter.
sendUpdates通过操作Google Calendar。用户的OAuth bearer token存储在中;每次调用都需要将其作为传入。该令牌至少包含权限以及身份范围();如果用户在安装时选择了写入权限,它还会包含更广泛的权限(读写权限)。
curl + jq$GOOGLE_CALENDAR_TOKENAuthorization: Bearer $GOOGLE_CALENDAR_TOKENcalendar.readonlyopenid email profilecalendarCalendar API返回标准JSON格式;失败时会返回 —— 请直接显示该错误信息。表示令牌已过期(需要重新安装)。如果写入操作返回,说明用户仅授予了权限 —— 请提示用户重新安装连接器并勾选读写权限选项。
{"error": {"code": 401|403|..., "message": "..."}}401403 insufficientPermissionscalendar.readonly**始终先调用**以了解该账户可查看的日历(包括用户的主日历以及任何订阅/共享的日历),同时调用,以便按照用户的本地时区而非UTC时区显示时间。
users/me/calendarListusers/me/settings/timezone在执行任何破坏性写入操作之前(创建、移动或取消有参会者的事件),请显示确切的事件详情并请求用户确认。当涉及参会者时,还需确认是否希望Google向参会者发送邮件 —— 这由查询参数控制。
sendUpdatesOptional: Google Workspace CLI (gws
) for agenda + create
gws可选:用于日程查看和创建的Google Workspace CLI (gws
)
gwsgwsgoogleworkspace+Use for two specific cases:
gws- reads the user's account timezone from
+agenda(cached for 24 h) and renders today's events in that zone, so you don't have to fetch the timezone yourself before formatting times.Settings.timezone - shapes the create-event JSON for you (attendees, sendUpdates, reminders) so a one-line invocation produces a well-formed request.
+insert
For everything else (events.list / patch / move / delete, freebusy,
calendarList) the curl recipes below are equivalent and shorter — stay
on those.
gwsgoogleworkspace+在以下两种特定场景中使用:
gws- 会从
+agenda读取用户账户的时区(缓存24小时),并以该时区显示今日事件,因此你无需在格式化时间前自行获取时区。Settings.timezone - 会帮你生成创建事件的JSON结构(参会者、sendUpdates、提醒),因此只需一行命令即可生成格式正确的请求。
+insert
对于其他所有操作(events.list / patch / move / delete、freebusy、calendarList),下面的curl方案功能等效且更简洁 —— 请使用这些方案。
Install
安装
sh
npm install -g @googleworkspace/cli # or: brew install googleworkspace-clish
npm install -g @googleworkspace/cli # 或:brew install googleworkspace-cliPre-built binaries also at https://github.com/googleworkspace/cli/releases
gws --version
undefinedgws --version
undefinedAuth
认证
gwsGOOGLE_WORKSPACE_CLI_TOKEN$GOOGLE_CALENDAR_TOKENgwssh
export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_CALENDAR_TOKEN"gwsGOOGLE_WORKSPACE_CLI_TOKEN$GOOGLE_CALENDAR_TOKENgwssh
export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_CALENDAR_TOKEN"Agenda + create
日程查看与创建
sh
undefinedsh
undefinedToday on the primary calendar, in the account's own timezone
主日历的今日日程,使用账户自身的时区
gws calendar +agenda
gws calendar +agenda
Today / week, with explicit overrides
今日/本周日程,使用显式覆盖参数
gws calendar +agenda --today --tz America/New_York
gws calendar +agenda --range week
gws calendar +agenda --today --tz America/New_York
gws calendar +agenda --range week
Create an event (auto-shapes attendees + sendUpdates JSON)
创建事件(自动生成参会者+sendUpdates JSON结构)
gws calendar +insert --calendar primary
--json '{ "summary":"Standup", "start":{"dateTime":"2026-05-06T10:00:00-04:00"}, "end": {"dateTime":"2026-05-06T10:30:00-04:00"}, "attendees":[{"email":"alice@example.com"}] }'
--params '{"sendUpdates":"all"}'
--json '{ "summary":"Standup", "start":{"dateTime":"2026-05-06T10:00:00-04:00"}, "end": {"dateTime":"2026-05-06T10:30:00-04:00"}, "attendees":[{"email":"alice@example.com"}] }'
--params '{"sendUpdates":"all"}'
Both helpers exit non-zero with a structured JSON error on stderr if
Google rejects the request — surface that verbatim. `+insert` against
attendees requires the broader `calendar` scope; on `403
insufficientPermissions` ask the user to re-install with read+write
checked.gws calendar +insert --calendar primary
--json '{ "summary":"Standup", "start":{"dateTime":"2026-05-06T10:00:00-04:00"}, "end": {"dateTime":"2026-05-06T10:30:00-04:00"}, "attendees":[{"email":"alice@example.com"}] }'
--params '{"sendUpdates":"all"}'
--json '{ "summary":"Standup", "start":{"dateTime":"2026-05-06T10:00:00-04:00"}, "end": {"dateTime":"2026-05-06T10:30:00-04:00"}, "attendees":[{"email":"alice@example.com"}] }'
--params '{"sendUpdates":"all"}'
如果Google拒绝请求,这两个辅助命令都会返回非零退出码,并在stderr输出结构化JSON错误 —— 请直接显示该错误信息。针对参会者执行`+insert`操作需要更广泛的`calendar`权限;如果返回`403 insufficientPermissions`,请提示用户重新安装并勾选读写权限选项。Recipes
操作方案
Verify auth + discover calendars (always run first)
验证认证并发现日历(始终先执行)
sh
undefinedsh
undefinedAccount confirmation + calendars the user can read
账户确认+用户可读取的日历
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
"https://www.googleapis.com/calendar/v3/users/me/calendarList"
| jq '.items[] | {id, summary, primary, accessRole, timeZone}'
"https://www.googleapis.com/calendar/v3/users/me/calendarList"
| jq '.items[] | {id, summary, primary, accessRole, timeZone}'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
"https://www.googleapis.com/calendar/v3/users/me/calendarList"
| jq '.items[] | {id, summary, primary, accessRole, timeZone}'
"https://www.googleapis.com/calendar/v3/users/me/calendarList"
| jq '.items[] | {id, summary, primary, accessRole, timeZone}'
User's preferred display zone (use this when formatting times)
用户偏好的显示时区(格式化时间时使用)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone"
| jq -r .value
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone"
| jq -r .value
The `id` of each calendar (`primary`, or an email-shaped id like
`team-monday@group.calendar.google.com`) is what subsequent
`calendars/{id}/events` calls take.curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone"
| jq -r .value
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone"
| jq -r .value
每个日历的`id`(`primary`,或类似`team-monday@group.calendar.google.com`的邮箱格式id)是后续`calendars/{id}/events`调用所需的参数。Today's agenda on the primary calendar
主日历的今日日程
sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
TODAY=$(TZ=$TZ date +%Y-%m-%d)
START="${TODAY}T00:00:00Z"
END="${TODAY}T23:59:59Z"
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "timeMin=$START" \
--data-urlencode "timeMax=$END" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode "timeZone=$TZ" \
| jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}'singleEvents=truesh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
TODAY=$(TZ=$TZ date +%Y-%m-%d)
START="${TODAY}T00:00:00Z"
END="${TODAY}T23:59:59Z"
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "timeMin=$START" \
--data-urlencode "timeMax=$END" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode "timeZone=$TZ" \
| jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}'singleEvents=trueThis week's meetings (Mon–Sun)
本周会议(周一至周日)
sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)Bash date math: Monday-of-this-week
Bash日期计算:本周一
MON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS fallback SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
|| TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS fallback SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events"
--data-urlencode "timeMin=${MON}T00:00:00Z"
--data-urlencode "timeMax=${SUN}T23:59:59Z"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
| jq -r '.items[] | "(.start.dateTime // .start.date)\t(.summary)\t((.attendees // []) | length) attendees"'
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events"
--data-urlencode "timeMin=${MON}T00:00:00Z"
--data-urlencode "timeMax=${SUN}T23:59:59Z"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
| jq -r '.items[] | "(.start.dateTime // .start.date)\t(.summary)\t((.attendees // []) | length) attendees"'
undefinedMON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS兼容方案 SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
|| TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS兼容方案 SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null
|| TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events"
--data-urlencode "timeMin=${MON}T00:00:00Z"
--data-urlencode "timeMax=${SUN}T23:59:59Z"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
| jq -r '.items[] | "(.start.dateTime // .start.date)\t(.summary)\t((.attendees // []) | length) attendees"'
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events"
--data-urlencode "timeMin=${MON}T00:00:00Z"
--data-urlencode "timeMax=${SUN}T23:59:59Z"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
| jq -r '.items[] | "(.start.dateTime // .start.date)\t(.summary)\t((.attendees // []) | length) attendees"'
undefinedSearch events by query
按查询条件搜索事件
sh
Q='quarterly review'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "q=$Q" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'maxResults=20' \
| jq '.items[] | {start: .start.dateTime, summary, htmlLink}'qsh
Q='quarterly review'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "q=$Q" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'maxResults=20' \
| jq '.items[] | {start: .start.dateTime, summary, htmlLink}'qGet one event's full details (incl. attendees, location, link)
获取单个事件的完整详情(包括参会者、地点、链接)
sh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \
| jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}'sh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \
| jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}'Free / busy across multiple calendars (next 7 days)
多个日历的忙闲状态(未来7天)
sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ)
NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ)
cat > /tmp/freebusy.json <<JSON
{
"timeMin": "$NOW",
"timeMax": "$NEXT_WEEK",
"timeZone": "$TZ",
"items": [
{"id": "primary"},
{"id": "team-monday@group.calendar.google.com"}
]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/freebusy.json \
"https://www.googleapis.com/calendar/v3/freeBusy" \
| jq '.calendars'Each calendar's response is
— gaps between are free.
{"busy": [{"start": "...", "end": "..."}]}sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ)
NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ)
cat > /tmp/freebusy.json <<JSON
{
"timeMin": "$NOW",
"timeMax": "$NEXT_WEEK",
"timeZone": "$TZ",
"items": [
{"id": "primary"},
{"id": "team-monday@group.calendar.google.com"}
]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/freebusy.json \
"https://www.googleapis.com/calendar/v3/freeBusy" \
| jq '.calendars'每个日历的响应格式为 —— 时间段之间的间隙即为空闲时间。
{"busy": [{"start": "...", "end": "..."}]}List events on a non-primary calendar
列出非主日历的事件
sh
CAL_ID='team-monday@group.calendar.google.com'sh
CAL_ID='team-monday@group.calendar.google.com'URL-encode the @ in the path
对路径中的@进行URL编码
CAL_ENCODED=$(printf %s "$CAL_ID" | jq -sRr @uri)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
--get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
--data-urlencode 'maxResults=20'
| jq '.items[] | {start: .start.dateTime, summary}'
--get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
--data-urlencode 'maxResults=20'
| jq '.items[] | {start: .start.dateTime, summary}'
undefinedCAL_ENCODED=$(printf %s "$CAL_ID" | jq -sRr @uri)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
--get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
--data-urlencode 'maxResults=20'
| jq '.items[] | {start: .start.dateTime, summary}'
--get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events"
--data-urlencode 'singleEvents=true'
--data-urlencode 'orderBy=startTime'
--data-urlencode 'maxResults=20'
| jq '.items[] | {start: .start.dateTime, summary}'
undefinedPagination
分页查询
sh
PAGE_TOKEN=''
while : ; do
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode 'maxResults=250' \
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
echo "$RESP" | jq -c '.items[]?'
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
[ -z "$PAGE_TOKEN" ] && break
donesh
PAGE_TOKEN=''
while : ; do
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode 'maxResults=250' \
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
echo "$RESP" | jq -c '.items[]?'
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
[ -z "$PAGE_TOKEN" ] && break
doneWrite recipes
写入操作方案
These all need the broader scope. If the user only granted
you'll get —
surface that and ask them to re-install with the read+write box
checked. Always echo the event summary, time and attendee list
back to the user before creating or cancelling anything.
calendarcalendar.readonly403 insufficientPermissions这些操作都需要更广泛的权限。如果用户仅授予了权限,你会收到错误 —— 请显示该错误并提示用户重新安装连接器并勾选读写权限选项。在创建或取消任何事件之前,务必向用户回显事件摘要、时间和参会者列表。
calendarcalendar.readonly403 insufficientPermissionsCreate a single event (with optional attendees + Google Meet link)
创建单个事件(可选参会者+Google Meet链接)
sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_event.json <<JSON
{
"summary": "Sync — Q2 OKR review",
"location": "Online",
"description": "Drafted by AceDataCloud.",
"start": {"dateTime": "2026-05-12T10:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T10:30:00", "timeZone": "$TZ"},
"attendees": [
{"email": "alice@example.com"},
{"email": "bob@example.com"}
],
"reminders": {"useDefault": true},
"conferenceData": {
"createRequest": {
"requestId": "meet-$(date +%s)",
"conferenceSolutionKey": {"type": "hangoutsMeet"}
}
}
}
JSONsh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_event.json <<JSON
{
"summary": "Sync — Q2 OKR review",
"location": "Online",
"description": "Drafted by AceDataCloud.",
"start": {"dateTime": "2026-05-12T10:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T10:30:00", "timeZone": "$TZ"},
"attendees": [
{"email": "alice@example.com"},
{"email": "bob@example.com"}
],
"reminders": {"useDefault": true},
"conferenceData": {
"createRequest": {
"requestId": "meet-$(date +%s)",
"conferenceSolutionKey": {"type": "hangoutsMeet"}
}
}
}
JSONsendUpdates: 'all' = email all attendees; 'externalOnly' = only non-org; 'none' = silent
sendUpdates: 'all' = 向所有参会者发送邮件; 'externalOnly' = 仅向外部参会者发送; 'none' = 不发送邮件
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
-H 'Content-Type: application/json'
--data @/tmp/_cal_event.json
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all"
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
-H 'Content-Type: application/json'
--data @/tmp/_cal_event.json
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all"
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
Drop the `conferenceData` block if the user didn't ask for a Meet
link — it'll fall back to a plain event.curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN"
-H 'Content-Type: application/json'
--data @/tmp/_cal_event.json
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all"
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
-H 'Content-Type: application/json'
--data @/tmp/_cal_event.json
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all"
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
如果用户不需要Meet链接,可删除`conferenceData`块 —— 事件会退化为普通事件。Create a recurring event
创建重复事件
sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_recur.json <<JSON
{
"summary": "Weekly 1:1",
"start": {"dateTime": "2026-05-12T15:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T15:30:00", "timeZone": "$TZ"},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_recur.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events" \
| jq '{id, recurrence, summary}'RRULE follows RFC 5545. Common patterns: , ,
. Add or
for a hard stop.
FREQ=DAILYFREQ=WEEKLY;BYDAY=MO,WE,FRFREQ=MONTHLY;BYMONTHDAY=15UNTIL=20261231T235959ZCOUNT=12sh
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_recur.json <<JSON
{
"summary": "Weekly 1:1",
"start": {"dateTime": "2026-05-12T15:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T15:30:00", "timeZone": "$TZ"},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_recur.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events" \
| jq '{id, recurrence, summary}'RRULE遵循RFC 5545标准。常见模式:、、。可添加或设置结束条件。
FREQ=DAILYFREQ=WEEKLY;BYDAY=MO,WE,FRFREQ=MONTHLY;BYMONTHDAY=15UNTIL=20261231T235959ZCOUNT=12Update an existing event (PATCH — partial update)
更新现有事件(PATCH —— 部分更新)
sh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"location":"Conference Room 4","description":"Now in-person."}' \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, location, description}'PATCHPUTPATCHsh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"location":"Conference Room 4","description":"Now in-person."}' \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, location, description}'PATCHPUTPATCHReschedule an event
重新安排事件
sh
EVENT_ID='abc123def4567890ghijklmnop'
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_resched.json <<JSON
{
"start": {"dateTime": "2026-05-12T14:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T14:30:00", "timeZone": "$TZ"}
}
JSON
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_resched.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, start, end}'sh
EVENT_ID='abc123def4567890ghijklmnop'
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_resched.json <<JSON
{
"start": {"dateTime": "2026-05-12T14:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T14:30:00", "timeZone": "$TZ"}
}
JSON
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_resched.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, start, end}'Add or change attendees
添加或修改参会者
Google requires you to send the complete attendee list when
patching attendees — fetch the current list, mutate, send back:
sh
EVENT_ID='abc123def4567890ghijklmnop'
CURRENT=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?fields=attendees" \
| jq '.attendees // []')
NEW=$(echo "$CURRENT" | jq '. + [{"email":"carol@example.com"}]')
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"attendees\": $NEW}" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, attendees}'Google要求在修改参会者时发送完整的参会者列表 —— 先获取当前列表,修改后再发送回去:
sh
EVENT_ID='abc123def4567890ghijklmnop'
CURRENT=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?fields=attendees" \
| jq '.attendees // []')
NEW=$(echo "$CURRENT" | jq '. + [{"email":"carol@example.com"}]')
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"attendees\": $NEW}" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, attendees}'Cancel / delete an event
取消/删除事件
sh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
-o /dev/null -w 'HTTP %{http_code}\n'204events.instancesDELETEEVENT_ID_YYYYMMDDTHHMMSSZsh
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
-o /dev/null -w 'HTTP %{http_code}\n'204events.instancesDELETEEVENT_ID_YYYYMMDDTHHMMSSZCommon error codes
常见错误码
| HTTP | meaning | what to tell the user |
|---|---|---|
| token expired / revoked | "Reconnect the Google Calendar connector on the Connections page." |
| write scope missing | "This action needs the Calendar read+write scope, but only |
| calendar id not visible to this account | check |
| wrong event / calendar id | double-check the id and try |
| recurring event id collision | append a UUID to your |
| quota / throttling | back off ~5s, then retry once. |
Never log or echo — treat it as a secret.
$GOOGLE_CALENDAR_TOKEN| HTTP | 含义 | 提示用户的内容 |
|---|---|---|
| 令牌过期/已撤销 | "在连接页面重新连接Google Calendar连接器。" |
| 缺少写入权限 | "此操作需要Calendar读写权限,但当前仅授予了 |
| 该账户无法查看此日历id | 先检查 |
| 事件/日历id错误 | 仔细检查id,并尝试调用 |
| 重复事件id冲突 | 在 |
| 配额/限流 | 等待约5秒后重试一次。 |
切勿记录或显示 —— 将其视为机密信息。
$GOOGLE_CALENDAR_TOKEN