@basecamp/basecamp
|
| name | basecamp |
| description | | |
| triggers | basecamp, /basecamp |
| invocable | true |
| argument-hint | [action] [args...] |
/basecamp - Basecamp Workflow Command
Full CLI coverage: 155 endpoints across todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, chat, pings, gauges, assignments, notifications, and accounts.
Agent Invariants
MUST follow these rules:
Choose the right output mode —
--jqwhen you need to filter/extract data;--jsonfor full JSON;--mdwhen presenting results to a human (see Output Modes below). Never pipe to externaljq— use--jqinstead.Parse URLs first with
basecamp url parse "<url>"to extract IDsComments are flat - reply to parent recording, not to comments
Check context via
.basecamp/config.jsonbefore assuming projectContent 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 render in message/comment bodies, but the TUI in-place editors refuse to open table-bearing content (edit it on Basecamp web, or replace the whole field via
messages update/comments update/todos update --description, which take fresh content and are unaffected), and human-readable CLI/TUI display of such content may lose table structure — both pending server-side Markdown support (BC3 #11986).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\nstays 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> --jsonProject scope is mandatory for most commands — via
--in <project>or.basecamp/config.json. Cross-project exceptions:basecamp reports assignedfor assigned work,basecamp assignmentsfor structured assignment views,basecamp reports overduefor overdue todos,basecamp reports schedulefor upcoming schedule across all projects,basecamp recordings <type>for browsing by type,basecamp notificationsfor notifications,basecamp gauges listfor account-wide gauges, and the seven list commands covered in item 7.Account-wide listing.
basecamp todos list --all-projects --jsonlists across every project; the same flag does the same oncards list,messages list,comments list,files list,forwards list, andcheckins 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 Nto raise the cap (it walks pages until N are collected) or--allfor everything.--page Nfetches exactly one page, but only on the paginated listings. The two overdue variants —basecamp todos list --all-projects --overdueandbasecamp cards list --all-projects --overdue— come from unpaginated endpoints. They accept--limitand--allbut reject--page, so do not generate--pageagainst them.
Output Modes
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} |
| 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).
CLI Introspection
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").
Pagination
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.
Smart Defaults
--assignee meresolves 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)--dueon a listing is a different flag and does not take dates: it accepts onlywith,without, oroverdue, and only account-wide.basecamp todos list --due tomorrowis rejected. For date-based listing use--overdue,--no-due-date, orbasecamp assignments due <scope>- Project from
.basecamp/config.jsonif--innot specified - Multiple identities use named profiles:
basecamp profile create <name>, then select one with global--profile <name>orBASECAMP_PROFILE=<name>.
Quick Reference
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) |
| 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 |
| 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[]? |
| 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 |
| Watch timeline | basecamp timeline --watch |
URL Parsing
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 404
Replying 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
Decision Trees
Finding Content
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
Modifying Content
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>
Common Workflows
Link Code to Basecamp Todo
# 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>
Track PR in Basecamp
# 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>
Bulk Process Overdue Todos
# 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>
Mentioning people (preferred — deterministic)
# 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>
Mentioning people (interactive — may be ambiguous)
# 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
Move Card Through Workflow
# 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 <card_id> --to <column_id> --position 1 --in <project>
# Move card to on-hold section of its current column
basecamp cards move <card_id> --on-hold --in <project>
# Move card to on-hold section of a specific column (numeric ID)
basecamp cards move <card_id> --to <column_id> --on-hold --in <project>
# Move card to on-hold section of a named column (requires --card-table)
basecamp cards move <card_id> --to "Column Name" --on-hold --card-table <table_id> --in <project>
Download File from Basecamp
basecamp files download <upload_id> --in <project> --out ./downloads
# Download attachment from a storage URL (no --in needed)
basecamp files download "https://storage.3.basecamp.com/123/blobs/abc/download/report.pdf"
# Stream to stdout (for piping)
basecamp files download <upload_id> --out - --in <project>
Working with Attachments (Multimodal Agent Workflow)
Messages, todos, cards, and documents may contain images and file attachments
(mockups, screenshots, annotated designs). Show commands surface these as
field-scoped collections — content_attachments and/or description_attachments
— keyed by which rich-text attribute contained them. The notice field hints at
the download command.
Step 1: Fetch the recording and check for attachments
basecamp todos show <id> --json
# Response includes description_attachments when attachments are present
# Messages/documents use content_attachments; cards may have both
# The notice field hints: "3 attachment(s) — download: basecamp attachments download <id>"
Step 2 (one-shot): Download attachments with the show command
# --download-attachments fetches + downloads in one shot
basecamp todos show <id> --download-attachments --json
# content_attachments/description_attachments entries now include "path" pointing to local files
# Downloads to OS temp dir by default, or specify: --download-attachments /tmp/att
Step 2 (two-step alternative): Download separately
# Download all at once (shows progress on stderr)
basecamp attachments download <id> --out /tmp/attachments
Step 3: View images with your native file-read tool
For multimodal LLMs (Claude, Gemini), use your file-read tool on the path
from the response to view downloaded images directly — no browser needed.
This surfaces visual context (mockups, screenshots, annotated designs) that
is often the most important part of a Basecamp todo or message.
# Stream a single image to stdout for piping
basecamp attachments download <id> --file mockup.png --out -
# Select by index when names collide
basecamp attachments download <id> --index 2 --out -
Key pattern: When a show command response contains content_attachments
or description_attachments, always download and view them — visual context is
often more important than the text content. Use --download-attachments for
one-shot fetch+download, or follow the breadcrumb hint for two-step control.
Resource Reference
Projects
basecamp projects list --json # List all
basecamp projects show <id> --json # Show details
basecamp projects create "Name" --json # Create
basecamp projects update <id> --name "New" # Update
basecamp projects trash <id> # Move to trash (recoverable)
Archiving a project: the CLI does not have a dedicated archive command, but the underlying status endpoint can be hit via raw API. Same path works for restoring to active or moving to trashed.
basecamp api put "projects/<id>/status/archived" -d '{}' --json # Archive
basecamp api put "projects/<id>/status/active" -d '{}' --json # Unarchive
basecamp api put "projects/<id>/status/trashed" -d '{}' --json # Trash (same as `projects trash`)
Verify with basecamp projects show <id> --jq '.data.status'.
Todos
basecamp todos list --in <project> --json # List in project
basecamp todos list --assignee me --in <project> # My todos
basecamp todos list --overdue --in <project> # Overdue only
basecamp todos list --status completed --in <project> # Completed
basecamp todos list --list <todolist_id> --in <project> # In specific list
basecamp todos create "Task" --in <project> --list <list> --assignee me --due tomorrow
basecamp todos complete <id> [id...] # Complete (multiple OK)
basecamp todos uncomplete <id> # Reopen
basecamp assign <id> [id...] --to <person> --in <project> # Assign to-do (multiple OK)
basecamp unassign <id> [id...] --from <person> --in <project> # Remove to-do assignee (multiple OK)
basecamp assign <id> [id...] --card --to <person> --in <project> # Assign card
basecamp unassign <id> [id...] --card --from <person> --in <project> # Remove card assignee
basecamp assign <id> [id...] --step --to <person> --in <project> # Assign card step
basecamp unassign <id> [id...] --step --from <person> --in <project> # Remove step assignee
basecamp todos position <id> --to 1 # Move to top
basecamp todos position <id> --to 1 --list <id|name|url> # Move to different list
basecamp todos sweep --overdue --complete --comment "Done" --in <project>
basecamp todos create "Task" --in <project> --list <list> --notify-on-completion "Jane,Bob" # Notify when done
basecamp todos update <id> --notify-on-completion "Jane" # Set who's notified on completion
basecamp todos update <id> --no-notify-on-completion # Clear completion notifications
Flags: --assignee (repeatable; server-side account-wide, client-side within a project; also on cards list account-wide, but not on messages), --status (completed/incomplete/archived/trashed), --overdue, --list, --due (listing filter: with/without/overdue only, account-wide only — not a date; see Smart Defaults), --limit, --all
Completion subscribers ("When done, notify…"): set with
--notify-on-completion <names or IDs, comma-separated> on todos create and
todos update; clear with --no-notify-on-completion on todos update.
Plain updates (title, due date, etc.) preserve existing completion subscribers.
Todo Subtasks (checklist steps): Basecamp to-do subtasks are stored as
Kanban::Step records, even when their parent is a normal Todo. The regular
basecamp todos show response may not include them; use
basecamp recordings list --in <project> --type Kanban::Step and filter by
parent.id to list/check subtasks for a todo.
# Create a subtask under a todo.
# Use the numeric project ID and todo ID in this card-style path.
basecamp api post /buckets/<project_id>/card_tables/cards/<parent_todo_id>/steps.json \
--data '{"title":"Subtask title"}' \
--json
# Read or edit a subtask
basecamp api get /buckets/<project_id>/card_tables/steps/<step_id>.json --json
basecamp api put /buckets/<project_id>/card_tables/steps/<step_id>.json \
--data '{"title":"Updated subtask title"}' \
--json
# List subtasks for a todo
PARENT_TODO_ID=<parent_todo_id> \
basecamp recordings list --in <project> --type Kanban::Step --all \
--jq '.data[] | select(.parent.id==(env.PARENT_TODO_ID | tonumber)) | {id,title,status,parent:.parent.id,url}'
# Assign or set a due date. Send only what you're changing — omitted fields are
# left alone. `assignee_ids` replaces the whole list, so name everyone who stays.
basecamp api put /buckets/<project_id>/card_tables/steps/<step_id>.json \
--data '{"assignee_ids":[<person_id>,<existing_person_id>],"due_on":"<YYYY-MM-DD>"}' \
--json
# Complete or reopen a subtask
basecamp api put /buckets/<project_id>/card_tables/steps/<step_id>/completions.json \
--data '{"completion":"on"}' \
--json
basecamp api put /buckets/<project_id>/card_tables/steps/<step_id>/completions.json \
--data '{"completion":"off"}' \
--json
# Trash a subtask from the todo UI by trashing the step record (Kanban::Step)
basecamp recordings trash <step_id> --in <project> --json
Key points: replace numeric placeholders such as <project_id>,
<parent_todo_id>, and <person_id> before running the examples. Bucket-scoped
API paths require a numeric project/bucket ID; --in <project> can still accept
a project name where CLI commands support name resolution. For creating todo
subtasks, Basecamp accepts the parent todo ID in the
/buckets/<project_id>/card_tables/cards/<parent_todo_id>/steps.json path. To
list subtasks under a todo, use
basecamp recordings list --in <project> --type Kanban::Step with the
parent.id filter shown above.
Completed subtasks have completed: true and a completion object with
created_at and creator. Open subtasks have completed: false and no
completion object. Trashed subtasks may still be readable directly with
status: "trashed" and inherits_status: false, but they no longer appear in
the todo UI.
In testing with todo-backed steps, these bucket-scoped direct GET requests
returned not_found:
/buckets/<project_id>/card_tables/cards/<parent_todo_id>/steps.json,
/buckets/<project_id>/card_tables/cards/<parent_todo_id>.json, and
/buckets/<project_id>/todos/<parent_todo_id>/steps.json. To inspect trashed
subtasks, add --status trashed; archived parents may require
--status archived.
Raw step updates are partial. PUT .../card_tables/steps/<id>.json leaves
every parameter you omit unchanged, so send only the fields you are changing.
Echoing back a title you did not mean to change is not merely redundant — it
reverts anyone who edited the title between your read and your write. To clear a
value, say so explicitly: "due_on": null clears the due date, "assignee_ids": [] removes everyone. assignee_ids always replaces the whole list rather than
adding to it, so name every person who should remain assigned.
(This is bc3#12521. Before it, an omitted field was cleared and a title-less
update was rejected, which is why older guidance said to resend the title. Todo
subtasks and card steps share one endpoint and one contract — PUT card_tables/steps/:id routes to the same controller for both.)
The generic
basecamp assign <step_id> --step ... command is intended for card steps and
may fail with Bad Request for todo-backed steps, so prefer assignee_ids on
the raw step update endpoint for todo subtasks.
Todolists
Todolists are containers for todos. Create a todolist before adding todos.
basecamp todolists list --in <project> --json # List todolists
basecamp todolists show <id> --in <project> # Show details
basecamp todolists create "Name" --in <project> --json # Create
basecamp todolists create "Name" --description "Desc" --in <project>
basecamp todolists create "Name" --visible-to-clients --in <project> # Visible to clients
basecamp todolists update <id> --name "New" --in <project> # Update
basecamp todolists position <id> --to 1 # Reorder one list (1 = top)
basecamp todolists position <id> <id> <id> # Order incomplete lists, top→bottom
Bulk position sets the visible order in one command: pass incomplete lists from
the same todoset, top to bottom. It always places them at the top.
Cards (Kanban)
Note: --assignee on cards list is account-wide only — pass --all-projects (or have no project in scope) and it becomes a real server-side filter. Within a single project cards have no assignee filter: fetch all and filter client-side. --due with|without|overdue is account-wide only on cards too. If a project has multiple card tables, you must specify --card-table <id>. When you get an "Ambiguous card table" error, the hint shows available table IDs and names.
basecamp cards list --in <project> --json # All cards
basecamp cards list --card-table <id> --in <project> # Specific table (required if multiple)
basecamp cards list --column <id> --in <project> # Cards in column
basecamp cards columns --in <project> --json # List columns (needs --card-table if multiple)
basecamp cards show <id> --in <project> # Card details
basecamp cards create "Title" "<p>Body</p>" --in <project> --column <id>
basecamp cards update <id> --title "New" --due tomorrow --assignee me
basecamp cards done <id|url> --in <project> # Move to the Done column automatically
basecamp cards move <id> --to <column_id> # Move to column (numeric ID)
basecamp cards move <id> --to "Done" --card-table <table_id> # Move by name (needs table)
basecamp cards move <id> --to "Done" --position 1 --card-table <table_id> # Move to position
basecamp cards move <id> --on-hold # Move to on-hold of current column
basecamp cards move <id> --to <column_id> --on-hold # Move to on-hold of target column
Cross-project card move (wormholes): the only way to move a card to another project is to teleport it through a wormhole — a portal on the card table that sends cards to a preconfigured column on another project's card table (max 4 per table). The teleport is asynchronous and mints a new card id: after the move is accepted, the server copies the card into the destination and deletes the original, so the original id 404s — do not reuse it.
basecamp cards wormholes list --in <project> # Discover wormholes (id, destination, linked)
basecamp cards wormholes create --to-column <id|url> --in <project> # Link to a column on another table (≤4)
basecamp cards wormholes update <id> --to-column <id|url> --in <project>
basecamp cards wormholes delete <id> --in <project>
basecamp cards move <card_id> --to-wormhole <wormhole_id> --in <project> # Teleport (async)
basecamp cards move <card_id> --to-wormhole <destination_column_url> --in <project> # Match by destination column
--to-wormhole is mutually exclusive with --to/--on-hold/--position. Pass
a numeric wormhole id to route directly, or a destination-column URL to match it
against the source table's wormholes.
Archived/trashed cards: cards list only returns active cards. For archived or trashed cards, use basecamp recordings cards --status archived --in <project> or --status trashed.
Identifying completed cards: Cards in Done columns have parent.type: "Kanban::DoneColumn" and completed: true. Use this to identify completed cards that haven't been archived.
When a card moved columns: don't read updated_at — it changes on any
modification. Use the event history instead: basecamp events <card_id> --json
records an adopted event for every column move, and a card crossing into or
out of a Done column pairs that with completed/uncompleted. See
Events.
Card Steps (checklists):
basecamp cards steps <card_id> --in <project> # List steps
basecamp cards step create "Step" --card <id> --in <project>
basecamp cards step complete <step_id> --in <project>
basecamp cards step uncomplete <step_id>
Column management:
basecamp cards column show <id> --in <project>
basecamp cards column create "Name" --in <project>
basecamp cards column update <id> --title "New"
basecamp cards column move <id> --position 2
basecamp cards column color <id> --color blue
basecamp cards column on-hold <id> # Enable on-hold section
basecamp cards column watch <id> # Subscribe to column
Messages
basecamp messages list --in <project> --json # List messages
basecamp messages show <id> --in <project> # Show message
basecamp messages create "Title" "Body" --in <project>
basecamp messages create "Draft" "WIP" --draft --in <project> # Create draft
basecamp messages publish <id> # Publish a draft
basecamp messages update <id> --title "New" --body "Updated"
basecamp messages pin <id> --in <project> # Pin to top
basecamp messages unpin <id> # Unpin
Archived/trashed messages: messages list only returns active messages. For archived or trashed messages, use basecamp recordings messages --status archived --in <project> or --status trashed.
Flags: --draft (create as draft), --no-subscribe (silent, no notifications), --subscribe "people" (comma-separated names, emails, IDs, or "me"; mutually exclusive with --no-subscribe), --message-board <id> (if multiple boards), --visible-to-clients (make visible to clients on the project; omit for the server default)
basecamp messages create "Bot update" "Done" --no-subscribe --in <project>
basecamp messages create "FYI" "Note" --subscribe "Alice,[email protected]" --in <project>
basecamp messages create "For the client" "..." --visible-to-clients --in <project>
Client visibility at create time: messages create, todolists create,
schedule create, checkins question create, and tools create accept
--visible-to-clients to post a client-visible recording in one call (for
tools create, only chat and kanban_board tool types honor it — other types
inherit the project default). Omitting the flag uses the
server default, which is context-dependent: team-only when you post as a team
member, but a client-authenticated caller always creates client-visible
records (an explicit --visible-to-clients=false is overridden server-side for
client callers). Passing --visible-to-clients posts client-visible in every
case. To change visibility on an already-created recording, use
recordings visibility <id> --visible.
Comments
basecamp comments list <recording_id> --in <project> --json
basecamp comments show <comment-id|comment-url> --json # Now returns reply_target + paste-ready mention (JSON)
basecamp comments thread <comment-id|comment-url> --json # Reply-ready: parent + focus + discussion + @mention
basecamp comments thread <comment-id> --all --json # Every fetched comment instead of a window
basecamp comments thread <comment-id> --window 11 --json # Focus-centered window of 11
basecamp comments create <recording_id> "Text" --in <project>
basecamp comments create <recording_id> "@Jane.Smith, looks good!" --in <project> # With @mention
basecamp comments update <id> "Updated" --in <project>
Cheap atoms vs. deep context (choose by need):
comments show <url> --jq '.data | {reply_target, mention}'— one API call. Returnsreply_target(recording_id— where a reply is posted, comments are flat — plusaccount_id) and a paste-ready authormention(JSON only; human output shows a reply breadcrumb). Use this for the exact-comment reply atoms.comments thread <url>— two extra calls. Adds the full parent recording, the surrounding discussion (windowed, truncation-honest), and focus attachments. Use this when the surrounding discussion matters.
Files & Documents
basecamp files list --in <project> --json # List all (folders, files, docs)
basecamp files list --vault <folder_id> --in <project> # List folder contents
basecamp files list --all-projects --json # Across every project (first 100)
basecamp files list --all-projects --limit 500 # Walk pages until 500 collected
basecamp files list --all-projects --page 2 # Exactly page 2
basecamp files list --all-projects --all # Every page (slow on big accounts)
basecamp files show <id> --in <project> # Show item (auto-detects type)
basecamp files versions <upload_id> --json # Every version of an uploaded file
basecamp files versions <upload_id> --limit 5 --json # Cap results (default: all)
basecamp files replace <upload_id> <file> # Replace the file, keep the ID/URL/comments
basecamp files replace <upload_id> <file> --description "v2 notes" # Also set a new description
basecamp files download <id> --in <project> # Download file
basecamp files download <id> --out ./dir # Download to specific dir
basecamp files download "https://storage.../download/f" # Download from storage URL
basecamp files uploads create <file> --in <project> # Upload file to root
basecamp files uploads create <file> --vault <folder_id> --in <project> # Upload to folder
basecamp files uploads create <file> --visible-to-clients --in <project> # Client-visible (root folder only)
basecamp files folder create "Folder" --in <project>
basecamp files doc create "Doc" "Body" --in <project>
basecamp files doc create "Draft" --draft --in <project>
basecamp files doc create "Notes" "..." --no-subscribe --in <project>
basecamp files doc create "For client" "..." --visible-to-clients --in <project> # Client-visible (root folder only)
basecamp files update <document_id> --title "New" --content "Updated"
basecamp files update <document_id> --title "New" --in <project> # Preserves existing document content
basecamp files update <document_id> --content "Updated" --in <project> # Preserves existing document title
Document update semantics: basecamp files update <document_id> is safe for partial updates in the CLI: when you pass only --title or only --content, the CLI first fetches the current document and preserves the untouched field.
Client visibility at create time: doc create and uploads create accept
--visible-to-clients, but the server only honors it in the project's root
Docs & Files folder. Targeting a nested folder (--vault/--folder) with the
flag is a hard error raised before anything is uploaded — a nested item inherits
its folder's visibility, and that can't be changed per-item afterward (the
visibility endpoint rejects nested docs/uploads). To make a nested item
client-visible, create it in the root folder, or change the eligible top-level
ancestor that controls the folder's visibility first. Omitting the flag uses the
server default; as with Messages, a client-authenticated caller always creates
client-visible records regardless. recordings visibility is not a
remediation for nested docs/uploads.
Upload versions: replacing a file keeps the earlier copies under the same
upload ID, so basecamp files versions <upload_id> is how you see the history of
one file. A file that was never replaced returns its single current version, not
an error. Only --page 1 is accepted; use --all to walk every page.
basecamp files replace <upload_id> <file> publishes a new version in place —
the upload keeps its ID, URL and comments, nobody is notified, and the
description carries forward unless --description is given. Use it instead of
uploads create when shipping a new build of the same file.
Subcommands: folders, uploads, documents (each with pagination flags)
Schedule
For upcoming events across all projects, use basecamp reports schedule --json.
basecamp schedule info --in <project> --json # Schedule info
basecamp schedule entries --in <project> --json # List entries
basecamp schedule show <id> --in <project> # Entry details
basecamp schedule show <id> --date 20240315 # Specific occurrence (recurring)
basecamp schedule create "Event" --starts-at "2024-03-15T09:00:00Z" --ends-at "2024-03-15T10:00:00Z" --in <project>
basecamp schedule create "Meeting" --all-day --notify --participants 1,2,3 --in <project>
basecamp schedule create "Sync" --starts-at "..." --ends-at "..." --no-subscribe --in <project>
basecamp schedule update <id> --summary "New title" --starts-at "..."
basecamp schedule settings --include-due --in <project> # Include todos/cards due dates
Flags: --all-day, --notify, --participants <ids>, --no-subscribe, --subscribe "people" (mutually exclusive), --status (active/archived/trashed), --visible-to-clients (make visible to clients; omit for the server default)
Check-ins
basecamp checkins --in <project> --json # Questionnaire info
basecamp checkins questions --in <project> # List questions
basecamp checkins question <id> --in <project> # Question details
basecamp checkins answers <question_id> --in <project> # List answers
basecamp checkins answers <question_id> --by me --in <project> # My answers only
basecamp checkins answers <question_id> --by "Alice Smith" --in <project> # Filter by person (name, email, or ID)
basecamp checkins answer <id> --in <project> # Answer details
basecamp checkins question create "What did you work on?" --in <project>
basecamp checkins question update <id> "New question" --frequency every_week
basecamp checkins answer create <question-id> "My answer" --in <project> # Defaults to today
basecamp checkins answer update <id> "Updated" --in <project>
Schedule options: --frequency (every_day, every_week, every_other_week, every_month, on_certain_days), --days 1,2,3,4,5 (0=Sun), --time "5:00pm"
Client visibility: checkins question create accepts --visible-to-clients to make the question visible to clients (omit for the server default; see the note under Messages for the context-dependent rule).
Managing a question:
basecamp checkins question pause <id> --json # Stop asking it
basecamp checkins question resume <id> --json # Start asking it again
basecamp checkins question answerers <id> --json # Who answers it
basecamp checkins question notify <id> --on-answer --json
basecamp checkins question notify <id> --no-on-answer --json
basecamp checkins question notify <id> --digest-include-unanswered --json
notify changes your own settings, and each one is left alone unless you
name it — so --on-answer does not silently reset the digest setting. The
--no-... spellings send an explicit false; passing neither setting is refused
rather than sent as an empty update.
Your pending reminders (account-wide, no --in):
basecamp checkins reminders --json
basecamp checkins reminders --limit 10 --json
reminders and answerers take --limit but deliberately no --page: the
API does not honor a page number on these, so the flag would accept a value it
could not act on.
Timeline
basecamp timeline --json # Account-wide activity
basecamp timeline --in <project> --json # Project activity
basecamp timeline me --json # Your activity
basecamp timeline --person <id> --json # Person's activity
basecamp timeline --watch # Live monitoring (TUI)
basecamp timeline --watch --interval 60 # Poll every 60 seconds
Use --limit N to cap results or --all to fetch everything (default: 100 events). --all and --page cannot be combined with --watch.
Events (change history)
basecamp timeline reports activity across a project or account. For the audit
trail of one specific item — todo, card, message, document — use basecamp events:
basecamp events <id|url> --json # Change history for one item
basecamp events <id> --limit 25 --json # Cap results (default 100)
basecamp events <id> --all --json # Fetch everything
Common action values: created, completed/uncompleted,
assignment_changed, content_changed, archived/unarchived,
commented_on, and — for cards — adopted, which is recorded every time a card
moves to another column. That makes events the way to answer "when did this
card move?" or "when was this actually finished?", neither of which updated_at
can tell you.
--page accepts only 1; use --all to walk every page.
Recordings (Cross-project)
Loading...
Select a file to preview
Analyzing security...
Checking scan reports and verification data.
Bill of Materials
Everything this skill can do — files, network, commands, and more.