Bulk Create Add-ons
Create up to 100 add-ons on one publication in a single request. Bulk items are pure database writes: attach images through the single add-on endpoints.
Endpoint
POST /api/v3/content/{content}/add-ons/bulk
{content} is the publication's internal ID, or any of its identifiers with id_type=external. The publication must be a PDF or an EPUB, and numbered (422 otherwise, with the reasons listed in the overview).
Each item names its position the same way a single create does: start_page (plus an optional location_in_start_page) on a PDF, or an anchor on either format. On an EPUB an item without an anchor fails on its own, with An add-on on an EPUB publication must carry an anchor. under its index. The rest of the batch is still written.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
add_ons | array | Yes | 1 to 100 add-on objects, same fields as single creation except image_from_url, which bulk items reject |
Limits
| Limit | Value |
|---|---|
| Maximum add-ons per request | 100 |
| Minimum add-ons per request | 1 |
| Requests per minute | 10 |
Per-item validation
Each item is validated individually: an invalid item is reported in errors with its index and never aborts the rest of the batch.
There is one exception, and it is about shape rather than field values: if any entry of add_ons is not a JSON object, the whole request is rejected with 422 before any item is processed and nothing is created. add_ons itself must also be a JSON array. An object keyed by strings is rejected the same way, because the index correlation depends on it. Once every entry is a well-formed object, an invalid field inside one only ever affects that one.
Request Example
curl -X POST "https://yourstore.publica.la/api/v3/content/123/add-ons/bulk" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-d '{
"add_ons": [
{ "title": "Front page piece", "body": "<p>…</p>", "start_page": 1 },
{ "title": "Center spread", "body": "<p>…</p>", "start_page": 8, "status": "published" },
{ "title": "Back page piece", "body": "<p>…</p>", "start_page": 32 },
{ "title": "Piece in an EPUB", "body": "<p>…</p>", "anchor": { "type": "page", "page": 14 } }
]
}'
Response
200 OK with a per-batch summary:
{
"data": {
"total": 3,
"created": 2,
"failed": 1,
"add_ons": [
{
"index": 0,
"id": "1044",
"title": "Front page piece",
"status": "draft",
"discarded": []
},
{
"index": 1,
"id": "1045",
"title": "Center spread",
"status": "published",
"discarded": [
{ "node": "h2", "reason": "tag_not_allowed", "detail": null }
]
}
],
"errors": [
{
"index": 2,
"errors": {
"start_page": ["The start page field must be at least 1."]
}
}
]
}
}
add_onscarries one entry per created piece, each with the zero-basedindexof the item that produced it. Match onindexrather than on position: it is the only correlation between a returnedidand your own record.errorscarries one entry per failed item, with the same zero-basedindex. The key is omitted when every item succeeds.discardedon each created item lists what the body sanitizer removed from that item'sbody. An item whose body sanitizes to nothing fails on its own: it joinserrorsunderbody, with itsdiscardedlist, and its neighbours are still created.
Batches are not atomic
A 500 means an unexpected failure (storage, database), not a rejected item. Items processed before it stay created, and the response carries no summary telling you which ones. Retrying the same batch re-creates them, because the endpoint has no idempotency key yet. After a 500, list the publication's add-ons and reconcile before retrying.
Errors
| Status | Condition |
|---|---|
401 | Missing or invalid X-User-Token |
403 | Add-ons feature not enabled, non-administrator token, or the publication is not owned by your store |
404 | Publication not found |
422 | add_ons missing, empty or over 100 items; the publication takes no add-ons, or is not numbered yet |
500 | Unexpected failure. Earlier items stay created (see above) |
429 | Rate limit exceeded (10 bulk requests per minute) |