Skip to main content
Every 4xx and 5xx carries the same JSON body.
cause is the field to branch on. messages is an array because a validator reports every failure at once — collapsing it to the first would make you fix one field per round trip.
messages is empty for everything a flow refused, as opposed to everything the request shape refused. A domain failure explaining itself to a stranger is a domain failure leaking what exists, so a 403 and a 404 say only what their status already says. The cause is there precisely so you do not have to parse prose that is not going to be written.

The table

409 and 412 share a cause on purpose. Both mean “the thing you were writing against has moved”, and the client’s response to either is identical.

402 planLimit is not a rate limit

It is a subscription ceiling — see Plans and limits. From this API it means one thing: creating a Variant past the Theme’s allowance.
Treat it as terminal for that request and surface “upgrade required” rather than queueing a retry.
Enforcement counts, then creates. Two creates arriving at the same instant can leave a Team one over its allowance. That is accepted behaviour, not a bug to report.

409 on a Variant publish

There is one 409 that re-reading will not clear: a Variant can be published only once per Theme version. A Variant’s version number is the Theme version its overrides are addressed against. Publishing a Variant writes a version file numbered with the Theme’s current version; publishing it a second time against the same Theme version finds that file already written and answers 409. To publish a Variant change again, publish the Theme first. See Versioning.

Concurrency in practice

1

Read the token set

The response carries etag. It is null when there is no draft, which is normal.
2

Send it back with the write

3

On 409, re-read

Do not retry the same batch blindly against a moved document. Re-read, decide whether your change still applies, and send it again with the new etag.