Interact with Basecamp via the Basecamp CLI. Full API coverage: projects, todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, and campfire. Use for ANY Basecamp question or action.
Full CLI coverage: 189 tracked in-scope endpoints across todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, chat, pings, gauges, assignments, notifications, and accounts.
MUST follow these rules:
Choose the right output mode — --jq when you need to filter/extract data; --json for full JSON; --md when presenting results to a human (see Output Modes below). Never pipe to external jq — use --jq instead.
Parse URLs first with basecamp url parse "<url>" to extract IDs
Comments are flat - reply to parent recording, not to comments
Check context via .basecamp/config.json before assuming project
Content fields accept Markdown and @mentions — message body and comment content accept Markdown syntax; the CLI converts to HTML automatically. Use Markdown formatting (lists, bold, links, code blocks, tables) for rich content. Four mention syntaxes are available (prefer deterministic for agents):
[@Name](mention:SGID) — zero API calls, embeds SGID directly (preferred for agents)[@Name](person:ID) — one API call, resolves person ID to SGID via pingable set@sgid:VALUE — inline SGID embed for pipeline composability@Name / @First.Last — fuzzy name resolution (may be ambiguous)
For todos, documents, and cards, content is sent as-is — use plain text or HTML directly.Table boundary: GFM tables round-trip: they render in message/comment
bodies, display converts them back to pipe tables, and the TUI in-place
editors open simple grids for editing. Only complex tables — merged
cells (colspan/rowspan), captions, extra header rows, nested tables,
attachments/images or block content inside cells, multi-paragraph or
multi-line cells, or a table inside a blockquote or list — refuse to open,
since a GFM pipe table can't represent those shapes (edit them on Basecamp
web, or replace the
whole field via messages update / comments update / todos update --description, which take fresh content and are unaffected). Complex
tables still display best-effort, flattened to a plain grid.
Multiline / non-ASCII content: do not rely on bash ANSI-C quoting ($'...\n...') — it is a bash/zsh extension. Under a POSIX /bin/sh (dash, busybox-ash, common in sandboxes) the $ is passed through literally and posts a stray leading $, and \n stays a literal backslash-n. Pipe the content via stdin instead, using - as the content argument:
printf '%s\n' '海报 mockup 方向稿:' '' '<bc-attachment ...>' | basecamp comments create <recording_id> - --in <project> --json
- means "read from stdin" on every content input: content-kind positionals
(comments create/update, messages create [body], cards create [body],
todos create, docs documents create [content], chat post/update, boost create,
checkins answer create/update, notes set) and content flags (--data on
api post/put, --body, --content, --description, --comment on
todos sweep, --file on notes set). Each command's --agent help lists
its stdin inputs. Rules:
- it is ignored (or, where
content is required and missing, the error teaches -).- anywhere else (a title, a name, a path) errors when stdin is
piped. Escape a positional after the -- separator
(basecamp projects create -- -); a flag value has no in-line escape — run
the command without piped stdin. basecamp help and shell completion are
exempt: they write nothing to Basecamp, and completion legitimately
receives - as the word being completed.- with nothing piped (interactive TTY) errors immediately instead of
hanging; use a pipe, a heredoc (basecamp comments create <id> - <<'EOF'),
or --edit where offered.printf 'x\n' | ... -
posts x (this keeps boost create - inside its 16-rune limit).Project scope is mandatory for most commands — via --in <project> or .basecamp/config.json. Cross-project exceptions: basecamp reports assigned for assigned work, basecamp assignments for structured assignment views, basecamp reports overdue for overdue todos, basecamp reports schedule for upcoming schedule across all projects, basecamp recordings <type> for browsing by type, basecamp notifications for notifications, basecamp gauges list for account-wide gauges, and the seven list commands covered in item 7.
Account-wide listing. basecamp todos list --all-projects --json lists across every project; the same flag does the same on cards list, messages list, comments list, files list, forwards list, and checkins answers. It overrides a configured project, and with no project in scope those commands already list account-wide rather than prompting. Flags that name something inside a single project are rejected there rather than silently ignored.
Account-wide listings return the first 100 items by default — account-wide "all" is the whole account, not one project's worth. Use --limit N to raise the cap (it walks pages until N are collected) or --all for everything. --page N fetches exactly one page, but only on the paginated listings.
The two overdue variants — basecamp todos list --all-projects --overdue and basecamp cards list --all-projects --overdue — come from unpaginated endpoints. They accept --limit and --all but reject --page, so do not generate --page against them.
Choosing a mode:
| Goal | Flag | Format |
|------|------|--------|
| Filter/extract JSON data | --jq '<expr>' | Built-in jq filter (no external jq needed). Implies --json; filter runs on the envelope. |
| Filter in agent mode | --agent --jq '<expr>' | Filter runs on data-only payload (no envelope), matching --agent contract. |
| Full JSON output | --json | JSON envelope: {ok, data, summary, breadcrumbs, meta}; errors: {ok:false, error, code, retryable, hint, meta} |
| Show results to a user | --md / -m | GFM tables, task lists, structured Markdown |
| Automation / scripting | --agent | Success: raw JSON data (no envelope); errors: {ok:false,...} object; no interactive prompts |
Always pass --json or --md explicitly — auto-detection depends on config and may not produce the format you expect. Use --md when composing reports, summarizing data, or displaying results inline. --agent is for headless integration scripts.
Avoiding interactive prompts. The flags --agent/--json/--quiet/--ids-only/--count and the environment variable BASECAMP_NONINTERACTIVE=1 suppress interactive selection prompts. --md does not — if a required target is ambiguous (e.g. a project with multiple todosets and no --todoset), and the CLI is attached to a terminal, it will show a blocking picker. When you need Markdown output and no prompts, either pass the flag that names whatever is ambiguous (--todoset <id> for the todoset case above, or --in <project> / --list <id> when the project or list is ambiguous) or set BASECAMP_NONINTERACTIVE=1 in the environment. BASECAMP_NONINTERACTIVE disables all prompts (they become actionable errors instead) without changing the output format — an escape hatch for agents running under a PTY.
Other modes: --quiet (success: raw JSON, no envelope; errors: {ok:false,...}), --ids-only, --count, --stats (session statistics), --styled (force ANSI), -v / -vv (verbose/trace), --jq '<expr>' (built-in jq filter — see below).
Navigate unfamiliar commands with --agent --help — returns structured JSON describing any command:
basecamp todos --agent --help
{"command":"todos","path":"basecamp todos","short":"...","long":"...","usage":"...","notes":["..."],
"subcommands":[{"name":"sweep","short":"...","path":"basecamp todos sweep"}],
"flags":[{"name":"assignee","type":"string","default":"","usage":"..."}],
"inherited_flags":[{"name":"json","shorthand":"j","type":"bool","default":"false","usage":"..."}]}
Walk the tree: start at basecamp --agent --help for top-level commands, then drill into any subcommand. Commands carry domain-specific agent hints (e.g., "--assignee filters the account-wide listing only; within a project, fetch all and filter client-side").
basecamp <cmd> --limit 50 # Cap results (default varies by resource)
basecamp <cmd> --all # Fetch all (may be slow for large datasets)
basecamp <cmd> --page 1 # First page only, no auto-pagination
--all and --limit are mutually exclusive. --page cannot combine with either.
--assignee me resolves to current user--due tomorrow / --due +3 / --due "next week" — natural date parsing, when setting a due date (todos create, todos update, cards create, and so on)--due on a listing is a different flag and does not take dates: it accepts only with, without, or overdue, and only account-wide. basecamp todos list --due tomorrow is rejected. For date-based listing use --overdue, --no-due-date, or basecamp assignments due <scope>.basecamp/config.json if --in not specifiedbasecamp profile create <name>, then select one with global --profile <name> or BASECAMP_PROFILE=<name>.Note: Most queries require project scope (via
--in <project>or.basecamp/config.json). Cross-project exceptions:basecamp reports assigned,basecamp assignments,basecamp reports overdue,basecamp reports schedule,basecamp recordings <type>,basecamp notifications,basecamp gauges list.Seven list commands also list account-wide:
basecamp todos list --all-projects --json, and likewisecards list,messages list,comments list,files list,forwards list, andcheckins answers.
| Task | Command |
|------|---------|
| List projects | basecamp projects list --json |
| My todos (in project) | basecamp todos list --assignee me --in <project> --json |
| My todos (cross-project) | basecamp reports assigned --json (defaults to "me") |
| My schedule (cross-project) | basecamp reports schedule --json (upcoming events across all projects) |
| All todos (cross-project) | basecamp todos list --all-projects --json (grouped by project) |
| Overdue todos (in project) | basecamp todos list --overdue --in <project> --json |
| Overdue todos (cross-project) | basecamp todos list --all-projects --overdue --json (flat, oldest first) or basecamp reports overdue --json (bucketed by lateness) |
| All cards (cross-project) | basecamp cards list --all-projects --json (grouped by project) |
| Someone's todos (cross-project) | basecamp todos list --all-projects --assignee "Ann" --json (server-side filter) |
| Two people's todos (cross-project) | basecamp todos list --all-projects --assignee ann --assignee bob --json (matches either) |
| Someone's cards (cross-project) | basecamp cards list --all-projects --assignee "Ann" --json |
| Todos with no due date set (cross-project) | basecamp todos list --all-projects --due without --json |
| My bookmarks | basecamp bookmarks list --json |
| Bookmark something | basecamp bookmarks add <id-or-url> --json |
| Is it bookmarked? | basecamp bookmarks check <id-or-url> --json (always exits 0) |
| Bubble a recording up | basecamp bubble-up add <id-or-url> --json |
| Schedule a bubble-up | basecamp bubble-up add <id-or-url> --at tomorrow --json |
| Pop a bubble-up | basecamp bubble-up remove <id-or-url> --json |
| My unpublished drafts | basecamp drafts list --json |
| Read my personal note | basecamp notes show --json |
| Replace my personal note | basecamp notes set "<content>" --json |
| Check-ins I owe answers to | basecamp checkins reminders --json |
| Add to Up Next | basecamp assignments prioritize <id> --json |
| Recolor a calendar | basecamp calendars update <id-or-url> --color blue --json |
| Todo outside any list | basecamp todos create "<content>" --loose --in <project> --json |
| Assign todo | basecamp assign <id> [id...] --to <person> --in <project> --json |
| Assign card | basecamp assign <id> [id...] --card --to <person> --in <project> --json |
| Assign card step | basecamp assign <id> [id...] --step --to <person> --in <project> --json |
| Create todo | basecamp todos create "Task" --in <project> --list <list> --json |
| Create todolist | basecamp todolists create "Name" --in <project> --json |
| Complete todo | basecamp todos complete <id> --json |
| List cards | basecamp cards list --in <project> --json |
| Create card | basecamp cards create "Title" --in <project> --json |
| Complete card | basecamp cards done <id|url> --in <project> --json |
| Move card | basecamp cards move <id> --to <column> [--position N] --in <project> --json |
| Move card to on-hold | basecamp cards move <id> --on-hold --in <project> --json |
| Move card to another project | basecamp cards move <id> --to-wormhole <wormhole_id> --in <project> --json (async teleport) |
| Post message | basecamp messages create "Title" "Body" --in <project> --json |
| Post with @mention | basecamp messages create "Title" "Hey @First.Last, ..." --in <project> --json |
| Post silently | basecamp messages create "Title" "Body" --no-subscribe --in <project> --json |
| Post to chat | basecamp chat post "Message" --in <project> --json |
| List pings | basecamp notifications --json --jq '.data.reads[]? | select(.section == "pings")' |
| Read ping thread | basecamp api get "/buckets/<circle_id>/chats/<chat_id>/lines.json" --agent |
| Post to ping thread | basecamp api post "/buckets/<circle_id>/chats/<chat_id>/lines.json" --data '{"content":"<p>message</p>"}' --json |
| Add comment | basecamp comments create <recording_id> "Text" --in <project> --json |
| Inspect comment / reply atoms | basecamp comments show <url> --json → reply_target + mention in .data |
| List attachments | basecamp attachments list <id\|url> --json |
| Download attachments | basecamp attachments download <id> --out /tmp/ |
| Show + download | basecamp todos show <id> --download-attachments --json |
| Stream attachment to stdout | basecamp attachments download <id> --file <name> --out - |
| Change history for an item | basecamp events <id\|url> --json (when a card moved columns, when a todo was completed) |
| Search | basecamp search "query" --json |
| Parse URL | basecamp url parse "<url>" --json |
| Upload file | basecamp files uploads create <file> [--vault <folder_id>] --in <project> --json |
| Download file | basecamp files download <id> --in <project> |
| Stream file to stdout | basecamp files download <id> --out - --in <project> |
| Download storage URL | basecamp files download "https://storage.3.basecamp.com/.../download/report.pdf" |
| My assignments | basecamp assignments --json (priorities + non-priorities) |
| Overdue assignments | basecamp assignments due overdue --json |
| Completed assignments | basecamp assignments completed --json |
| Notifications | basecamp notifications --json |
| Mark notification read | basecamp notifications read <id> --json |
| All bubble-ups (BC5) | basecamp notifications bubbleups --json |
| Gauges (account-wide) | basecamp gauges list --json |
| Gauge needles | basecamp gauges needles --in <project> --json |
| Create needle | basecamp gauges create --position 75 --color green --in <project> --json |
| Account details | basecamp accounts show --json |
Parse URLs before acting on them — unless you're handing the URL to a command
that accepts a URL directly (show, comments show, comments thread,
attachments list/attachments download), which extract the IDs for you. Only comments show and
comments thread verify the URL's host and account before any fetch. For other
URL-accepting commands, only pass URLs from a trusted Basecamp host:
basecamp url parse extracts IDs but does not validate the URL's origin, so
parsing an attacker-controlled path yields trusted-looking IDs.
basecamp url parse "https://3.basecamp.com/2914079/buckets/41746046/messages/9478142982#__recording_9488783598" --json
Returns: account_id, project_id, type, recording_id, comment_id (from fragment).
URL patterns:
/buckets/27/messages/123 - Message 123 in project 27/buckets/27/messages/123#__recording_456 - Comment 456 on message 123/buckets/27/card_tables/cards/789 - Card 789/buckets/27/card_tables/columns/456 - Column 456 (for creating cards)/buckets/27/todos/101 - Todo 101/buckets/27/uploads/202 - Upload/file 202/buckets/27/documents/303 - Document 303/buckets/27/schedule_entries/404 - Schedule entry 404Replying to comments:
# Comments are flat - reply to the parent recording_id, not the comment_id
basecamp url parse "https://...messages/123#__recording_456" --json
# Returns recording_id: 123 (parent), comment_id: 456 (fragment) - comment on 123, not 456
basecamp comments create 123 "Reply" --in <project>
# Or get the whole reply-ready context deterministically in one call:
basecamp comments thread "https://...messages/123#__recording_456" --json
# .data.reply_target.recording_id → where to post the reply
# .data.reply_target.account_id → the account that reply belongs to (build a fully-qualified command)
# .data.focus.author.mention.syntax → paste-ready [@Name](mention:SGID)
# .data.comments → surrounding discussion (default window of 41)
# --all returns every fetched comment; --window N sets the window size
# When the account came from the URL (none configured), the reply breadcrumb carries --account
Need to find something?
├── Know the type + project? → basecamp <type> list --in <project> --json
│ (some groups have default list behavior; use --agent --help if unsure)
├── My assigned work? → basecamp assignments --json (priorities + non-priorities)
│ Or: basecamp reports assigned --json (traditional view, defaults to "me")
├── My overdue assignments? → basecamp assignments due overdue --json
├── My notifications? → basecamp notifications --json
├── Upcoming schedule? → basecamp reports schedule --json (cross-project)
├── Overdue across projects? → basecamp reports overdue --json
├── Browse by type cross-project? → basecamp recordings <type> --json
│ (types: todos, messages, documents, comments, cards, uploads)
│ Note: Defaults to active status; use --status archived for archived items
│ ⚠ No assignee data — cannot filter by person; use reports assigned instead
├── Full-text search? → basecamp search "query" --json
├── Have a comment URL, or a notification link targeting a comment? → basecamp comments thread <url> --json
└── Have a URL? → basecamp url parse "<url>" --json
Want to change something?
├── Have URL? → basecamp url parse "<url>" → use extracted IDs
├── Have ID? → basecamp <resource> update <id> --field value
├── Change status? → basecamp recordings trash|archive|restore <id>
├── Complete todo? → basecamp todos complete <id>
├── Complete card? → basecamp cards done <id|url> --in <project>
└── Reply to a comment? → basecamp comments show <url> --jq '.data | {reply_target, mention}'
(one call, cheap atoms — the mention is machine-only, so use --jq/--json, not plain show)
or basecamp comments thread <url> when you need the surrounding discussion;
then basecamp comments create <reply_target.recording_id> <text>
# Get commit info and comment on todo (use printf %q for safe quoting)
COMMIT=$(git rev-parse --short HEAD)
MSG=$(git log -1 --format=%s)
basecamp comments create <todo_id> "Commit $COMMIT: $(printf '%s' "$MSG")" --in <project>
# Complete when done
basecamp todos complete <todo_id>
# Create todo for PR work
basecamp todos create "Review PR #42" --in <project> --assignee me --due tomorrow
# When merged
basecamp todos complete <todo_id>
basecamp chat post "Merged PR #42" --in <project>
# Preview overdue todos
basecamp todos sweep --overdue --dry-run --in <project>
# Complete all with comment
basecamp todos sweep --overdue --complete --comment "Cleaning up" --in <project>
# 1. Look up the person
basecamp people pingable --jq '.data[] | select(.name == "Jane Smith")'
# => {"id": 42000, "attachable_sgid": "BAh7CEkiCG...", "name": "Jane Smith"}
# 2. Use SGID in Markdown mention syntax (zero API calls during post)
basecamp comments create 123 "Hey [@Jane Smith](mention:BAh7CEkiCG...), check this" --in <project>
# Or use person ID (one lookup during post)
basecamp comments create 123 "Hey [@Jane Smith](person:42000), check this" --in <project>
# Fuzzy matching: use @First.Last to reduce ambiguity
basecamp comments create <id> "@Jane.Smith, please review this" --in <project>
basecamp messages create "Update" "cc @Jane, @Alex" --in <project>
basecamp chat post "@Jane, done!" --in <project>
# Ambiguous names return an error with suggestions
# Use @First.Last for disambiguation
# List columns to get IDs
basecamp cards columns --in <project> --json
# Complete a card (moves it to the Done column automatically)
basecamp cards done <card_id> --in <project>
# Move card to column
basecamp cards move <card_id> --to <column_id> --in <project>
# Move card to specific position in column (1-indexed)
basecamp cards move
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
Manage Apple Notes via the `memo` CLI on macOS (create, view, edit, delete, search, move, and export notes). Use when a user asks OpenClaw to add a note, list notes, search notes, or manage note folders.
Work with Obsidian vaults (plain Markdown notes) and automate via obsidian-cli.
Use when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
Manage Apple Reminders via remindctl CLI (list, add, edit, complete, delete). Supports lists, date filters, and JSON/plain output.
Manage Trello boards, lists, and cards via the Trello REST API.
Category:productivity