# Businessmap concepts

What the objects a script manipulates actually are. Enum values below were read from the live
OpenAPI spec; confirm anything time-sensitive with `bmapi.py`.

Businessmap was formerly called **Kanbanize**, which is why accounts live on either
`<subdomain>.kanbanize.com` or `<subdomain>.businessmap.io` and why the legacy API is still "v1".

## The container hierarchy

```
Account (one subdomain)
└── Workspace          type 1 = Team, 2 = Management
    └── Board          type 1 = Kanban board, 2 = AI Canvas
        └── Workflow   type 0 = Cards, 1 = Initiative, 2 = Timeline
            ├── Column   vertical stage; nestable via parent_column_id
            └── Lane     horizontal swimlane; nestable via parent_lane_id
                └── Card
```

A board can hold **several workflows** at once — commonly a Cards workflow beside an Initiative
workflow. A card's position is the tuple *(board, workflow, column, lane, position)*.

Columns carry a `section`, which is the board area they belong to:

| `section` | Area |
|---|---|
| `1` | Backlog |
| `2` | Requested |
| `3` | In Progress |
| `4` | Done |
| `5` | Archive |
| `null` | the column is a subcolumn |

Cards move by **PATCHing `column_id` / `lane_id` / `position`**, never by a dedicated "move"
endpoint.

## Cards

The unit of work. Key fields on the card object:

- **Identity** — `card_id` (global), `custom_id` (human-facing), `board_id`, `workflow_id`.
- **Placement** — `column_id`, `lane_id`, `position`, `section`, plus `last_column_id` /
  `last_lane_id` and `in_current_position_since`.
- **Content** — `title`, `description` (**HTML**, not Markdown), `size`, `priority`, `color`,
  `deadline`, `type_id`. `priority` runs **backwards** — `1` Critical, `2` High, `3` Average,
  `4` Low, and `null` counts as Average.
- **People** — `owner_user_id`, `co_owner_ids`, `watcher_ids`, `reporter`.
- **Flow timestamps** — `created_at`, `first_start_time`, `last_end_time` and friends, which is
  what cycle-time reporting is built on.
- **Attached collections** — `custom_fields`, `stickers`, `tag_ids`, `milestone_ids`, `subtasks`,
  `attachments`, `linked_cards`, `annotations`, `cover_image`.
- **State** — `is_blocked` + `block_reason`, `child_card_stats`, subtask counts.

Cards relate to each other as **parent/child** (breakdown) and **predecessor/successor**
(sequencing), plus a loose "relative" link. Initiatives are simply cards living on an Initiative
workflow, with ordinary cards as their children.

> Descriptions and comments are HTML. Build them as `<p>…</p>`, `<ul><li>…</li></ul>`, `<br>` —
> raw newlines and Markdown render literally. Escape any interpolated user content.

## Card classification

Four separate mechanisms, often confused:

| | What it is | Cardinality |
|---|---|---|
| **Card type** | The kind of work — Task, Bug, Feature. Carries colour and an icon. | one per card |
| **Tag** | Free-form label for filtering. | many per card |
| **Sticker** | Icon-and-label marker, more visual than a tag. | many per card |
| **Custom field** | A typed, named data slot. | many per card |

All four are defined **at account level** and then made available per board through `availability`
and `is_enabled`. A card can only carry one whose definition is enabled on its board — a very
common cause of a 400 from an otherwise correct request.

## Custom fields

Defined once for the account, attached to boards, then given values per card. The type decides the
value shape:

`SingleLine` · `MultiLine` · `Number` · `Date` · `Link` · `Dropdown` · `CardPicker` ·
`Contributor` · `File` · `Vote`

Dropdown fields have **allowed values** with their own IDs — setting one means sending the value
ID, not the label. `CardPicker` holds selected cards, `Contributor` holds users, `File` holds
attachments, `Vote` holds votes. Read a card's `custom_fields` array and match on `field_id`.

Because a missing field returns the string `"undefined"` rather than null, always guard:

```
var value = "undefined";
foreach (card->custom_fields as k => field) {
    if (field->field_id == myFieldId) { value = field->value; }
}
if (value == "undefined") { exit("field not set"); }
```

## Outcomes — the OKR model

**Outcomes are Businessmap's OKRs**, and they hang off a card rather than existing standalone.

- `name`, `owner_user_id`, `comment`, optional `prefix` / `suffix` for display units.
- `starting_value` → `current_value` → `target_value`, each with an optional `*_formula` so the
  value can be computed rather than entered.
- `operator` — `or_more` (higher is better) or `or_less` (lower is better).
- `starting_time`, `target_time`, `current_value_since` — the measurement window.
- `weight` — contribution when rolled up.
- `checkpoints` — interim targets; `values` — the measured time series.
- `links_to_cards`, `links_to_outcomes`, `links_from_outcomes` — how objectives cascade into
  contributing key results and delivery cards.
- `tag_ids` — outcomes have their own tags.

## Scheduling and effort

- **Milestones** — dated markers, account-defined and board-enabled, attached to many cards.
- **Logged time** — time entries against a card, grouped by category.
- **Block reasons** / **discard reasons** — controlled vocabularies for why a card is stuck or was
  dropped; `is_blocked` pairs with `block_reason`.

## People

**Users** belong to **teams** and hold **roles** whose **permissions** are granted per board or
workspace. `/me` returns the identity behind the current API key — useful for defaulting an email
recipient or an owner.

## Business Rules

Board-level automation: a trigger (card created, moved, field changed, a schedule…), optional
conditions, and actions. The **"Invoke Web Service"** action is what calls Code Runner, so a
script is always the tail end of a rule someone configured. The rule decides *when* the script
runs and which values get interpolated into its source; the script decides what happens next. See
SETUP.md.

## API surface

`v2` is the current API and covers 439 paths across 231 tagged areas — cards, boards, workflows,
custom fields, outcomes, users, webhooks and so on. `v1` (legacy), `v3` and the reporting API are
reachable through `send_to_bm()` but are rarely the right choice for new work.

Common shapes:

- Responses wrap payloads in a `data` key.
- List endpoints paginate. Check the operation for its own `offset`/`limit` or cursor parameters
  rather than assuming.
- Many list endpoints accept comma-separated ID filters (`card_ids`, `board_ids`), which is how you
  avoid a request per card.
- Batch endpoints exist for several entities (`/cards/createMany`, `deleteMany`, `updateMany`, and
  various `batch…Operations`) — prefer them inside loops, given the 180 s ceiling.

Look the endpoint up rather than trusting this summary:

```
python3 "$SKILL/scripts/bmapi.py" search outcome
python3 "$SKILL/scripts/bmapi.py" show PATCH /cards/{card_id}
```
