Send and create were one-time-shaped. Append is list-shaped: a resource that grows by one entry each call. Today's resource: Google Tasks. Same shape applies to Sheets, Docs, Notion, Linear — anything you'd add a row/item to.
A real script doesn't hardcode the list's ID — it discovers it. Two-step pattern:
# Step 1: find a list
lists = toolset.execute_action(Action.GOOGLETASKS_LIST_TASK_LISTS, {})
items = lists.get("items", []) or lists.get("response_data", {}).get("items", [])
tasklist_id = items[0]["id"]
# Step 2: add an item to it
result = toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": "row added by zuzu day 11",
})
print(result.get("id") or result.get("response_data", {}).get("id"))Why discover instead of hardcode the list ID?
Hardcoded IDs make scripts non-portable. The first list is fine for now; in week 3 you'll filter items by title to pick a specific list.
And the verification?
The response includes the new task's id. Asserting it's non-empty confirms the task landed. We're not double-checking by re-listing — the API only returns an id when the create actually succeeded.
The single biggest fragility in automation scripts is hardcoded resource IDs. A script that says tasklist_id = "MDY0M..." works only on the account where you wrote it. Find the list at runtime and the script ports anywhere.
# Discover
lists = toolset.execute_action(Action.GOOGLETASKS_LIST_TASK_LISTS, {})
items = lists.get("items", []) or lists.get("response_data", {}).get("items", [])
if not items:
raise RuntimeError("no task lists found — Google Tasks should auto-provision the 'My Tasks' list")
tasklist_id = items[0]["id"]Google Tasks auto-provisions a default list (My Tasks) for every signed-in user. So LIST_TASK_LISTS always returns at least one entry.
result = toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": "weekly retro notes",
"notes": "optional — multi-line description",
"due": "2026-12-31T00:00:00.000Z", # optional ISO timestamp
})| Param | Meaning |
|---|---|
tasklist | discovered above |
title | the task name (required) |
notes | optional description |
due | optional due date (ISO 8601) |
{
"id": "abc123def456",
"title": "weekly retro notes",
"status": "needsAction",
...
}Depending on the Composio response wrapper, the data may be at the top level OR under result["response_data"] — read both paths defensively.
| Tool | Discovery | Write |
|---|---|---|
| Tasks | LIST_TASK_LISTS | CREATE_TASK |
| Sheets | SEARCH_SPREADSHEETS | SPREADSHEETS_VALUES_APPEND |
| Docs | search via Drive | INSERT_TEXT (or batch update) |
The shape is the same. Discover by name (or take the first), then write. The lesson uses Tasks because Google auto-provisions a default list — no setup needed. Sheets and Docs require you to have at least one of the resource type in your Drive, which is the only reason this lesson didn't pick Sheets.
Send and create were one-time-shaped. Append is list-shaped: a resource that grows by one entry each call. Today's resource: Google Tasks. Same shape applies to Sheets, Docs, Notion, Linear — anything you'd add a row/item to.
A real script doesn't hardcode the list's ID — it discovers it. Two-step pattern:
# Step 1: find a list
lists = toolset.execute_action(Action.GOOGLETASKS_LIST_TASK_LISTS, {})
items = lists.get("items", []) or lists.get("response_data", {}).get("items", [])
tasklist_id = items[0]["id"]
# Step 2: add an item to it
result = toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": "row added by zuzu day 11",
})
print(result.get("id") or result.get("response_data", {}).get("id"))Why discover instead of hardcode the list ID?
Hardcoded IDs make scripts non-portable. The first list is fine for now; in week 3 you'll filter items by title to pick a specific list.
And the verification?
The response includes the new task's id. Asserting it's non-empty confirms the task landed. We're not double-checking by re-listing — the API only returns an id when the create actually succeeded.
The single biggest fragility in automation scripts is hardcoded resource IDs. A script that says tasklist_id = "MDY0M..." works only on the account where you wrote it. Find the list at runtime and the script ports anywhere.
# Discover
lists = toolset.execute_action(Action.GOOGLETASKS_LIST_TASK_LISTS, {})
items = lists.get("items", []) or lists.get("response_data", {}).get("items", [])
if not items:
raise RuntimeError("no task lists found — Google Tasks should auto-provision the 'My Tasks' list")
tasklist_id = items[0]["id"]Google Tasks auto-provisions a default list (My Tasks) for every signed-in user. So LIST_TASK_LISTS always returns at least one entry.
result = toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": "weekly retro notes",
"notes": "optional — multi-line description",
"due": "2026-12-31T00:00:00.000Z", # optional ISO timestamp
})| Param | Meaning |
|---|---|
tasklist | discovered above |
title | the task name (required) |
notes | optional description |
due | optional due date (ISO 8601) |
{
"id": "abc123def456",
"title": "weekly retro notes",
"status": "needsAction",
...
}Depending on the Composio response wrapper, the data may be at the top level OR under result["response_data"] — read both paths defensively.
| Tool | Discovery | Write |
|---|---|---|
| Tasks | LIST_TASK_LISTS | CREATE_TASK |
| Sheets | SEARCH_SPREADSHEETS | SPREADSHEETS_VALUES_APPEND |
| Docs | search via Drive | INSERT_TEXT (or batch update) |
The shape is the same. Discover by name (or take the first), then write. The lesson uses Tasks because Google auto-provisions a default list — no setup needed. Sheets and Docs require you to have at least one of the resource type in your Drive, which is the only reason this lesson didn't pick Sheets.
Create a free account to get started. Paid plans unlock all tracks.