---
title: "Writing API"
description: "Use when you replace a book, replace one page, append or prepend content, list versions or restore a version over HTTP, and need the version check that stops two writers overwriting each other."
url: https://docs.heytrove.ai/reference/api/items-write
updated: 2026-10-01
---

# Writing API

> Load this page when: you need to edit book text over HTTP, or recover from a VERSION_CONFLICT

## For agents

- Read the book first and send its version as expectedVersion on every replace.
- On VERSION_CONFLICT, read again, redo your edit on the fresh text, and retry. Nothing was lost.
- Use append when you only add content. It never conflicts.

Every write saves the earlier text as a version first, except appends. Writes share one budget of content writes. See [Errors](https://docs.heytrove.ai/reference/api.md#errors).

The write checks work like this: read the book with [Get full content](https://docs.heytrove.ai/reference/api/items-read.md#get-full-content), and send its `version` back as `expectedVersion`. If anyone wrote since, the server refuses your write.

## Replace a whole book

`PUT https://heytrove.ai/api/v1/items/{itemId}/content`

Replaces the full text. Everything not in `content` is deleted. Use it for books under 1 MB.

**Parameters**

| Name | In | Type | Required | Constraints | Meaning |
| --- | --- | --- | --- | --- | --- |
| `itemId` | path | string | yes | none | Book to replace. |
| `content` | body | string | yes | up to 1,000,000 bytes | The new markdown. Top-level `# ` headings split it into pages. |
| `expectedVersion` | body | integer | no | 0 or more | The `version` you read. Always send it when several writers can touch the book. |

**Request**

```bash
curl -X PUT https://heytrove.ai/api/v1/items/cmabc123/content \
  -H "Authorization: Bearer trove_your_key" \
  -H "Content-Type: application/json" \
  -d '{"content":"# Anatomy of an Index\n\nUpdated text.","expectedVersion":7}'
```

**Response**

```json
{
  "id": "cmabc123",
  "title": "Use The Index, Luke",
  "version": 8,
  "pageCount": 1,
  "updatedAt": "2026-10-01T09:30:00.000Z"
}
```

**Errors**

| Status | Message | Code | Fix |
| --- | --- | --- | --- |
| 400 | `content is required` | none | Send a string. |
| 400 | ``expectedVersion must be a non-negative integer — pass the `version` field returned by GET /items/:id/content`` | none | Send an integer. |
| 409 | `This book changed since you read it - you based this write on version {a} and it is now version {b}. Your write was NOT applied and nothing was lost.` | `VERSION_CONFLICT` | Read again, redo your edit, and retry with `expectedVersion` {b}. The body adds `currentVersion`, `changedSince`, `nextAction` and `retryable`. |
| 413 | `content exceeds maximum size of 1000000 bytes — …` | none | Edit one page, or append. |
| 403 | `This item is read-only for your account — you can view it but not edit it (you don't own it and don't have team write access).` | none | Ask the owner for write access. |
| 404 | `Item not found` | none | Check the id. |
| 429 | `Too many content writes in a short window — wait a minute and retry.` | `RATE_LIMITED` | Wait for `Retry-After`. |

**CLI equivalent**: [trove items put](https://docs.heytrove.ai/reference/cli/items-writing.md#trove-items-put).

## Replace one page

`PUT https://heytrove.ai/api/v1/items/{itemId}/pages/{pageNum}`

Replaces exactly one page. Only that page travels, so it works on books of any size. The new content must be one page: a single top-level `# ` heading, with other headings at `##` or lower. Page 1 may have no heading.

**Parameters**

| Name | In | Type | Required | Constraints | Meaning |
| --- | --- | --- | --- | --- | --- |
| `itemId` | path | string | yes | none | Book to edit. |
| `pageNum` | path | integer | yes | 1 to 999999 | Page to replace. |
| `content` | body | string | yes | not empty, up to 1,000,000 bytes | The new page. |
| `expectedVersion` | body | integer | no | 0 or more | The `version` you read. |

**Request**

```bash
curl -X PUT https://heytrove.ai/api/v1/items/cmabc123/pages/4 \
  -H "Authorization: Bearer trove_your_key" \
  -H "Content-Type: application/json" \
  -d '{"content":"# Where Clauses\n\nNew text.","expectedVersion":8}'
```

**Response**

```json
{
  "id": "cmabc123",
  "title": "Use The Index, Luke",
  "version": 9,
  "pageCount": 42,
  "updatedAt": "2026-10-01T09:31:00.000Z",
  "pageNum": 4
}
```

**Errors**

| Status | Message | Code | Fix |
| --- | --- | --- | --- |
| 400 | `pageNum must be a positive integer` | none | Use a whole number of 1 or more. |
| 400 | `content is required` | none | Send non-empty text. |
| 400 | `content would split into {n} pages — pages are split on top-level "# " headings and this operation replaces exactly one page. …` | `INVALID_INPUT` | Keep one `# ` heading. Demote the others to `##`. |
| 404 | `Page {n} not found (document has {count} pages)` | none | Pick a page inside the book. |
| 409 | The version conflict message | `VERSION_CONFLICT` | Same as above. |

**CLI equivalent**: [trove items put](https://docs.heytrove.ai/reference/cli/items-writing.md#trove-items-put) with `ID:PAGE`.

## Append or prepend pages

`POST https://heytrove.ai/api/v1/items/{itemId}/pages`

Adds content to the end of a book, or to the start. Only the new text travels, and it never conflicts with another writer. Start the text with a `# ` heading so it becomes its own page. An append is not saved as a restorable version.

**Parameters**

| Name | In | Type | Required | Constraints | Meaning |
| --- | --- | --- | --- | --- | --- |
| `itemId` | path | string | yes | none | Book to extend. |
| `content` | body | string | yes | not empty, up to 1,000,000 bytes | Text to add. |
| `position` | body | string | no | `start` or `end` | Where to add it. Default `end`. |

**Request**

```bash
curl https://heytrove.ai/api/v1/items/cmabc123/pages \
  -H "Authorization: Bearer trove_your_key" \
  -H "Content-Type: application/json" \
  -d '{"content":"# New chapter\n\nText."}'
```

**Response**

```json
{
  "id": "cmabc123",
  "title": "Use The Index, Luke",
  "version": 10,
  "pageCount": 43,
  "updatedAt": "2026-10-01T09:32:00.000Z"
}
```

**Errors**

| Status | Message | Code | Fix |
| --- | --- | --- | --- |
| 400 | `content is required` | `INVALID_INPUT` | Send non-empty text. |
| 400 | `position must be one of: start, end` | `INVALID_INPUT` | Use `start` or `end`. |
| 413 | `content exceeds maximum size of 1000000 bytes` | `PAYLOAD_TOO_LARGE` | Send it in smaller parts. |
| 413 | `This write would grow the book past its fixed 30MB cap …` | none | The cap is permanent. Split the content into separate books. Do not retry. |

**CLI equivalent**: [trove items append](https://docs.heytrove.ai/reference/cli/items-writing.md#trove-items-append).

## List versions

`GET https://heytrove.ai/api/v1/items/{itemId}/versions`

Lists up to 50 earlier versions, newest first. Only the owner can list them. The call counts as one read.

**Parameters**

| Name | In | Type | Required | Constraints | Meaning |
| --- | --- | --- | --- | --- | --- |
| `itemId` | path | string | yes | none | Book to inspect. |

**Request**

```bash
curl https://heytrove.ai/api/v1/items/cmabc123/versions \
  -H "Authorization: Bearer trove_your_key"
```

**Response**

```json
{
  "id": "cmabc123",
  "title": "Use The Index, Luke",
  "currentVersion": 10,
  "versions": [
    {
      "version": 9,
      "savedAt": "2026-10-01T09:31:00.000Z",
      "bytes": 81234,
      "describes": "The text the book held at version 8."
    }
  ],
  "note": "…"
}
```

Each entry holds the text as it was before the write that produced the next version. The wording of `describes` and `note` comes from the server.

**Errors**

| Status | Message | Code | Fix |
| --- | --- | --- | --- |
| 403 | `Version history is available to the book's owner only. …` | `FORBIDDEN` | Ask the owner. |
| 429 | The read-limit message | `READ_LIMIT_EXCEEDED` | Stop. Tell the user. |

**CLI equivalent**: [trove items history](https://docs.heytrove.ai/reference/cli/items-writing.md#trove-items-history).

## Restore a version

`POST https://heytrove.ai/api/v1/items/{itemId}/versions`

Restores an earlier version. The current text is saved first, so a restore can itself be undone. Restoring version N brings back the text the book held at version N-1.

**Parameters**

| Name | In | Type | Required | Constraints | Meaning |
| --- | --- | --- | --- | --- | --- |
| `itemId` | path | string | yes | none | Book to restore. |
| `version` | body | integer | yes | 1 or more | Version from the list. |

**Request**

```bash
curl https://heytrove.ai/api/v1/items/cmabc123/versions \
  -H "Authorization: Bearer trove_your_key" \
  -H "Content-Type: application/json" \
  -d '{"version":9}'
```

**Response**

```json
{
  "id": "cmabc123",
  "title": "Use The Index, Luke",
  "version": 11,
  "pageCount": 42,
  "updatedAt": "2026-10-01T09:40:00.000Z"
}
```

The response adds `unchanged` and `note` when the version already equals the stored text.

**Errors**

| Status | Message | Code | Fix |
| --- | --- | --- | --- |
| 400 | `version must be a positive integer — list restorable versions with GET /items/:id/versions` | `INVALID_INPUT` | Send a version of 1 or more. |
| 404 | `No snapshot at version {n}. Available versions: {list}` | `NOT_FOUND` | Pick a listed version. |
| 409 | `Version {n} is a single-page rollback point from an append, not a full copy of the book — …` | `CONFLICT` | Pick a version from the list. |

**CLI equivalent**: [trove items restore](https://docs.heytrove.ai/reference/cli/items-writing.md#trove-items-restore).
