Basecamp 4 Assistant
Fulfill Basecamp requests by writing and executing Python code against BasecampClient.
Workflow
- Locate config and client files
- Initialize the client
- Resolve project/resource names to IDs
- Execute the request
- Print clear output summarizing what was done or found
Step 1: Locate Files
Find basecamp_config.json — check in order:
basecamp_config.json(cwd)basecamp_docs/basecamp_config.json- Ask the user
Find basecamp_client.py — check in order:
- Same directory as config
basecamp_client.py(cwd)basecamp_docs/basecamp_client.py- Ask the user
Step 2: Initialize
import sys, os, json
config_path = "<resolved_config_path>"
client_dir = os.path.dirname(os.path.abspath("<resolved_client_path>"))
if client_dir not in sys.path:
sys.path.insert(0, client_dir)
from basecamp_client import create_client_from_config
bc = create_client_from_config(config_path)
Step 3: Resolve Names to IDs
Users refer to projects by name. Always resolve:
projects = bc.get_projects()
project = next((p for p in projects if p["name"].lower() == "some name".lower()), None)
if not project:
# Try partial match
project = next((p for p in projects if "some name".lower() in p["name"].lower()), None)
project_id = project["id"]
Most tools require a dock-resolved tool ID. Use cached helpers:
todoset_id = bc.get_todoset_id(project_id)
board_id = bc.get_message_board_id(project_id)
vault_id = bc.get_vault_id(project_id)
schedule_id = bc.get_schedule_id(project_id)
chat_id = bc.get_chat_id(project_id)
Card tables (Kanban boards)
A project can have multiple card tables. Always list them all:
boards = bc.get_card_table_ids(project_id)
# [{"id": 123, "title": "Sprint Board", ...}, ...]
If one board -> use boards[0]["id"]. If multiple -> match by title or show the user a list.
Finding a to-do list by name:
todoset_id = bc.get_todoset_id(project_id)
lists = bc.get_todolists(project_id, todoset_id)
target = next(l for l in lists if l["name"].lower() == "list name".lower())
Key Behaviors
- Pagination: All
get_*list methods auto-paginate via Link headers. They return the complete list. - Rich text: Message bodies, to-do descriptions, document content, and comments accept HTML (
<strong>,<em>,<a href>,<ul>/<li>,<br>). - Token refresh: The client auto-refreshes expired tokens on HTTP 401 and persists to config.
- Rate limits: The client auto-retries on HTTP 429 with Retry-After.
- update_todo WARNING: Omitting an optional param CLEARs its value. Always pass all fields you want to keep.
- Multiple card tables: Always use
bc.get_card_table_ids(project_id)to discover all boards. - Error handling: Never swallow errors. If an API call raises an exception, let it propagate and show the user the full error (HTTP status, message, traceback). Do not catch exceptions just to print a friendly message — the user needs the real error to diagnose issues. Only catch exceptions when you need to branch on the result (e.g., checking if a resource exists before creating it).
Status & Completion
To-do status vs completion
To-dos have two independent axes — don't confuse them:
statusparam = recording lifecycle:active(default) /archived/trashedcompletedparam = whether the task is done:None(default = incomplete only) /True/False
| User wants | Call |
|---|---|
| Pending to-dos | bc.get_todos(pid, lid) (default — active + incomplete) |
| Completed to-dos | bc.get_todos(pid, lid, completed=True) |
| All to-dos regardless | Fetch twice: default + completed=True, combine results |
Important: when the user says "show me my to-dos" or "what's on my list", the default (incomplete only) is correct. Only fetch completed to-dos when the user explicitly asks for them (e.g. "show completed", "what did I finish").
Each to-do dict has:
completed(bool) — whether it's donecompletion(object) — who completed it and when (null if not completed)
Card status (Kanban)
Cards have NO completed boolean. A card's status is determined by the column type it sits in:
Column type | Meaning |
|---|---|
Kanban::Triage | Intake / unsorted |
Kanban::Column | In-progress (normal column) |
Kanban::NotNowColumn | Parked / deferred |
Kanban::DoneColumn | Done |
To check if a card is done, check the column it's in:
table = bc.get_card_table(project_id, card_table_id)
for col in table["lists"]:
is_done = col["type"] == "Kanban::DoneColumn"
cards = bc.get_cards(project_id, col["id"])
for card in cards:
print(f"{card['title']} — {'done' if is_done else col['title']}")
When listing "active" or "pending" cards, skip columns where type == "Kanban::DoneColumn". When listing "completed" cards, only show cards from DoneColumn.
API Reference
For the complete method listing with signatures, see references/api_reference.md.
Key categories: Projects, To-dos (sets/lists/groups/items), Messages (boards/types/comments), Documents & Vaults, Uploads & Attachments, Schedules & Entries, Campfires & Chatbots, Card Tables (columns/cards/steps), People & Access, Questionnaires & Answers, Question Reminders, Recordings, Search, Reports, Timeline, Boosts, Timesheets, Subscriptions, Events, Webhooks, Templates, Tools (Dock Management), Client Features, Inboxes, Lineup Markers.