You have four spreadsheets in your Drive — citations, wave data, budget, and a temp file. Before read_range or append_row can run, you need the spreadsheet ID. How do you find it without hardcoding?
append_row from Day 18 takes a sheet_id. I've been hardcoding it in the tests. But in a real pipeline, I'd want to find the Sheet by title — "Candidate Citations Q2 2026" — so the script works even if the file is moved or renamed.
GOOGLESHEETS_SEARCH_SPREADSHEETS takes a query string and returns matching spreadsheet dicts — each with a spreadsheetId you can pass directly to read_range. The query works like a Drive search:
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": query}
)
sheets = result.get("files", [])Why .get("files", []) and not items like Calendar? Every action seems to use a different key.
Sheets search uses the Drive API under the hood, which uses files as its list key. Calendar uses items. Gmail uses messages. The .get(key, default) reflex stays the same — you just check the response shape for each action. Print result.keys() on your first run if you're unsure. The key names are in the Composio action docs and will become familiar after a few actions:
def search_sheets(query: str) -> list:
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": query}
)
sheets = result.get("files", [])
print(f"Found {len(sheets)} sheets matching '{query}'")
return sheetsSo I search by title, take the first result's spreadsheetId, and pass it to read_range. The pipeline doesn't need hardcoded IDs — it discovers them at runtime.
A pipeline that finds its own data sources. That's genuinely portable research infrastructure.
I can hand this script to a co-author with a different Drive structure and it will find the right Sheet by title. That's the reproducibility I've been trying to achieve.
One caveat: search returns all spreadsheets matching the query string, not just the one you're thinking of. If you have "Citations 2025" and "Citations 2026", the query "Citations" returns both. Always check len(sheets) and handle the multi-result case — take the most recently modified, or require an exact title match.
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": "Candidate Citations"}
)
sheets = result.get("files", []) # 'files', not 'items' or 'messages'filesSheets search uses the Drive API — response key is files. Each file dict includes:
| Field | Meaning |
|---|---|
id | The spreadsheet ID for read_range |
name | Display name |
modifiedTime | Last modified timestamp |
exact = [s for s in sheets if s["name"] == "Candidate Citations Q2 2026"]
if exact:
sheet_id = exact[0]["id"]You have four spreadsheets in your Drive — citations, wave data, budget, and a temp file. Before read_range or append_row can run, you need the spreadsheet ID. How do you find it without hardcoding?
append_row from Day 18 takes a sheet_id. I've been hardcoding it in the tests. But in a real pipeline, I'd want to find the Sheet by title — "Candidate Citations Q2 2026" — so the script works even if the file is moved or renamed.
GOOGLESHEETS_SEARCH_SPREADSHEETS takes a query string and returns matching spreadsheet dicts — each with a spreadsheetId you can pass directly to read_range. The query works like a Drive search:
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": query}
)
sheets = result.get("files", [])Why .get("files", []) and not items like Calendar? Every action seems to use a different key.
Sheets search uses the Drive API under the hood, which uses files as its list key. Calendar uses items. Gmail uses messages. The .get(key, default) reflex stays the same — you just check the response shape for each action. Print result.keys() on your first run if you're unsure. The key names are in the Composio action docs and will become familiar after a few actions:
def search_sheets(query: str) -> list:
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": query}
)
sheets = result.get("files", [])
print(f"Found {len(sheets)} sheets matching '{query}'")
return sheetsSo I search by title, take the first result's spreadsheetId, and pass it to read_range. The pipeline doesn't need hardcoded IDs — it discovers them at runtime.
A pipeline that finds its own data sources. That's genuinely portable research infrastructure.
I can hand this script to a co-author with a different Drive structure and it will find the right Sheet by title. That's the reproducibility I've been trying to achieve.
One caveat: search returns all spreadsheets matching the query string, not just the one you're thinking of. If you have "Citations 2025" and "Citations 2026", the query "Citations" returns both. Always check len(sheets) and handle the multi-result case — take the most recently modified, or require an exact title match.
result = toolset.execute_action(
Action.GOOGLESHEETS_SEARCH_SPREADSHEETS,
{"query": "Candidate Citations"}
)
sheets = result.get("files", []) # 'files', not 'items' or 'messages'filesSheets search uses the Drive API — response key is files. Each file dict includes:
| Field | Meaning |
|---|---|
id | The spreadsheet ID for read_range |
name | Display name |
modifiedTime | Last modified timestamp |
exact = [s for s in sheets if s["name"] == "Candidate Citations Q2 2026"]
if exact:
sheet_id = exact[0]["id"]Create a free account to get started. Paid plans unlock all tracks.