Authentication
Send an API key as a bearer token. The key must carry the links.edit permission; a key without it is refused with 403, not 404.
This endpoint takes no organisation id. Your key already identifies the organisation it belongs to, and the response is scoped to it.
Try it
Replace anything in angle brackets with your own values, and the key placeholder with a key from your dashboard.
curl -X POST https://api.zinndigital.com/v1/content/runs \
-H "Authorization: Bearer zdk_live_…" \
-H "Content-Type: application/json" \
-d '{ "brief": <string>, "site_ids": <string[]> }'Signed in? The API console in your dashboard fills in your real organisation id and your own key, and runs the request against the live API so you can see the actual response. Open this endpoint in the API console
Details
Writes one article from the brief and places it on every site named, eight at a time. Answers 201 immediately with the run row — the work itself happens on the durable queue, and the run screen shows it progressing. Gated on links.edit, the same key as scheduling a post by hand, because that is what this creates. ⛔ publish_policy defaults to draft_only, so a request that omits it produces scheduled drafts and puts nothing live. unattended is the only value that publishes without a human, and it is the mode the durability guarantees are written for. ⛔ Every site id must belong to this organisation. A run naming a site that does not is refused whole with 422 rather than quietly posting to the rest — "it only posted to some of them" is not a state a customer can act on. Sites that cannot receive a post (suspended, a non-WordPress stack, transferred away) are recorded as skipped with a reason and do not stop the run.
Request body
| Name | Type | Required | What it is |
|---|---|---|---|
brief | string | Yes | — |
site_ids | string[] | Yes | — |
anchors | ContentRunAnchor[] | No | — |
category | string | No | — |
publish_policy | ContentRunPublishPolicy | No | — |
recipe_id | string | No | — |
Response
| Name | Type | Required | What it is |
|---|---|---|---|
id | string | Yes | — |
state | ContentRunState | Yes | Where a run got to. partial is a real, honest, terminal answer and not a euphemism: some sites published and some did not, nothing is left to try, and the failures can be… |
publish_policy | ContentRunPublishPolicy | Yes | How far a run may go on its own. draft_only is the default and puts nothing live. |
recipe_id | string | No | — |
brief | string | Yes | — |
category | string | No | — |
post_id | string | No | The article this run wrote. Written once and reused for every site, so a resumed run places the SAME copy rather than paying for a second generation. |
attempt | integer | Yes | Which pass over the failures this is. 0 is the original run. |
workflow_id | string | No | — |
totals | ContentRunTotals | Yes | — |
reason | ContentRunFailureReason | No | — |
provider | string | No | — |
message | string | No | An English sentence for the customer. Clients should render their own localised text from reason and fall back to this only for a code they do not know. |
resumable | boolean | Yes | Whether a resume would actually do anything. False on a paused run with nothing failed — do not offer the button when this is false. |
started_at | string | No | — |
finished_at | string | No | — |
created_at | string | Yes | — |
items | ContentRunItem[] | Yes | — |
Errors this endpoint can return
401 · 403 · 422 · 503