Google Tasks API 使ってみる
Google Tasks API を REST で扱うときに、最初にハマりやすい点を含めて、実際に操作できる形でまとめる。 対象は公式の REST Reference とその関連ページ(認可・Quickstart)です。 REST Reference: スコープ: Quickstart (JavaScript): リソ…
Google Tasks API を REST で扱うときに、最初にハマりやすい点を含めて、実際に操作できる形でまとめる。
対象は公式の REST Reference とその関連ページ(認可・Quickstart)です。
- REST Reference: Google Tasks API
- スコープ: Choose Google Tasks API scopes
- Quickstart (JavaScript): JavaScript quickstart
- リソース詳細: tasklists, tasks
1. まず押さえる全体像
Google Tasks API のサービスエンドポイントは次。
https://tasks.googleapis.com
主要リソースは2つだけ。
tasklists: タスクリストの単位(例: 仕事 / 個人)tasks: 各タスクリスト配下のタスク
代表的な HTTP パス:
- リスト一覧:
GET /tasks/v1/users/@me/lists - タスク一覧:
GET /tasks/v1/lists/{tasklist}/tasks - タスク作成:
POST /tasks/v1/lists/{tasklist}/tasks - タスク更新(部分):
PATCH /tasks/v1/lists/{tasklist}/tasks/{task} - タスク更新(全体):
PUT /tasks/v1/lists/{tasklist}/tasks/{task} - タスク削除:
DELETE /tasks/v1/lists/{tasklist}/tasks/{task} - タスク移動:
POST /tasks/v1/lists/{tasklist}/tasks/{task}/move
2. 認証とスコープ
REST 呼び出しには OAuth 2.0 のアクセストークン(Bearer)が必要。
用途ごとのスコープ:
- 読み取りのみ:
https://www.googleapis.com/auth/tasks.readonly - 作成/更新/削除あり:
https://www.googleapis.com/auth/tasks
最小権限の原則で、最初は tasks.readonly から始めるのが安全。
3. まずは最短で叩く(curl)
以下では ACCESS_TOKEN を取得済みとして説明。
export ACCESS_TOKEN='ya29...'
3-1. タスクリスト一覧を取る
curl -s \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://tasks.googleapis.com/tasks/v1/users/@me/lists"
3-2. あるリストのタスク一覧を取る
TASKLIST_ID='xxxxx'
curl -s \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks?maxResults=100&showCompleted=true&showHidden=true"
tasks.list はフィルタが多い。
- 期間系:
dueMin,dueMax,completedMin,completedMax,updatedMin - 表示系:
showCompleted,showDeleted,showHidden,showAssigned - ページング:
maxResults(最大100),pageToken
補足: 公式では、tasks.list はデフォルトでは割り当てタスク(Docs / Chat Spaces 由来)を返さないと明記されている。必要なら showAssigned=true。
3-3. タスク作成
curl -s -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "APIで作ったタスク",
"notes": "本文メモ"
}' \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks"
親子関係や並び順を指定するときは、クエリで parent と previous を使える。
3-4. タスクを部分更新(PATCH)
TASK_ID='yyyyy'
curl -s -X PATCH \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "タイトルだけ変更"
}' \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks/${TASK_ID}"
PATCH は部分更新。1項目だけ変えたいときは基本これで十分。
3-5. タスク完了にする
curl -s -X PATCH \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "completed"
}' \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks/${TASK_ID}"
未完了へ戻すなら status: "needsAction"。
3-6. タスク削除
curl -s -X DELETE \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks/${TASK_ID}"
3-7. タスクの順序/階層を移動
curl -s -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://tasks.googleapis.com/tasks/v1/lists/${TASKLIST_ID}/tasks/${TASK_ID}/move?parent=PARENT_TASK_ID&previous=PREV_TASK_ID"
- 先頭に置くなら
previousを省略 - トップレベルに戻すなら
parentを省略
4. Task リソースで知っておくと得する点
tasks リソースの主なフィールドは title, notes, status, due, completed, deleted, hidden など。
実務で重要なのは次。
dueは日付情報中心で、時刻は API 設定時に保持されない(時間まで厳密管理する用途には不向き)positionは読み取り専用。順序変更はmoveを使うhiddenは読み取り専用。clear実行後の完了タスク表示に関係assignmentInfoは割り当てタスク向けのコンテキスト情報
5. よくある落とし穴
PATCHではなくPUTを使って意図せず全体更新してしまう- 完了タスクが見えず「消えた」と誤解する(
showHidden/showCompletedを確認) - 参照だけのつもりで
tasksスコープを要求してしまう(最小権限にする) - タスクIDではなくリストIDを渡して 404 になる(
tasklistとtaskの取り違え)
6. 実装方針のおすすめ
- 初期は
tasklists.listとtasks.listだけで読み取り機能を完成させる - 更新系は
PATCHを中心にして差分更新に寄せる - ページング(
nextPageToken)を先に実装しておく - 割り当てタスクを扱うなら
showAssignedとassignmentInfoを設計に含める
7. Next.js で使うなら(実装サンプル)
ここでは「ブラウザから直接 Google API を叩かず、Next.js サーバー経由で叩く」構成にする。
アクセストークンはサーバー側で管理する前提。
7-1. 共通ヘルパー(App Router / Pages Router どちらでも使える)
src/lib/googleTasks.ts みたいな場所に置く想定。
const TASKS_BASE_URL = "https://tasks.googleapis.com/tasks/v1";
export async function listTasks(params: {
accessToken: string;
tasklistId: string;
showCompleted?: boolean;
}) {
const { accessToken, tasklistId, showCompleted = true } = params;
const query = new URLSearchParams({
maxResults: "100",
showCompleted: String(showCompleted),
showHidden: "true",
});
const res = await fetch(
`${TASKS_BASE_URL}/lists/${tasklistId}/tasks?${query.toString()}`,
{
headers: { Authorization: `Bearer ${accessToken}` },
cache: "no-store",
}
);
if (!res.ok) {
const body = await res.text();
throw new Error(`Google Tasks list failed: ${res.status} ${body}`);
}
return res.json();
}
export async function insertTask(params: {
accessToken: string;
tasklistId: string;
title: string;
notes?: string;
}) {
const { accessToken, tasklistId, title, notes } = params;
const res = await fetch(`${TASKS_BASE_URL}/lists/${tasklistId}/tasks`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ title, notes }),
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Google Tasks insert failed: ${res.status} ${body}`);
}
return res.json();
}
7-2. App Router: Route Handler 例
src/app/api/google-tasks/[tasklistId]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { insertTask, listTasks } from "@/lib/googleTasks";
function getAccessTokenFromServerSession() {
// 実際は DB / セッションストアから取り出す
const token = process.env.GOOGLE_ACCESS_TOKEN;
if (!token) throw new Error("GOOGLE_ACCESS_TOKEN is missing");
return token;
}
export async function GET(
_req: NextRequest,
{ params }: { params: { tasklistId: string } }
) {
try {
const accessToken = getAccessTokenFromServerSession();
const data = await listTasks({
accessToken,
tasklistId: params.tasklistId,
showCompleted: true,
});
return NextResponse.json(data);
} catch (e) {
return NextResponse.json(
{ error: e instanceof Error ? e.message : "unknown error" },
{ status: 500 }
);
}
}
export async function POST(
req: NextRequest,
{ params }: { params: { tasklistId: string } }
) {
try {
const body = (await req.json()) as { title?: string; notes?: string };
if (!body.title) {
return NextResponse.json({ error: "title is required" }, { status: 400 });
}
const accessToken = getAccessTokenFromServerSession();
const data = await insertTask({
accessToken,
tasklistId: params.tasklistId,
title: body.title,
notes: body.notes,
});
return NextResponse.json(data, { status: 201 });
} catch (e) {
return NextResponse.json(
{ error: e instanceof Error ? e.message : "unknown error" },
{ status: 500 }
);
}
}
7-3. Pages Router: API Route 例
src/pages/api/google-tasks/[tasklistId].ts
import type { NextApiRequest, NextApiResponse } from "next";
import { insertTask, listTasks } from "@/lib/googleTasks";
function getAccessTokenFromServerSession() {
const token = process.env.GOOGLE_ACCESS_TOKEN;
if (!token) throw new Error("GOOGLE_ACCESS_TOKEN is missing");
return token;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const tasklistId = req.query.tasklistId as string;
const accessToken = getAccessTokenFromServerSession();
if (req.method === "GET") {
const data = await listTasks({ accessToken, tasklistId });
return res.status(200).json(data);
}
if (req.method === "POST") {
const { title, notes } = req.body as { title?: string; notes?: string };
if (!title) return res.status(400).json({ error: "title is required" });
const data = await insertTask({ accessToken, tasklistId, title, notes });
return res.status(201).json(data);
}
return res.status(405).json({ error: "Method Not Allowed" });
} catch (e) {
return res.status(500).json({
error: e instanceof Error ? e.message : "unknown error",
});
}
}
7-4. クライアント側から呼ぶ例
// タスク一覧取得
const listRes = await fetch(`/api/google-tasks/${tasklistId}`);
const listData = await listRes.json();
// タスク追加
await fetch(`/api/google-tasks/${tasklistId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "買い物する", notes: "牛乳" }),
});
7-5. Next.js 実装での注意点
- Google の
access_token/refresh_tokenはNEXT_PUBLIC_で公開しない - トークン更新はサーバー側で実行する(必要なら OAuth クライアントで refresh)
GET側はcache: "no-store"かrevalidateを明示して古いタスク表示を防ぐ- まずは API Route で薄い BFF を作ってから UI を繋ぐと安全
参照した公式ページ(2026-02-05 時点で確認):