google-tasks

Installation
SKILL.md

Drive Google Tasks via curl + jq. The user's OAuth bearer token is in $GOOGLE_TASKS_TOKEN; every call needs it as Authorization: Bearer $GOOGLE_TASKS_TOKEN. At minimum the token carries tasks.readonly plus the identity scopes (openid email profile); if the user opted in to write at install time it also carries the broader tasks scope (read + write).

The Tasks API returns standard JSON; failures surface as {"error": {"code": 401|403|..., "message": "..."}} — show that error verbatim. 401 means the token expired (re-install). 403 insufficientPermissions on a write means the user only granted tasks.readonly — ask them to re-install with the read+write box checked.

Always start with users/@me/lists to discover which task lists the account has — the user's default plus any extras they created on calendar.google.com or in the Tasks app.

Before bulk creates / completions / deletes echo the exact titles back to the user and ask them to confirm. Don't trash a task by guessing an id.

Recipes

Verify auth + list all task lists (always run first)

curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq '.items[] | {id, title, updated}'

The default list is usually titled "我的任务" / "My Tasks" but the id (a long opaque string like MTAxMjM0NTY3OA) is what every subsequent lists/{id}/tasks call needs.

List all unfinished tasks across every list

curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
    curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
      --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
      --data-urlencode 'showCompleted=false' \
      --data-urlencode 'maxResults=100' \
      | jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, status, notes}'
  done | jq -s '. | sort_by(.due // "9999")'

showCompleted=false filters out done items at the API level. The default showCompleted=true&showHidden=false returns done tasks too.

Pending tasks in one specific list, sorted by due date

LIST_ID='MTAxMjM0NTY3OA'
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
  --data-urlencode 'showCompleted=false' \
  --data-urlencode 'maxResults=100' \
  | jq '.items // [] | sort_by(.due // "9999") | .[] | {title, due, notes, status, position}'

position is the user's drag-to-reorder rank inside the list — useful when the user says "what's at the top of my tasks". Tasks without a due field are open-ended.

Tasks due today

TODAY=$(date -u +%Y-%m-%d)
TOMORROW=$(date -u -d "+1 day" +%Y-%m-%d 2>/dev/null \
  || date -u -v+1d +%Y-%m-%d)
TODAY_START="${TODAY}T00:00:00.000Z"
TOMORROW_START="${TOMORROW}T00:00:00.000Z"

curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
    curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
      --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
      --data-urlencode "dueMin=$TODAY_START" \
      --data-urlencode "dueMax=$TOMORROW_START" \
      --data-urlencode 'showCompleted=false' \
      | jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, notes}'
  done | jq -s

dueMin / dueMax are RFC 3339 timestamps. The Tasks API stores due at midnight UTC, so the local-day window is approximate around the date boundary — that's fine for "due today" semantics, mention the caveat only if the user pushes back.

Overdue tasks (everything still pending with a due date in the past)

NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
    curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
      --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
      --data-urlencode "dueMax=$NOW" \
      --data-urlencode 'showCompleted=false' \
      | jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, daysOverdue: (((now * 1000) - (.due | sub("Z"; "+00:00") | fromdateiso8601 * 1000)) / 86400000 | floor)}'
  done | jq -s

Recently completed tasks (this week, for a recap)

ONE_WEEK_AGO=$(date -u -d "-7 days" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null \
  || date -u -v-7d +%Y-%m-%dT%H:%M:%S.000Z)

curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
    curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
      --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
      --data-urlencode 'showCompleted=true' \
      --data-urlencode 'showHidden=true' \
      --data-urlencode "completedMin=$ONE_WEEK_AGO" \
      | jq --arg list "$LIST_TITLE" '.items[]? | select(.status=="completed") | {list: $list, title, completed}'
  done | jq -s '. | sort_by(.completed)'

completedMin / completedMax mirror dueMin/Max and only apply to tasks already moved to the "completed" state. You must pass showCompleted=true AND showHidden=true to see them — Google hides completed tasks from the default list.

Get one task's details

