> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usertour.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Content representation

> How a content version is read and written — steps, blocks, rules, and type-specific data.

A piece of **content** (flow, checklist, launcher, banner, tracker,
resource-center, announcement) has **versions**. The editable head is the *draft* version
(`editedVersionId`); publishing makes a version live in an environment.

A version's authorable body is exposed as a stable **representation** — the same
shape you read and write — rather than Usertour's internal builder model. You
**read** it with `expand`, and **write** it with
`PATCH content/{contentId}/versions/{id}` (drafts only).

## A version's body

| Content type                                                                         | Body      | Read with       | Write field |
| ------------------------------------------------------------------------------------ | --------- | --------------- | ----------- |
| `flow` (tooltip / modal / …)                                                         | **steps** | `?expand=steps` | `steps`     |
| `checklist` / `launcher` / `banner` / `tracker` / `resource-center` / `announcement` | **data**  | `?expand=data`  | `data`      |

Both also carry version-level **start / hide rules** (`startRules`, `hideRules`)
and a `themeId`.

## Steps (flow)

```json theme={null}
{
  "object": "step",
  "id": "cm9...",          // server-owned; the write handle (see below)
  "key": "welcome",        // your handle, write-only — wire goto_step by it
  "name": "Welcome",
  "type": "modal",
  "placement": { "position": "center" },
  "content": [
    { "object": "block", "type": "text", "markdown": "Welcome **{{ first_name | default: \"there\" }}**" },
    { "object": "block", "type": "button", "text": "Next", "variant": "primary", "actions": [{ "type": "goto_step", "step": "pricing" }] }
  ],
  "triggers": [ { "when": [ /* conditions */ ], "do": [ /* actions */ ] } ]
}
```

**Blocks** (`content[]`): `text` (markdown), `image`, `button`, `embed`,
`question` (nps / rating / text / choice), and `columns` (a row of nested blocks).
Full payload per block type: **[Blocks](/api-reference-v2/blocks)**.

**Rich text** is a small markdown subset: paragraphs, `# `/`## ` headings (h1/h2
only — no h3+), `-`/`*` and `1.` lists, ` ``` ` code fences; inline `**bold**`,
`*italic*`, `[text](url)` (append `{target=_blank}` right after a link to open
it in a new tab), and a Liquid-style user-attribute placeholder
`{{ attribute | default: "fallback" }}`. Emphasis applies to placeholders too:
`**Hi {{ name }}!**` renders the whole greeting — the interpolated value
included — in bold.

### Step identifiers: `id`, `cvid`, `key`

* **`id`** — server-owned **write handle**. Echo a step's `id` to update it;
  **omit** `id` to create a new step. Steps you omit from the list are deleted.
* **`cvid`** — server-owned, version-stable reference. Returned on read; survives
  version copies. You never set one.
* **`key`** — **your** handle, **write-only** (not stored, not returned). Give a
  step a `key` and a `goto_step` action elsewhere in the *same write* can target
  it with `"step": "<key>"` — so you can author a whole flow, including forward
  and cyclic links, in one request without knowing cvids yet.

A `goto_step` `step` resolves to a `key` you sent in this request, or an existing
`cvid` — both map to the same step, so you don't have to choose. The stored value
is always the cvid (reads return cvids).

## Rules

Conditions and actions are reused everywhere (step triggers, button/question
actions, version start/hide rules):

* **Conditions** — a recursive tree (`attribute` with a `scope`, `current_url`,
  `segment`, `element`, `content_state`, `event`, `text_input`, `text_filled`,
  `time_window`, nested `group` with `all`/`any`).
* **Actions** — `goto_step`, `start_content`, `navigate`, `dismiss`.
  (`run_javascript` is **echo-only**: builder-authored scripts appear on reads
  and are preserved by echoing them back unchanged; authoring one is rejected.)
* **Start rules** (`startRules`) — when the content auto-starts, plus frequency /
  priority / wait. **Hide rules** (`hideRules`) — when to hide it.

Field reference: **[Conditions & actions](/api-reference-v2/conditions-and-actions)**
and **[Start, hide & trigger rules](/api-reference-v2/rules)**.

## Type-specific data (non-flow)

For the non-flow types, the body lives in `data` (read with `?expand=data`):
checklist items, launcher target/tooltip/behavior, banner placement/content,
tracker event, or the resource-center block tree. Nested rich content reuses the
same blocks, and conditions/actions reuse the same rules.

A newly created non-flow content is seeded with its type's default `data`, so you
only send the fields you want to change — the write is a field-level merge. Full
payload per type: **[Type-specific data](/api-reference-v2/type-data)**.

## Writing

`PATCH /v2/projects/{projectId}/content/{contentId}/versions/{id}` — send only the
fields you want to change:

```json theme={null}
{
  "steps": [ /* full list, merged by id */ ],
  "startRules": { "when": [ /* conditions */ ] },
  "hideRules": null,                  // null clears
  "themeId": "cm7...",                // switches theme (cannot be cleared)
  "data": { /* for non-flow types */ }
}
```

* Writes target **draft** versions only.
* It is a **field-level merge**: styling, layout, and other details the
  representation doesn't express are preserved from the existing version.
* Omitted top-level fields are left untouched.

To start a fresh draft, `POST content/{contentId}/versions` (forks the edited
version), or `POST content/{contentId}/versions/{id}/restore` (forks a historical
version forward).
Publishing is a separate, explicit step — see the **Publish a version** endpoint.
