# update_chapter

Edit a chapter (name, description, prose, cover).

`PATCH /v1/chapters/{collectionId}` · MCP tool `update_chapter` · scope `write` · stable · since 2026-09-17

body is a full replacement of the prose paragraphs ([] clears). coverStorageId comes from a photo upload with target=blob; clearCover removes it.

## Path parameters

- `collectionId` (string, required): Id of the chapter or collection (from list_chapters).

## Body parameters

- `body` (array of strings, optional): Ordered prose paragraphs, chapters only, full replacement ([] clears): at most 100, each at most 10,000 characters; empty paragraphs are dropped.
- `clearCover` (boolean, optional): true removes the cover image.
- `coverStorageId` (string, optional): Storage id returned by the upload endpoint with target=blob.
- `description` (string, optional): One-line summary shown on the chapter card. Empty string clears.
- `expectedUpdatedAt` (number, optional): Optional staleness guard: the chapter's updatedAt you last read (list_chapters). The write is refused with stale_editor when the chapter changed since.
- `kind` (string, optional): chapter or collection. Turning a chapter into a collection clears its body and its front matter (intro), intro photos included. One of `chapter`, `collection`.
- `name` (string, optional): Chapter title, e.g. Yellowstone or Bernese Oberland. Cannot be empty.
- `slug` (string, optional): Optional URL slug, unique in the guide: 1 to 63 lowercase letters, digits or hyphens, a letter or digit first (stored lower case). Empty string clears.

## Returns

200.

- `ok` (boolean, required): Always true: the write went through. Always `true`.
- `collectionId` (string, required): The chapter's or collection's id.

## Examples

### Set the cover from an upload with target=blob

```bash
curl -X PATCH "https://sceniq.earth/api/v1/chapters/kn7e5r9t3y7v1j5p9p3a7s1d5f9g3h7j" \
  -H "Authorization: Bearer $SCENIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "coverStorageId": "kg2a8c4e0g6j2k8m4p0q6s2v8w4y0a6c"
}'
```

MCP `tools/call`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_chapter",
    "arguments": {
      "collectionId": "kn7e5r9t3y7v1j5p9p3a7s1d5f9g3h7j",
      "coverStorageId": "kg2a8c4e0g6j2k8m4p0q6s2v8w4y0a6c"
    }
  }
}
```

Response 200:

```json
{
  "ok": true,
  "collectionId": "kn7e5r9t3y7v1j5p9p3a7s1d5f9g3h7j"
}
```

### Replace the prose, refused if someone else saved in between

```bash
curl -X PATCH "https://sceniq.earth/api/v1/chapters/kn73r7x6mp88mxa9swcbshwd3e7zvr39" \
  -H "Authorization: Bearer $SCENIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": [
    "Tunnel View, Glacier Point and the trails off Glacier Point Road show the valley from three heights. Glacier Point Road closes to cars in winter, so from November to late May this chapter shrinks to the valley floor.",
    "Sunset belongs to Tunnel View and Taft Point; Glacier Point works from late afternoon into the blue hour.",
    "In spring the waterfalls run full and Tunnel View gets Bridalveil Fall at its best; by September it can be a trickle."
  ],
  "expectedUpdatedAt": 1790586840000
}'
```

MCP `tools/call`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_chapter",
    "arguments": {
      "collectionId": "kn73r7x6mp88mxa9swcbshwd3e7zvr39",
      "body": [
        "Tunnel View, Glacier Point and the trails off Glacier Point Road show the valley from three heights. Glacier Point Road closes to cars in winter, so from November to late May this chapter shrinks to the valley floor.",
        "Sunset belongs to Tunnel View and Taft Point; Glacier Point works from late afternoon into the blue hour.",
        "In spring the waterfalls run full and Tunnel View gets Bridalveil Fall at its best; by September it can be a trickle."
      ],
      "expectedUpdatedAt": 1790586840000
    }
  }
}
```

Response 200:

```json
{
  "ok": true,
  "collectionId": "kn73r7x6mp88mxa9swcbshwd3e7zvr39"
}
```

## Errors

- `invalid_argument` (400): A required field is missing, a field is unknown, or a value does not match its type (the Convex validator's path and value are in the message).
- `invalid_request` (400): A writer refused a value: a map link on a host that is not allowed, a title that is too long, a date that does not exist, too many items in a list. The message is the writer's own sentence.
- `stale_editor` (409): The row changed after the expectedUpdatedAt you sent (update_chapter, save_sales_page_draft).
- `unauthenticated` (401): The request carries no key. Send it as Authorization: Bearer sk_sceniq_... (or X-Api-Key).
- `invalid_api_key` (401): The key is unknown, revoked or expired.
- `api_scope_required` (403): A read-only key called a write operation, or a key without the publish option called publish_changes.
- `forbidden` (403): The key belongs to a creator account that is no longer active.
- `not_found` (404): The id does not exist or belongs to another creator. The two answers are identical on purpose, so ids never reveal what exists.
- `payload_too_large` (413): A JSON body over 2 MB, an image over 6 MB, a batch upload over 19 MB, or an MCP message over 2 MB.
- `rate_limited` (429): Over a limit: requests per key per minute, per creator per hour, uploads per key per minute, or check_links per guide per hour.
- `internal_error` (500): An unexpected failure. The message is hidden on purpose.

Reference: https://developers.sceniq.earth/reference/update_chapter
