Supplementary reference, not contract. The canonical schema view for the CLI is
cli-design.md§2 (which was generated from the live SDK types and verified against the docs). This file is a quick orientation cheat sheet — keep in sync, but don't treat it as authoritative for design decisions.
This is a quick reference for the entities the CLI cares about. The canonical Monday docs are at https://developer.monday.com/api-reference/ — this file exists so an agent reading the repo can get oriented without leaving the working directory.
- URL:
https://api.monday.com/v2 - Method:
POST - Auth header:
Authorization: <token>(noBearerprefix) - API version header:
API-Version: YYYY-MM(optional — omit to track current stable; pin for reproducibility) - Content-Type:
application/json(ormultipart/form-datafor file uploads)
Account
└── Workspace (groups boards by team/project)
└── Board (the spreadsheet-like primary object)
├── Group (a section of rows on a board)
│ └── Item (a row — a task, ticket, etc.)
│ ├── Column value (typed cell — text, status, person, …)
│ ├── Subitem (nested item — board has its own subitems board)
│ └── Update (comment thread on the item)
└── Column (column definition: id, type, settings)
Items are the primary object an agent will create / read / update.
- Read one:
items(ids: [ID!])— accepts an array, returns full items. - Read many (paginated):
boards(ids: [ID!]) { items_page(limit: 500) { cursor items { ... } } }— thennext_items_page(cursor: ...)to paginate. The flatitemsquery withoutidsis deprecated. - Create:
create_item(board_id, item_name, group_id?, column_values?, position_relative_method?, relative_to?, create_labels_if_missing?).column_valuesis a JSON object keyed by column ID (the SDK'sJSONscalar handles wire stringification — the CLI neverJSON.stringifys). The CLI surfaces this asmonday item create --board <bid> --name <n>with optional--set/--set-raw/--group/--position before|after --relative-to <iid>. M9 ships single-round-trip (every--setvalue bundles intocolumn_values— no fallback two-call pattern; partial-state risk by design per cli-design §5.8). - Create subitem:
create_subitem(parent_item_id, item_name, column_values?, create_labels_if_missing?). Surfaced asmonday item create --parent <iid> --name <n>(M9, classic boards only —hierarchy_type: "multi_level"rejected pre-mutation; deferred to v0.3). The auto-generated subitems board's ID is derived server-side from the parent; the CLI also derives it client-side (parent'ssubtaskscolumn'ssettings_str.boardIds[0]) for column-token resolution. - Update column value:
change_column_value(single column, typed per column kind) orchange_multiple_column_values(bulk). - Move:
move_item_to_group(item_id, group_id),move_item_to_board(item_id, board_id: ID!, group_id: ID!, columns_mapping?: [ColumnMappingInput!])(M11). Both return the post-mutationItem.move_item_to_boardrequiresgroup_id— Monday picks no default destination group on the target board, so the CLI'smonday item moverequires--to-groupfor both same-board AND cross-board forms.columns_mappingis[{source: ID!, target?: ID}]— strictly source-to-target ID mapping with no value slot, so the CLI ships only the simple--columns-mapping '{<src>: <target>}'form; value-overrides are deferred to v0.3. Same-boardmove_item_to_groupis wire-level no-op when the item is already in the target group (idempotent); cross-boardmove_item_to_boardre-running on the target board is undefined SDK behaviour, so the verb-levelidempotent: falseis the conservative bound. Neither accepts a position — Monday's 2026-01 API does NOT expose a way to reorder existing items via the public GraphQL surface (position_relative_methodis only oncreate_itemandcreate_group). Post-create reordering is deferred until Monday adds the mutation. - Archive / delete:
archive_item(item_id),delete_item(item_id)(M10). Both return the post-mutationItem;archive_itemis wire-level idempotent (re-archive is a no-op),delete_itemreturnsnot_foundpast the first call (the CLI marksidempotent: falsebecause re-running with the same<iid>after an interimcreatewould delete the new item). - Duplicate:
duplicate_item(item_id, board_id: ID!, with_updates?: Boolean)(M10). Note the requiredboard_idparameter — the CLI looks it up via a separateItemBoardLookupround-trip before firing the mutation, so duplicate's live path is two-leg unlike archive + delete's single-leg paths.with_updates: truecopies the source item's updates (Monday "comments") onto the duplicate. Not idempotent — every call creates a new item. - Upsert:
monday item upsert --board <bid> --name <n> --match-by <col>[,...] [--set ...](M12) — three-state branch decision (decideBranch): 0 matches →create_item; 1 match →change_multiple_column_valueswith syntheticname; 2+ matches →ambiguous_match(the 27th stable error code) withdetails.candidates: [{id, name}](up to 10) so the agent tightens--match-byand retries. v0.2 match-by safe-list: always-safe (name/text/long_text/numbers/ external_id-shaped hidden text), safe-via-label-text (status/dropdown), restricted (people→meonly). Sequential- retry idempotent; concurrent agents are not a uniqueness guarantee — see cli-design §5.8 + §9.1 + §9.3.
"Updates" in Monday lingo are item-scoped comments. M13 ships the full mutation surface around them.
- Create:
create_update(item_id, body, parent_id?)— M5b ships the create surface. M13 adds the rest: - Reply:
create_update(item_id?, body, parent_id)— threaded reply, surfaced asmonday update reply <parent-id> --body <md>. Noteparent_idIS the parent update's ID; Monday derivesitem_idserver-side from the parent. - Edit:
edit_update(id, body)—monday update edit <update-id> --body <md>.idempotent: true. - Delete:
delete_update(id)—monday update delete <id> --yes. Destructive;--yesmandatory. - Like / unlike / pin / unpin:
like_update/unlike_update/pin_to_top/unpin_from_top, all keyed by the update id. The CLI ships them as four matching verbs. - Clear-all:
clear_all_updates(item_id)—monday update clear-all <item-id> --yes. First partial-success consumer — returnsdata.results: [{update_id, ok, error?}]. Envelope isok: truewhenever the dispatch ran (even if every per- target call failed); per-target failures land indata.results[i].error, not the top-levelerrorslot. This is the §6.4 partial-success contract M13 introduced; M14's workspace add/remove-users + M15's board add-users reuse it.
Default-replies behaviour change (M13's one breaking
change): monday update list no longer populates replies by
default — pass --with-replies to opt in. The pre-M13 default
populated replies; agents relying on the old behaviour pin
the flag explicitly.
Workspace is the top-level container for boards. M14 ships
the full mutation surface:
- Create:
create_workspace(name, kind, description?)—monday workspace create --name <n> --kind <open|closed> [--description <d>]. - Update:
update_workspace(id, attributes: {name? / description?})— per-attribute fan-out via theattributesinput object. - Delete:
delete_workspace(workspace_id)— destructive,--yesmandatory. - Add / remove users:
add_users_to_workspace/delete_users_from_workspacewith[user_ids: [ID!]!, kind: 'subscriber' | 'owner']. M14's first resolver-fronted partial-success consumer —--usersaccepts mixed numeric IDs + emails; emails resolve throughuserByEmailper-token, and per-token resolution failures land asdata.results[i]: { user_id: <input-token>, ok: false, error: { code: 'user_not_found', message } }records (the input token echoed verbatim because the resolved Monday ID isn't known on a failure). Whole-callok: falsewith codeuser_not_foundfires only when no dispatchable user_id remains after parsing/resolution.
Board is the central organising entity. M15 ships the full
mutation surface:
- Create:
create_board(board_name, board_kind, workspace_id?, description?, folder_id?, template_id?, board_owner_ids?, board_owner_team_ids?, board_subscriber_ids?, board_subscriber_team_ids?)—monday board create --name <n> --kind <public|private|share>with optional--workspace <id>/--folder <id>/--template <bid>. - Update:
update_board(board_id, board_attribute, new_value)— per-attribute fan-out across the singleupdate_boardwire surface. CLI flag mapping:--name→board_attribute: name,--description→board_attribute: description. The success-envelopedataprojects from a force-live final read leg because Monday's per-attribute calls return only the changed slice (distinguishes board-update from M16 column-update + M17 group-update which both project from the trailing per-attribute response). - Archive / delete:
archive_board(board_id)/delete_board(board_id). Both destructive,--yesmandatory. - Duplicate:
duplicate_board(board_id, board_name, duplicate_type: DuplicateBoardType, folder_id?, workspace_id?, keep_subscribers?). Wrapped envelope —data: { board: <projection>, is_async }because Monday'sBoardDuplicationcarries anis_asyncslot the projection doesn't model. Whenis_async: trueagents poll the new board for readiness rather than treating the response as the final state. - Add users:
add_subscribers_to_board(board_id, user_ids, kind)— partial-success consumer (M14 add/remove-users reused).
M16 and M17 ship the full board-structure mutation surface plus
introduce the §8 eager-invalidation contract: every board-
structure mutation calls invalidateBoard(boardId) post-success
so a same-process board describe / board groups / board columns sees fresh state without TTL eviction.
- Create:
create_column(board_id, column_type, title, description?, defaults?). Note the wire arg isdefaults: JSON, NOTsettings_str(which is the read-side serialisation). M16 omitsid(agent-supplied custom column id) +after_column_id(placement) — agents needing them use M9'sdev mutateescape hatch. The CLI emits anoncanonical_column_typewarning for non-allowlisted types with per-categorysuggested_write_path(raw_writable/read_only_forever/files_shaped). - Update: per-attribute fan-out across two wire surfaces.
--titleroutes tochange_column_title(board_id, column_id, title)(the more-specific surface);--descriptionroutes tochange_column_metadata(board_id, column_id, column_property: description, value).column-update's success-envelopedataprojects from the trailing per- attribute call's response (no force-live read leg — distinguishes column-update from board-update). - Delete:
delete_column(board_id, column_id). Destructive,--yesmandatory. First two-tuple destructive verb —confirmation_requiredenvelope echoes{board_id, column_id, hint}per cli-design §6.5 (the wire signature is two-tuple, so both ids ride indetails). Note: Monday has NOarchive_columnmutation — column lifecycle is delete-only.
- Create:
create_group(board_id, group_name, group_color?).--nameis required;--colorvalidates against the pinnedGROUP_COLOR_VALUESpalette insrc/api/group-color.ts(41- name set covering Monday's documented group colours). M17 omits all three placement arguments (positiondeprecated,position_relative_method+relative_todeferred to v0.3 with--before <gid>/--after <gid>flags). - Update:
update_group(board_id, group_id, group_attribute: GroupAttributes!, new_value: String!)— per- attribute fan-out across a single wire surface (simpler than column-update's two-surface fan-out).--name→group_attribute: title,--color→group_attribute: color. v0.2 surfaces onlytitle+color; the position-related enum values (positiondeprecated,relative_position_after/relative_position_beforenon-deprecated) are deferred to v0.3. No force-live read leg — Monday'supdate_groupreturns the full Group projection post-mutation, so the trailing per-attribute call's response is authoritative (mirrors column-update; diverges from board-update's force- live shape). This is the load-bearing M17-pre-flight finding. - Archive:
archive_group(board_id, group_id). Destructive,--yesmandatory. 2nd two-tuple destructive verb after M16 column-delete;confirmation_requiredenvelope echoes{board_id, group_id, hint}. Idempotent — re-archiving an already-archived group is a no-op. Dry-run is snapshot- bearing (mirrorsboard archive— recoverable destructive, preview shows what will be hidden). - Duplicate:
duplicate_group(board_id, group_id, add_to_top?, group_title?). Load-bearing divergence from sibling duplicate verbs — Monday'sduplicate_groupwire signature has NOwith_updatesargument, unlikeduplicate_itemandduplicate_board. The M17 pre-flight pinned the wire truth and dropped the flag.--namemaps to wiregroup_title?. - Delete:
delete_group(board_id, group_id). Destructive,--yesmandatory. 3rd two-tuple destructive verb after column-delete + group-archive. Dry-run is minimal destructive- no-read (mirrorscolumn-delete/board delete).
Each column type has its own JSON shape. The 10 types the CLI's friendly translator surfaces (writable allowlist post-M8):
| Type | Example wire value | CLI translator |
|---|---|---|
text |
"some text" |
bare string |
long_text |
{"text": "..."} (multi) / "..." (simple) |
bare string + multi re-wrap |
numbers |
"42" |
bare string |
status |
{"label": "Done"} or {"index": 1} |
label-first / {index:N} for non-negative integer |
dropdown |
{"labels": ["Backend"]} or {"ids": [1,2]} |
comma-split, all-numeric → ids, else labels |
date |
{"date": "2026-04-29", "time": "14:30:00"} |
ISO + relative tokens (+1w / today / tomorrow) resolved against MONDAY_TIMEZONE |
people |
{"personsAndTeams": [{"id": 12345, "kind": "person"}]} |
comma-split emails / me token, resolved via userByEmail |
link (M8) |
{"url": "https://example.com", "text": "Example"} |
pipe-form `url |
email (M8) |
{"email": "alice@example.test", "text": "Alice"} |
pipe-form `email |
phone (M8) |
{"phone": "+14155550100", "countryShortName": "US"} |
E.164 with explicit phone:countryCode |
tags (M19) |
{"tag_ids": [101, 202]} |
comma-split tag names, resolved via per-account directory cache |
board_relation (M19) |
{"item_ids": [12345, 67890]} |
comma-split item IDs, validated against column.settings.boardIds |
dependency (M19) |
{"item_ids": [12345, 67890]} |
sibling of board_relation; reads column.settings.dependencyBoards |
Other types (creation_log, mirror, formula, auto_number,
last_updated, item_id, files, battery, etc.) surface
unsupported_column_type from the friendly path. The M8
--set-raw <col>=<json> escape hatch accepts the wire JSON
verbatim; it's gated against read-only-forever and files-shaped
types (add_file_to_column is a separate multipart mutation
deferred to v0.4).
The
personcolumn type is deprecated in Monday's schema — usepeople(plural) for both single-assignee and multi-assignee values. SDK 14.0.0 still types both, but new boards always create thepeopleform.
Use change_simple_column_value for the simple text/number case to skip
the JSON-string layer.
The CLI accepts a column ID or a column title in --set,
--where, and --columns flags. Resolution rules are normative —
agents key off them. The full implementation is in
src/api/columns.ts; the canonical contract is cli-design.md §5.3.
Order of resolution:
- Exact ID match — Monday IDs are stable lowercase snake_case
strings (
status_4,date_1). Case-sensitive. - NFC-normalised exact title match — titles are NFC-normalised,
trimmed, internal whitespace collapsed to single spaces. So
Café(composed) andCafé(decomposed) resolve identically;Plan AandPlan A(multiple spaces) match the same target. - NFC + case-fold fallback — locale-independent
(
toLocaleLowerCase('und')). Picks upSTATUSmatchingStatuswhen no NFC-exact match exists. - Multi-match at any level →
ambiguous_columnwithdetails.candidates. - No match →
column_not_found. Read-paths that hit a missing column on a cache hit auto-refresh the metadata once before surfacing.
Explicit prefix syntax: id:status_4 forces the ID path,
title:Status forces the title path. Useful when an ID and a title
collide. The id: form still emits a column_token_collision
warning when the value also matches a different column's title —
informational so agents auditing data shape see the overlap.
Archived columns are filtered out by default; they surface as
column_not_found for read paths. Pass --include-archived on read
commands to see them. Mutations against an archived column return
column_archived regardless.
monday board describe <bid> is the single richest read in v0.1 —
columns + groups + hierarchy_type + is_leaf + per-column
example_set of suggested --set invocations for every writable
column type. Agents that have run board describe once can
construct a mutation against any M5b-writable column without
consulting Monday's docs. Ships live for v0.1 reads; M5b mutations
read it through the cached loadBoardMetadata helper.
monday board doctor <bid> (M6) layers diagnostics on top of the
same metadata: duplicate column titles (would cause
ambiguous_column at write time), columns with non-writable types
(per the v0.1 / v0.2 / read-only-forever roadmap split), and
broken board_relation targets. Run it before a bulk update
session to catch problems up front.
Monday Dev is built on top of normal boards/items with conventions:
- Tasks board — items are tasks; usually has
Sprint,Epic,Status,Owner,Priority,Effortcolumns. - Epics board — connected to the tasks board via a
connect_boardscolumn (the "Epic" column on tasks links to a row on the epics board). - Sprints board — current/next/past sprints; tasks reference a sprint via a connect-boards column.
- Bugs board — same shape as tasks, separate board.
There is no separate Dev API — everything goes through the standard
GraphQL items/boards endpoints. The CLI surfaces these as monday dev …
subcommands for ergonomic shortcuts (e.g. monday dev sprint current)
that resolve the right board IDs from config.
Monday charges a "complexity budget" per minute (10M points / minute by default). Each field has a complexity cost that scales with the number of objects returned. A query that returns 500 items × 20 columns costs substantially more than the same query for 10 items.
The API wrapper in src/api/ should:
- Surface
429(rate limit) and complexity errors with theretry_in_secondsfield from Monday's response. - Apply exponential backoff with jitter for transient errors.
- Log query complexity at
--verboseso users can spot expensive calls.
add_file_to_column and add_file_to_update use multipart/form-data.
The SDK's request() accepts File/Blob instances directly — see the
upstream README for the canonical pattern.