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

# Update & Delete Add-ons

Correct an add-on's content, publish or unpublish it, or remove it.

## Update

```
PUT /api/v3/content/{content}/add-ons/{id}
```

Updates are **partial**: only the fields present in the payload are validated and written. Updating a title never blanks the type, position or any other field you did not send.

:::note

The verb is `PUT`, but the semantics are a merge, not a replacement: fields absent from the payload are left untouched rather than cleared. If you read this as strict REST, `PATCH` is the behaviour you should expect.

:::

### Request Body

Any subset of the [creation fields](./add-ons-v3-create.md#request-body):

| Field                                            | Notes                                                                                                                                                                                      |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title`, `body`, `type`                          | Same rules as creation; when present they cannot be null. `body` is sanitized on save                                                                                                      |
| `status`                                         | `draft` or `published`. Publish a draft or pull a published add-on back to draft                                                                                                           |
| `start_page`, `end_page`                         | `end_page` is validated against the **stored** `start_page` when only one of the two is sent                                                                                               |
| `location_in_start_page`                         | Same `{x, y}` format and validation as creation; send `null` to remove the marker                                                                                                          |
| `anchor`                                         | Re-anchors the piece. Complete when present: the type and page always, the coordinates of a `point`. Not accepted together with `start_page` or `location_in_start_page`, and not nullable |
| `image_from_url`                                 | Downloads, validates and stores a new image, replacing `image_url`. Cannot be null                                                                                                         |
| `pretitle`, `author`, `by_line`, `image_caption` | Same rules as creation; send `null` to clear one                                                                                                                                           |
| `source`, `body_format`                          | **Not accepted**. Both are set by the system. Sending either returns `422`                                                                                                                 |

Clearing a field and omitting it are different: an omitted key is left as it is, an explicit `null` writes null. Only the fields marked nullable above accept it. `title`, `body`, `type`, `status`, `start_page`, `end_page` and `anchor` are required columns or positions, so `null` on any of those returns `422`.

There is no way to remove an add-on's image through this API. `image_url` is a response field and is rejected if you send it, and `image_from_url` only accepts a URL, so a `null` there returns `422` rather than answering `200` to a clear it would not perform. Replace the image by sending a new `image_from_url`. To remove one entirely, use the dashboard editor.

### Re-anchoring

Sending an `anchor` replaces the position whole, and the page range follows it: `end_page` collapses onto the anchor's page unless the same payload names another end. A piece is never left spanning the pages it used to sit on.

```bash
curl -X PUT "https://yourstore.publica.la/api/v3/content/456/add-ons/1044" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{ "anchor": { "type": "page", "page": 22 } }'
```

An anchor that travels must be complete: `{ "anchor": { "type": "page" } }` is refused under `errors.anchor.page` rather than storing a position with no page. An explicit `"anchor": null` is refused too. Removing a position is not part of the contract, re-anchoring is.

A piece the reader anchored to a passage or a figure keeps that anchor when an update moves only its page: the page changes, the selection stays. Moving such a piece through `start_page` on a fixed-layout EPUB drops the locator of the page it left, which is why the anchor is the way to move it.

Moving a position (`start_page`, `end_page` or `anchor`) needs the product's page count, so an update that carries any of them answers `422` while a PDF is still converting, or on an EPUB the platform has not numbered. Every other field stays editable meanwhile. On a product that takes no add-ons at all (an audiobook holding a record created before the format rule) the same update answers `422` with `Add-ons are only supported on PDF and EPUB publications.`

### Example: publish a draft

```bash
curl -X PUT "https://yourstore.publica.la/api/v3/content/123/add-ons/1044" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{ "status": "published" }'
```

`200 OK` with the updated [add-on object](./add-ons-v3-overview.md#the-add-on-object) under `data`. When the payload carried a `body`, the response also carries `sanitization.discarded` with what the [sanitizer](./add-ons-v3-overview.md#rich-text-bodies) removed; a body that sanitizes to nothing is refused with `422` under `errors.body` and the stored body is kept.

---

## Delete

```
DELETE /api/v3/content/{content}/add-ons/{id}
```

Deletes the add-on and its stored image. Readers stop seeing it immediately.

```bash
curl -X DELETE "https://yourstore.publica.la/api/v3/content/123/add-ons/1044" \
  -H "X-User-Token: your-api-token"
```

`204 No Content` on success.

---

## Errors

| Status | Condition                                                                                                     |
| ------ | ------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing or invalid `X-User-Token`                                                                             |
| `403`  | Add-ons feature not enabled, non-administrator token, or the product is not owned by your store               |
| `404`  | Product or add-on not found, or the add-on belongs to a different product than the URL claims                 |
| `422`  | Validation failure (see [creation errors](./add-ons-v3-create.md#errors)), or `source`/`body_format` provided |
| `429`  | Rate limit exceeded (20 requests per minute, shared with create)                                              |