LIST_ID='MTAxMjM0NTY3OA'
TASK_ID='dGFza0lkRXhhbXBsZQ'
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
  | jq '{title, due, status, notes, completed, position, links: .links}'

links exposes the user's manual hyperlinks (e.g. an attached email or Drive doc) — render them as a list to the user when present.

Pagination

maxResults caps at 100 per page. Use nextPageToken:

LIST_ID='MTAxMjM0NTY3OA'
PAGE_TOKEN=''
while : ; do
  RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
    --get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
    --data-urlencode 'maxResults=100' \
    --data-urlencode 'showCompleted=false' \
    ${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
  echo "$RESP" | jq -c '.items[]?'
  PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
  [ -z "$PAGE_TOKEN" ] && break
done

Write recipes

These all need the broader tasks scope. If the user only granted tasks.readonly you'll get 403 insufficientPermissions — surface that and ask them to re-install with the read+write box checked.

Add a new task

LIST_ID='MTAxMjM0NTY3OA'
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"title":"Draft Q2 plan","notes":"Outline + risks + asks.","due":"2026-05-15T00:00:00.000Z"}' \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
  | jq '{id, title, due, status}'

Google stores due as midnight UTC of the chosen day — the time of day is ignored in the UI. To insert at the very top of the list, add ?previous= (no value) to the URL.

Bulk add three tasks under user confirmation

LIST_ID='MTAxMjM0NTY3OA'
DUE='2026-05-12T00:00:00.000Z'
for T in 'Reply to Alice' 'Review PR #404' 'Send meeting recap'; do
  curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
    -H 'Content-Type: application/json' \
    --data "{\"title\":$(jq -nr --arg t "$T" '$t'),\"due\":\"$DUE\"}" \
    "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
    | jq -c '{id, title, due}'
done

Always list the titles you're about to create and ask for the user's go-ahead before running this loop — there is no atomic batch endpoint.

Mark a task complete

LIST_ID='MTAxMjM0NTY3OA'
TASK_ID='dGFza0lkRXhhbXBsZQ'
NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data "{\"status\":\"completed\",\"completed\":\"$NOW\"}" \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
  | jq '{id, title, status, completed}'

Reverse with {"status":"needsAction","completed":null}.

Edit a task's title / notes / due date

LIST_ID='MTAxMjM0NTY3OA'
TASK_ID='dGFza0lkRXhhbXBsZQ'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"title":"Draft Q2 plan (rev2)","notes":"Cover risks + asks + budget.","due":"2026-05-20T00:00:00.000Z"}' \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
  | jq '{id, title, due, notes}'

Delete a task

LIST_ID='MTAxMjM0NTY3OA'
TASK_ID='dGFza0lkRXhhbXBsZQ'
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
  -o /dev/null -w 'HTTP %{http_code}\n'

204 = success. There is no soft-delete — once gone the task is gone. Echo the title back before deleting.

Re-order: move a task to a position

LIST_ID='MTAxMjM0NTY3OA'
TASK_ID='dGFza0lkRXhhbXBsZQ'
PREV='dGFza0lkUHJldg'  # task id this one should appear AFTER; omit to move to top
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  --data '' \
  "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID/move?previous=$PREV" \
  | jq '{id, title, parent, position}'

Use ?parent=... instead of ?previous=... to nest a task under another task as a sub-task.

Create a brand-new task list

curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"title":"Q2 follow-ups"}' \
  "https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
  | jq '{id, title}'

Common error codes

HTTP meaning what to tell the user
401 UNAUTHENTICATED token expired / revoked "Reconnect the Google Tasks connector on the Connections page."
403 insufficientPermissions write scope missing "This action needs the Tasks read+write scope, but only tasks.readonly was granted. Re-install the connector with the read+write box checked."
404 notFound wrong list / task id re-list with users/@me/lists to find the right id.
429 quotaExceeded quota / throttling back off ~5s, then retry once.

Never log or echo $GOOGLE_TASKS_TOKEN — treat it as a secret.

Related skills

More from acedatacloud/skills

Installs
1
GitHub Stars
5
First Seen
2 days ago