Studio API: errors and validation
How the managed Studio API service reports failures, the full status + error_code catalog, and the Studio rules it enforces on every write. For the routes themselves, see the Endpoint reference.
The error envelope
Every service-level error uses one shape:
{
"error_message": "<human-readable summary>",
"error_code": 32, // stable numeric code (table below)
"errors": { // present when there are field-level issues
"<path>": ["<message>", …] // keyed by the offending field / node path
}
}Two things make this actionable:
- error_code is stable: branch on it, not on the message text.
- errors is keyed by path: for a bad layout, each failing node path appears with its own message, and all failures come back at once (you don't fix-and-resubmit one at a time).
400s are the exception. DTO-shape failures (missing title, a non-object ui, a bad enum) are caught by the framework's request validator before the handler, so they use the framework's default shape ({ statusCode, message: [...], error: "Bad Request" }), not the envelope above. Everything else uses the envelope.
Status + error_code catalog
| HTTP | error_code | When | errors payload |
|---|---|---|---|
| 400 | - (request validator) | DTO shape/type/enum failure (missing title, bad place_composition_as, ui not an object, limit out of range) | framework default shape |
| 401 | 39 composition_missing_auth_token | Neither a session authtoken nor an OAuth Authorization: Bearer token reached the service. Not a rejection of OAuth, both styles are accepted | - |
| 404 | 1 project_not_found | Project not found (access-check fetch) | { "uid": ["is not valid."] } |
| 404 | 31 composition_project_not_found | Project vanished between checks (rare race) | { "projectUid": ["is not valid."] } |
| 404 | 30 composition_not_found | Entry uid not found | { "uid": ["is not valid."] } |
| 404 | 44 composition_flavor_mismatch | …/{uid}/template on a section (or /section on a template) | { "uid": ["This composition is a \"section\", not a \"page\"."] } |
| 422 | 21 stack_not_found | Access denied: no read/write on the project's stack (Studio convention, not 403) | - |
| 422 | 32 composition_invalid | Structural validation failed | { "<node path>": ["<rule message>", …] }, one entry per failing path, all at once |
| 422 | 32 composition_invalid | Provided composable_uid was blank/whitespace-only | { "composable_uid": ["composable_uid must not be blank when provided."] } |
| 422 | 40 composition_uid_immutable | PUT tried to change composable_uid | { "composable_uid": ["composable_uid is immutable and cannot be changed."] } |
| 422 | 37 composition_decompression_failed | GET …?decompression=true but the stored ui is corrupt | { "ui": ["is not valid."] } |
| 409 | 33 composition_uid_conflict | composable_uid already used, checked content-type-wide (all locales). errors is keyed by the field the CMA flagged (usually composable_uid). | { "composable_uid": ["is not unique."] } |
| 409 | 41 composition_referenced | DELETE without force while other compositions reference this one | { "references": ["Referenced by N composition(s): …"] } |
| 500 | 36 composition_compression_failed | ui round-trip integrity check failed before persist | - |
| 401 / 403 / 409 / 422 / 429 / 502 | 38 composition_cma_error | Contentstack CMA failure: any upstream 4xx status is preserved. Only genuine 5xx/gateway failures become a 502. | upstream message in error_message |
Two codes worth internalizing. 422 stack_not_found means access denied, not "missing". Check the caller's rights on the project's stack. And a 409 on create almost always means a duplicate composable_uid somewhere in the content type (including another locale), caught before the write.
Codes on the other route groups
The table above covers the composition routes. The rest of the service uses the same envelope with its own codes:
| HTTP | error_code | Where | When |
|---|---|---|---|
| 404 | 1 project_not_found | Projects | The uid isn't a live project in your organization (also returned when you lack access, rather than confirming it exists) |
| 422 | 21 stack_not_found | Projects | Access denied on the project's connected stack, on create, update, and delete alike |
| 422 | 2 project_create_failed | Projects | An authorization-SDK failure during create |
| 422 | 3 project_update_failed | Projects | An authorization-SDK failure during update |
| 422 | 4 project_delete_failed | Projects | An authorization-SDK failure during delete |
| 401 | 48 registered_components_missing_auth_token | Registered components | Neither credential reached the service. The counterpart to 39 on the composition routes |
| 422 | 45 registered_components_fetch_failed | Registered components | An authorization-SDK failure during the lookup |
| 502 or upstream status | 46 registered_components_upstream_error | Registered components | The component service was unreachable, failed, or returned an unexpected body. A client error such as 403 passes through with its own status |
Why so many 422s. The envelope defaults to 422 whenever a code is raised without an explicit status. That's why access denial, project write failures, and validation failures all land on the same status. The error_code is what distinguishes them, which is the reason to branch on it rather than on the status.
What the service validates
On every write (create, and any update that includes ui), the service checks the layout against Studio's structural rules and returns results in two tiers:
| Tier | Effect | Where it shows |
|---|---|---|
| Errors | Block the write | 422 composition_invalid, keyed by path |
| Warnings | Don't block | warnings[] on the 201 / 200 success body |
The checks are crash-guards, the shapes that would break the canvas or the renderer if they slipped through. Representative rules:
- Root must be page: for both templates and sections. section is a nested-container type, never the tree root.
- No empty slots: an empty slots array crashes the renderer. Populate it or drop the key.
- Repeater / Condition Block shape: a Repeater needs its iteration metadata. A Condition Block needs its cases. (See Smart containers.)
- Placement consistency: a section can't carry a url / connected_content_type. Those are stripped or rejected per flavor.
- Section placement warning: a tree that places section-composition nodes warns you to keep linked_sections in sync, or the editor shows "Template Did Not Load".
Because the results are collected, a single POST tells you every structural problem at once. Fix them together and resubmit.
Why these and not more? The service validates the structural problems it can catch without loading your components or content-type schema, the ones that hard-break rendering. Deeper semantic checks (URL-variable correctness, binding-context matching) depend on the connected content type and are enforced by the canvas/runtime, not this pre-check. The layout-tree vocabulary these rules police is documented once in Building Blocks.
Handling errors well
- Branch on error_code, not error_message: messages are translated and may change. Codes are stable.
- On 422 composition_invalid, read every key in errors: they're all there. Don't fix one and resubmit blind.
- Treat 422 stack_not_found as an auth problem, not a missing resource.
- On 409 at create, pick a different composable_uid (or omit it to let the service backfill a unique one).
- On 409 composition_referenced at delete, call GET …/{uid}/references to see who depends on it before deciding whether to ?force=true.
- Retry 502 (transient gateway). A preserved 401/403 is not retryable. Fix the credential/permission.
See also: Endpoint reference, Compositions, Projects, Chapter overview, Building Blocks.