Errors
The shape of an error, the few rules that make failures safe to handle, and every error code of API v2 in one place.
Response shape
A success is an object with "success": true plus the endpoint’s own fields. An error is "success": false, a machine code in error, an explanation in message, plus fields specific to that error — the offending value, the allowed ones, candidates to choose from:
{"success":false,"error":"page_not_found","message":"there is no page with the slug \"nope-nope\"","slug":"nope-nope"}
{"success":false,"error":"reserved_dataset_name","message":"the name \"menu.header\" belongs to the platform: the menu.*, site.*, pages.* namespaces and breadcrumbs are reserved for system sources and cannot be overwritten by a dataset","reserved":true}
{
"success": false,
"error": "invalid_slug",
"message": "slug \"site_header\" does not match the naming scheme block__element--modifier: …",
"slug": "site_header",
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?$",
"hint": "block__element--modifier: lowercase latin letters and digits, …"
}
Branch on error, never on message. The text is written for people and changes; the code is the contract.
Errors that come in the WordPress shape
Some failures happen before an endpoint is reached, and those come back in the standard WordPress shape {code, message, data.status}, with no success field. This covers authentication, site choice, the rate limit and unknown routes. Their messages are in Russian — another reason to read the code only:
{"code":"missing_token","message":"Нужен API-токен: заголовок Authorization: Bearer <token> или X-OnPress-Key","data":{"status":401}}
{"code":"invalid_token","message":"Неверный или отозванный API-токен","data":{"status":401}}
{"code":"site_not_found","message":"в сети нет сайта с доменом \"nope.example.com\"","data":{"status":404}}
{"code":"not_found","message":"Маршрут не найден","data":{"status":404}}
A client that handles both shapes reads error when success is false, and code otherwise.
A request either runs completely or not at all
This is a principle, not a property of some endpoints. A request that cannot be done exactly as asked fails before anything is half-done: the page is not created, a parent is not invented, a language is not reassigned, the tree is not rebuilt. After a failed call there is nothing to clean up — fix the body and repeat.
Where the old behaviour was lenient, it is available as an explicit flag: create_missing_parents, link_soft, allow_invalid, "missing": "keep". The untidy result is then what you asked for, not what happened to you.
Endpoints that write a lot have a dry run: "dry_run": true on POST /frames, PUT /site/css, PATCH /site/design, PUT /data/{name}, PUT /docs/sidebar and POST /pages/language. The response shows every change; nothing is written.
Warnings are part of the answer
A successful response carries warnings: sentences about what is off but did not stop the request. Read them. That is where “the page is not published, its address does not serve it”, “template is deprecated”, “data-op-bind is written with double quotes”, “unknown frontmatter key stored as meta” and “an op/frame points at a slug that does not exist” live. An empty warnings is information too.
Common codes
| Code | HTTP | Shape | Meaning |
|---|---|---|---|
missing_token | 401 | WordPress | neither Authorization: Bearer nor X-OnPress-Key was sent |
invalid_token | 401 | WordPress | unknown or rotated-away token |
forbidden_site | 403 | WordPress | the user is not a member of the chosen site |
site_not_found | 404 | WordPress | domain names no site of the network |
not_found | 404 | WordPress | no such route |
too_many_requests | 429 | WordPress | over 120 requests a minute; see Retry-After |
auth_error | 500 | WordPress | the token could not be checked |
forbidden | 403 | v2 | the endpoint needs more than membership: the site’s administrator or a network super admin |
nothing_to_update | 400 | v2 | a PATCH or write with nothing to change |
page_not_found | 404 | v2 | a page reference (id, slug or path) resolved to nothing |
ambiguous_page | 409 | v2 | a slug or path is held by several pages; they are in candidates |
db_error | 500 | v2 | the database refused the write; db_code holds the driver code |
internal_error | 500 | v2 | unhandled service error; details are in the service log |
Pages and markdown
POST /pages/markdown and POST /pages/language. Details in Pages and markdown.
| Code | HTTP | When | Way out |
|---|---|---|---|
absolute_url_in_path | 422 | url has a scheme or a domain | pass a path |
parent_not_found | 422 | a segment of url does not resolve | create_missing_parents: true |
parent_create_failed | 500 | a requested parent stub could not be written | — |
ambiguous_page | 409 | the slug (plus language) matches several pages | pass id or lang, or slug_lookup: false |
lang_mismatch | 409 | id names a page of another language than lang | use translation_of instead of id |
unknown_language | 422 | lang is not a language of the site; the known ones are listed | — |
translation_link_failed | 422 | translation_of does not hold; reason says why | link_soft: true |
translation_group_conflict | 409 | the page already belongs to another translation group | PUT /pages/{ref}/translations |
front_page_group_conflict | 409 | the same, and the page is a language home page | — |
frame_not_found | 404 | frame names no frame of the site | — |
frame_single | 400 | frame lists more than one frame, or uses the old type=ref form | one slug or id |
gone | 410 | frame: default:<type> — removed together with frame types | frame: <slug> |
post_type_not_supported | 501 | frontmatter type is not page; v2 writes pages only | publish posts through v1 |
polylang_inactive | 409 | POST /pages/language on a site without Polylang | — |
reason of translation_link_failed is one of polylang_missing, lang_required, origin_not_found, origin_has_no_language, same_language. When the translation group itself refuses the members, the same code comes with 409 and a rejected list. A body without a markdown string is 400 with the explanation right in error and no separate code.
Translations
GET, PUT, POST, DELETE /pages/{ref}/translations. Details in Translations.
| Code | HTTP | When |
|---|---|---|
bad_translations | 400 | translations is not an object |
page_required | 400 | POST …/translations/{lang} without page |
unknown_language | 422 | no such language on the site |
translations_rejected | 409 | some entries are wrong; rejected lists each with lang, ref, reason |
page_in_other_group | 409 | a listed page belongs to another group with other members |
page_has_no_language | 409 | the page in the URL has no language; fix it with POST /pages/language |
same_language | 409 | {lang} is the language of the page in the URL |
page_language_mismatch | 409 | the page given is in another language; actual_lang is returned |
polylang_inactive | 409 | the site has no Polylang |
Docs sidebar
/docs/sidebar and its item endpoints. Details in Docs sidebar.
| Code | HTTP | When |
|---|---|---|
bad_items | 400 | items is not an array |
bad_missing | 400 | missing is neither hide nor keep |
bad_reorder | 400 | reorder has neither order nor id |
item_ref_required | 400 | the item was named with an empty or unparsable reference |
not_a_page | 404 | the reference resolved to a record of another type |
page_language_mismatch | 404 | the path has one language prefix, the page found is in another |
unknown_language | 422 | lang is not a language of the site |
absolute_url_in_path, parent_not_found | 422 | as in publishing |
duplicate_item | 409 | one page is listed twice in the tree |
not_a_sibling | 409 | order contains a page from another branch |
sidebar_items_unresolved | 409 | the umbrella error of PUT: nothing applied, the breakdown is in errors |
Frames
/frames and /frames/rebake. Details in Frames and How it works.
| Code | HTTP | When |
|---|---|---|
frame_html_required | 400 | no html, or it is not a string |
invalid_slug | 400 | the slug does not follow block__element--modifier; pattern and hint in the answer |
frame_url_not_supported | 400 | url in the body of POST, PUT or PATCH /frames: a frame is a template, the address belongs to the page |
page_required | 400 | assign without page |
nothing_to_rebake | 400 | POST /frames/rebake names nothing |
frame_not_found | 404 | no frame with that id or slug on this site |
slug_taken | 409 | another frame of the site already has that slug |
gone | 410 | /frames/defaults, /frames/{ref}/default, a PATCH with only is_default |
frame_too_large | 413 | the markup is over 512 KB |
A real one:
{"success":false,"error":"gone","message":"default frames by type are gone (frames v3): a page is bound to its frame with POST /frames/{ref}/assign or frontmatter \"frame\", and frames link to each other with <!-- wp:op/frame {\"ref\":\"site__header\"} /-->"}
Data
/data, /data/sources, /data/resolve. Details in Data.
| Code | HTTP | When |
|---|---|---|
dataset_name_required | 400 | no name given |
bad_dataset_name | 400 | the name does not match [a-z0-9][a-z0-9_-]{0,63} |
bad_dataset_items | 400 | items is neither an array nor an object |
bad_patch_op | 400 | unknown operation, or one missing its payload |
item_ref_required | 400 | an item addressed by neither index nor match |
match_token_unsupported | 400 | match uses a token the service cannot compute; supported lists the ones it can |
dataset_not_found | 404 | no dataset with that name |
item_not_found | 404 | no item at that index or matching that filter |
source_not_found | 404 | the name is neither a system source nor a dataset |
reserved_dataset_name | 409 | the name belongs to the platform |
dataset_too_large | 413 | over 512 KB |
Styles
/site/css and /site/design. Details in Styles.
| Code | HTTP | When |
|---|---|---|
css_required | 400 | no css string in the body; to clear, call DELETE |
css_too_large | 413 | over 256 KB; bytes and max_bytes in the answer |
unknown_token | 400 | no such design token; the list is in GET /site/design |
bad_token_value | 400 | the value does not fit the token’s type or range |
{"success":false,"error":"bad_token_value","message":"\"h1.size\": a number is expected, got \"big\"","token":"h1.size"}
Menus
/menus. Details in Menus.
| Code | HTTP | When |
|---|---|---|
menu_name_required | 400 | POST /menus without a name |
menu_ref_required | 400 | no menu reference |
location_required | 400 | an assignment without a location |
unknown_location | 400 | the theme does not register that location |
unknown_language | 400 | no such language on the site |
bad_items, bad_reorder | 400 | malformed items or reorder |
menu_not_found | 404 | no menu with that id, slug or name |
menu_not_assigned | 404 | ~location was used but nothing is assigned there |
item_not_found | 404 | no such item in this menu, or not on that level |
ambiguous_menu | 409 | the name is shared by several menus |
menu_items_unresolved | 409 | some items did not resolve; nothing was written |
menu_items_invalid | 409 | some items point at pages a visitor cannot see; allow_invalid writes anyway |
bad_parent | 409 | an item was made its own parent |
polylang_inactive | 409 | a language was given on a site without Polylang |
menu_assignment_not_stored | 500 | the location assignment did not persist |
Media
/media. Details in Media.
| Code | HTTP | When |
|---|---|---|
source_required, source_ambiguous | 400 | neither or both of url and data |
filename_required | 400 | data without filename |
bad_url | 400 | not http(s), has credentials, or points at a local or private host |
bad_data, bad_field | 400 | data is not base64; alt, title or lang is not a string |
unknown_language | 400 | no such language on the site |
source_url_required | 400 | GET /media without source_url or sha256 |
bad_source_url, bad_sha256, bad_id | 400 | malformed lookup parameter |
media_not_found | 404 | no attachment for that source, hash or id |
file_too_large | 413 | over 15 MB |
unsupported_type | 415 | not JPEG, PNG, WebP, GIF or SVG |
empty_file | 400 | zero bytes |
unsafe_svg, source_not_found, download_failed, sideload_failed | 422 | the file or its source was refused; the reason is in message |
media_failed, media_unavailable | 502, 503 | the PHP side answered something unexpected, or is not deployed |
Sites and settings
/sites, /site/settings, /site/front-page, /site/cache/flush. Details in Sites and settings.
| Code | HTTP | When |
|---|---|---|
forbidden | 403 | creating a site without being a network super admin; settings without being the site’s administrator |
invalid_name, invalid_donor, invalid_keep_frames | 400 | bad input to POST /sites |
donor_not_found | 404 | no such donor site |
site_exists | 409 | the domain is taken; blog_id in the answer, safe to repeat |
purge_failed | 500 | the clone exists but emptying it stopped |
unknown_field, invalid_analytics, invalid_site_icon | 400 | bad input to PATCH /site/settings |
nothing_to_change | 400 | PATCH /site/settings with nothing to set |
invalid_page, invalid_lang | 400 | bad input to PUT /site/front-page |
no_default_translation | 409 | the front page has no translation in the default language |
invalid_transients, nothing_to_flush | 400 | bad or empty POST /site/cache/flush |
internal_unavailable, internal_failed | 502 | the PHP side is not deployed or failed |
Language of messages
Messages of v2 endpoints are English. Errors in the WordPress shape — authentication, site choice, the rate limit, unknown routes — are Russian, and so are a few messages that come from the PHP side, for example media_not_found. None of that matters if you branch on the code.