Reference
The appendix: status codes, the error-code catalogue, rate-limit specifics, ID prefixes, status enums, and the plan/storage endpoints. Keep this open while you build.
For the precise request/response schema of any endpoint, also consult the auto-generated OpenAPI
spec at {BASE_URL}/swagger/public-v1/swagger.json and the interactive docs at {BASE_URL}/docs
(README §5).
1. HTTP status codes
Section titled “1. HTTP status codes”| Status | Meaning in this API |
|---|---|
200 OK |
Success (reads, and operations that return the updated resource). |
201 Created |
A resource was created (asset upload, embed token). |
202 Accepted |
Async work accepted (execution / batch submitted; ZIP export building). |
204 No Content |
Success with no body (delete, revoke). |
302 Found |
Redirect to a file (ZIP download endpoints — follow it). |
400 Bad Request |
Malformed request: bad ID format, invalid body, validation failure. |
401 Unauthorized |
Missing/invalid/expired bearer token, or bad client credentials at the token endpoint. |
402 Payment Required |
Storage quota exceeded. On an execution submit, code: "insufficient_credits" (the balance cannot cover the run) or code: "credit_cap_reached" (a usage limit of the organization or workspace is reached — see 04 §1.3). Also code: "api_access_disabled" (the plan does not include API access) and code: "subscription_blocked" (the organization’s subscription has ended or is paused: the account is read-only in the Madoo web app and every API call is refused until a member reactivates it there, in Settings → Billing). |
403 Forbidden |
No workspace context, accessing another workspace’s resource, or an operation needing API key auth. Also code: "organization_deactivated" (any change, purchase or run while Madoo has deactivated the organization; reads still work), and code: "EMAIL_NOT_VERIFIED" on a run (or batch) when the e-mail address is not verified yet — for an API key, no owner of the organization has verified theirs: nothing runs until the verification link is opened (an owner opens the one Madoo e-mailed at sign-up; building and reading still work). |
401 Unauthorized with code: "user_deactivated" |
Madoo deactivated the user: every call is refused, sessions and connected apps are revoked. |
404 Not Found |
Resource does not exist (or is not published, for workflows). |
409 Conflict |
Resource state conflict. |
413 Payload Too Large |
File over the upload cap: 250 MB for upload, 100 MB by default for import, the session’s max_size_bytes for presigned upload. |
422 Unprocessable Entity |
Valid request, but the resource state forbids it (e.g. outputs not ready; cancel an already-terminal run). |
429 Too Many Requests |
Rate limit exceeded — back off and retry. |
503 Service Unavailable |
code: "ai_spend_paused" on an execution submit: an AI safety limit was reached, or Madoo has temporarily paused AI generation (for everyone, for your plan, your organization or your workspace), and the run has AI steps. Nothing ran and nothing was charged; the ai_spend member says why and, for a safety limit, when AI resumes (Retry-After too). Retry then, with a new Idempotency-Key. |
500 Internal Server Error |
Unexpected server error. Quote the request_id. |
2. Error response shape (RFC 7807)
Section titled “2. Error response shape (RFC 7807)”Every error body follows RFC 7807 Problem Details:
{ "type": "https://docs.madoo.ai/public-api/errors/workflow-not-found", // URI for the error type "title": "Not Found", // short title for the HTTP status "status": 404, // HTTP status code "detail": "Workflow wf_… not found.", // human-readable, may change "code": "workflow_not_found", // machine-readable — branch on THIS "instance": "/api/v1/executions", // the request path "request_id": "0HMÉ…", // correlation id — quote it to support "errors": [ // present for field validation only { "field": "allowed_origins", "message": "At least one allowed origin is required." } ]}- Branch on
code, never ondetail. typeis a URI built from the code (codewith underscores turned into dashes), underhttps://docs.madoo.ai/public-api/errors/. It identifies the error type; branch oncode.errors[]appears only for field-level validation failures (code: validation_error).request_idis the correlation ID for that request — include it in any support request.- Some errors add a member with their details:
execution_id(a run committed as failed),credit_cap(402 credit_cap_reached, Executions §1.3) andai_spend(503 ai_spend_paused, Executions §1.4).
3. Error-code catalogue
Section titled “3. Error-code catalogue”Codes you will encounter, grouped by area. (Not exhaustive — always read the code field at
runtime.)
Authentication (01)
code |
HTTP | Meaning |
|---|---|---|
invalid_request |
400 | Missing grant_type or malformed Basic header. |
unsupported_grant_type |
400 | grant_type ≠ client_credentials. |
invalid_client |
401 | Bad/missing credentials, or key revoked/expired. |
Workflows (03)
code |
HTTP | Meaning |
|---|---|---|
invalid_id |
400 | Workflow ID not a valid wf_…. |
not_found |
404 | Workflow missing from the workspace. |
invalid_version |
400 | Requested version out of range. |
version_not_available |
404 | That version’s definition is unavailable. |
invalid_status |
400 | status filter not one of draft/published/archived/all. |
Authoring (10) — validation_error (422) carries the structured
errors[]/warnings[] issue lists; the per-issue codes (unknown_node_type, unknown_preset,
unknown_model, cycle_detected, …) are catalogued in 10-authoring.md §2.
code |
HTTP | Meaning |
|---|---|---|
validation_error |
400 / 422 | 400: missing name/definition (field errors). 422: blocking definition issues (errors[]) — also refuses publish of an invalid definition. |
plan_limit_exceeded |
402 | Workflow cap (or custom-interface gate) of your plan reached. |
executions_in_progress |
409 | DELETE refused: non-terminal executions exist. |
invalid_state |
422 | Lifecycle transition not allowed (publish/revert of an archived workflow). |
precondition_failed |
412 | If-Match tag stale: the definition changed since you read it. |
idempotency_conflict |
409 | The same Idempotency-Key was reused with a different request payload. |
missing_required_inputs, invalid_interface_id, invalid_model_parameters, admission_limit_exceeded, invalid_dataset_input, … |
422 | Estimate refused: the inputs/interface you passed, or a runtime value, cannot run as asked. The detail says what to fix. A value a node’s model will not accept that is written in the definition comes back as validation_error with an invalid_model_parameter issue instead. |
Executions (04)
code |
HTTP | Meaning |
|---|---|---|
invalid_request |
400 | Missing/invalid workflow. |
workflow_not_found |
404 | Workflow missing. |
workflow_not_published |
400 | Workflow exists but is not published. |
insufficient_credits |
402 | The balance cannot cover the run’s estimated cost. With an Idempotency-Key, execution_id names the run committed as failed. |
credit_cap_reached |
402 | A daily or monthly usage limit of the organization or the workspace is reached; credit_cap says which, the usage and resets_at_utc. Adding credits does not help (04 §1.3). A run stopped by a limit while running ends failed with error_code CREDIT_CAP_REACHED. |
ai_spend_paused |
503 | An AI safety limit was reached (ai_spend.reason: "safety_limit", with ai_spend.resets_at and Retry-After), or Madoo has temporarily paused AI generation for the platform, your plan, your organization or your workspace (reason: "paused"): a run with AI steps does not start (a run with no AI steps still does). Nothing was charged; retry later with a new Idempotency-Key. A run already in progress whose AI step meets the pause ends failed with error_code AI_SPEND_PAUSED and the unused credits go back; a pause of a single AI provider shows only this way. |
PROVIDER_ACCOUNT_UNAVAILABLE (a step’s error_code) |
— | The AI provider account Madoo set up for your organization is temporarily unusable: the AI step did not run, nothing was charged for it, and it is not retried. Contact Madoo support; retry once they confirm. |
validation_error |
400 | Inputs or supplied execution bounds failed validation (for example a missing required input, malformed bound, or an exact plan already above that bound). |
invalid_id |
400 | Execution ID not a valid run_…. |
not_found |
404 | Execution missing. |
not_completed |
422 | Outputs requested before the run is terminal. |
already_terminal |
422 | Tried to cancel a finished run. |
output_not_found |
404 | Preview: the run has no output with that key (the detail lists its keys). |
output_absent |
422 | Preview: the output was skipped (an optional branch). |
not_previewable |
422 | Preview: the output is text or JSON — read it from /result. |
preview_pending |
202 | Preview: a video or audio preview is being prepared by the worker — call again after Retry-After seconds. |
preview_failed |
500 | Preview: the file could not be previewed; the detail gives the reason. |
output_too_large |
422 | Preview: the file is too large to preview — download it. |
invalid_argument |
400 | Preview: mode, items or max_dimension is invalid. |
busy |
503 | Preview: the page renderer is busy; retry after Retry-After seconds. |
invalid_status |
400 | ZIP requested for a non-completed run. |
Steps that write text with an AI model — a failed step reports these in the execution’s error_code /
error_message and in its trace node’s error; the message says what to change. A failed step is charged only what
the provider billed for it (the tokens of a cut or invalid answer; usually nothing for a provider error).
code |
Meaning |
|---|---|
ai_response_incomplete |
The model stopped at the token limit: the answer was cut short, or none was produced. Raise Max Tokens on the node (with a reasoning model, or lower Reasoning Effort). Same code for every provider. |
invalid_structured_response |
The step asked for a JSON object (response_format: json_object) and the answer was not valid JSON. Describe the object in the prompt — its keys and what each holds — and raise Max Tokens if the answer is long. |
structured_response_not_object |
The answer was JSON, but a list or a single value instead of one object. Ask for one object, for example {"items": [...]}. |
category_not_in_list |
Image Classifier: the model answered a category that is not one of the node’s categories. The step fails instead of picking one: add a category such as other, or say in the prompt what each category covers. |
locate_answer_invalid |
Locate Subject (ai/locate_subject): the model’s answer is not a usable box (not JSON, coordinates missing, outside 0–1000 or in the wrong order). Retried like a provider error; a box is never invented. |
subject_not_found |
Locate Subject with on_not_found: fail: the subject is not in the picture. With continue (default) the step succeeds with found false and no crop. |
locate_subject_missing |
Locate Subject: neither reference pictures nor a description say what to find. |
Validate JSON Schema (utility/json_schema_validate) in fail mode reports this the same way, in the trace too.
code |
Meaning |
|---|---|
json_schema_validation_failed |
The document breaks the schema. The message lists up to five errors, each with where, which rule and why — /colours [maxItems]: Value should have at most 6 items. Fix the producer (an AI step: say the limit in its prompt) or loosen the rule; use mode: route to send failing documents to a review branch instead of stopping. |
json_parse_invalid |
Parse JSON (utility/json_parse): the text is not JSON. The message gives the line and position where it breaks; when the JSON sits inside a longer answer, set find: first. |
json_parse_schema_invalid |
Parse JSON: the text is JSON but breaks the chosen schema; the message lists the errors as above. |
json_parse_text_missing |
Parse JSON: no text, or an empty one, arrived on its text input. |
json_compose_missing |
Compose JSON (utility/json_compose): a {{name}} of the template had no value; the message names the place, e.g. $.keyframes[0].source.uri. |
json_compose_template_invalid, json_compose_template_missing |
Compose JSON: the template is not JSON, or there is none. |
json_compose_schema_invalid |
Compose JSON: the document breaks the chosen schema; the message lists the errors. |
text_expression_invalid |
Text Expression (text/expression): the expression does not compile, uses {% %}, or does arithmetic (compute with utility/calculate). |
text_expression_failed |
Text Expression: writing failed, or crossed a sandbox limit (2 s, 1,000 loop iterations, 100,000 characters). |
text_expression_value_missing |
Text Expression: a variable it reads has no input connected (an optional input left empty is read as empty). |
Steps that assemble text (Text Template, Format as Text, Join Text, Replace Text — 0 credits) report these the same way. A text longer than 64 KB arrives as a stored file and is read like any other text.
code |
Meaning |
|---|---|
format_type_mismatch |
Format as Text received a value of the wrong kind: a text for a number format, or a date not in ISO form (2026-10-03, 2026-10-03T14:30). Dates written for people, such as 03/10/2026, are refused rather than guessed. |
invalid_text_format |
Unknown format, language, currency or date style. |
template_placeholder_not_text |
A Text Template placeholder, or its template, received a file that is not text (an image, a video). |
join_input_not_text, replace_input_not_text |
Join Text or Replace Text received a file that is not text. |
invalid_regex |
Replace Text with use_regex: the pattern is not a valid regular expression. |
regex_timeout |
Replace Text with use_regex: the pattern ran for more than 2 seconds on the text and was stopped. Simplify it (no nested repetitions such as (a+)+). |
Date Calculation (utility/date, 0 credits) reports these the same way.
code |
Meaning |
|---|---|
date_invalid |
No date, a date not in ISO form (03/10/2026 is refused rather than guessed), or a date that does not exist (2026-02-30). Give 2026-11-01, a date and time, or today. |
date_missing_other |
A difference with no other_date. |
invalid_date_operation |
Unknown operation or unit, or an amount that is not a whole number. |
date_out_of_range |
The result falls outside the years 1 to 9999. |
Assets (05)
code |
HTTP | Meaning |
|---|---|---|
invalid_file |
400 | No/empty file. |
invalid_file_type |
400 | Extension not allowed. |
invalid_request |
400 | Missing path. |
forbidden |
403 | Asset in another workspace. |
not_found |
404 | No asset at path. |
file_too_large |
413 | Over the cap (250 MB upload, 100 MB import by default, max_size_bytes presigned). |
quota_exceeded |
402 | Storage quota exceeded. |
unsupported_format |
415 | Structured-data inspection cannot read the selected/detected format. |
duplicate_column |
422 | CSV headers are ambiguous after trim/case-fold. |
ambiguous_table |
422 | JSON exposes multiple arrays and table was not selected. |
invalid_data |
422 | CSV/JSON content is malformed or not a tabular row collection. |
row_limit_exceeded |
422 | Dataset cardinality exceeds the explicit inspection bound. |
source_changed |
409 | Asset ETag changed between validation and read; retry inspection. |
invalid_options |
400 | Bundle validation/checksum policy is unsupported. |
Bundle-level deterministic problems such as ASSET_CHECKSUM_MISMATCH, DUPLICATE_ASSET_ID,
INVALID_RELATIVE_PATH, and ASSET_CONTENT_SIGNATURE_MISMATCH are returned in the successful
bundle-preflight response’s issues[]. A cross-workspace reference remains HTTP 403.
The contract, all authoring surfaces, and the distinction between logical paths and storage references
are documented in the canonical
Bundle manifest authoring guide.
Workflow definitions (10) — issue codes inside a 422 validation_error
(errors[], each with path, node_id and a suggestion), from REST validate/create/update, MCP
validate_workflow and the agent. The full list is in 10 §2;
the ones for nodes that fill a document template (design/template_render, document/pdf,
aggregate/pdf) and for sub-workflows (workflow/sub):
code |
Meaning |
|---|---|
missing_template_id |
The node needs template_id (a tpl_…), or template_revision was given without it. |
invalid_template_id |
template_id is not a tpl_… returned by design-template discovery. |
invalid_template_revision |
template_revision is not a positive number. |
template_not_found |
The template does not exist in this workspace (for design/template_render: is not published). |
template_revision_not_found |
That published revision does not exist. |
template_pin_failed |
design/template_render could not fix the selected revision (message says why). |
internal_template_parameter |
documentId, documentVersionId, templateContract or another internal document field was authored; use template_id / template_revision. |
unknown_port |
A connection names a port the node does not have; on a template node the message lists the template’s field codes. |
missing_workflow |
A workflow/sub node has no workflow: the wf_… ID of the workflow to run. |
invalid_workflow_id |
workflow is not a wf_… returned by GET /api/v1/workflows or search_workflows. |
workflow_not_found |
The workflow named by workflow does not exist in this workspace. |
internal_workflow_parameter |
workflowId, the internal number the editor stores, was authored; use workflow with the wf_… ID. |
subworkflow_interface_unavailable |
The called workflow exists but its inputs and outputs could not be read. |
template_field_not_connected |
document/pdf or aggregate/pdf: a field the template marks required and gives no default value has nothing connected to its input port, so every run would fail. Connect a value to it, or give the field a default (or make it optional) in the template. |
Design templates (11)
code |
HTTP | Meaning |
|---|---|---|
invalid_id |
400 | Template ID not a valid tpl_…, or a page / element ID malformed. |
invalid_sample_set_id |
400 | Sample set ID malformed. |
invalid_status |
400 | status filter not one of published/draft/archived/all. |
invalid_revision |
400 | Revision number not positive. |
missing_idempotency_key |
400 | A draft edit, publish, create or duplicate without Idempotency-Key. |
validation_error |
400 | Command fields invalid (the message names the field). |
not_found |
404 | Template, revision or draft element missing from the workspace. |
page_not_found / master_not_found / sample_set_not_found |
404 | The named page, master page or sample set is not in the draft. |
precondition_required |
428 | If-Match missing: read the draft (outline or content) first. |
precondition_failed |
412 | If-Match stale: the draft changed since you read it. |
idempotency_conflict |
409 | The same Idempotency-Key was reused with a different request. |
archived |
409 | The template is archived: it cannot be edited or returned to draft. |
content_invalid |
422 | A whole document (PUT …/draft/content, create with content) failed validation; errors[] lists every problem (field = JSON path). |
publish_not_ready |
422 | The draft fails a publish-readiness check. |
invalid_argument / design_page_selection_invalid |
400 | Direct render: page_selection or another argument is invalid. |
preview_failed / render_failed |
422 | The exact preview or the direct render could not complete (message says why; a render may return the service’s own code instead of render_failed). |
render_too_large |
413 | Direct render over the page/size bound: use a workflow. |
design_render_busy |
503 | The render pool is saturated; retry after Retry-After. |
Workflow folders (03)
code |
HTTP | Meaning |
|---|---|---|
invalid_id |
400 | Not a fld_… (or root), or a workflow ID not a wf_…. |
not_found |
404 | Folder missing from the workspace. |
conflict |
409 | A folder with that name already exists in the same location (create, rename or move). |
code |
HTTP | Meaning |
|---|---|---|
invalid_request |
400 | Empty items / invalid workflow. |
batch_create_failed |
400 | Batch could not be created. |
invalid_state |
422 | start/cancel not allowed from the current batch state. |
no_failed_items |
422 | Nothing to retry. |
validation_error |
400 | Invalid embed token request (field errors). |
api_key_required |
403 | Embed token creation needs API key auth. |
invalid_jti |
400 | Embed token id not an emb_…. |
Cross-cutting
code |
HTTP | Meaning |
|---|---|---|
too_many_requests |
429 | Rate limit exceeded. |
internal_error |
500 | Unexpected server error. |
4. Rate limiting
Section titled “4. Rate limiting”Madoo rate-limits the Public API to protect the platform. Two policies apply:
| Policy | Applies to | Limit |
|---|---|---|
| Public API | All /api/v1/* endpoints except the token endpoint |
Plan-based sliding window, partitioned per user/organization. Fallback when no plan limit is configured: 60 requests/minute. |
| Auth | POST /api/v1/auth/token |
Sliding window per IP. Production: a small number of requests per 15-minute window (strict, to deter credential guessing). Development: ~100/minute. |
Behaviour:
- Responses to
/api/v1/*include anX-RateLimit-Limitheader. Treat it as an advisory hint, not a precise live budget — the authoritative signal is the 429 response. - Exceeding a limit returns HTTP 429 with a Problem Details body (
code: too_many_requests). - Build a backoff-and-retry into your client (exponential backoff with jitter). For the token endpoint specifically, cache the token rather than minting one per request (01-authentication §4).
5. ID prefixes
Section titled “5. ID prefixes”| Prefix | Resource | Endpoints |
|---|---|---|
wf_ |
Workflow | /api/v1/workflows; workflow of workflow/sub nodes in workflow definitions |
run_ |
Execution | /api/v1/executions (⚠️ the execution ZIP endpoints take the raw GUID — strip run_) |
bat_ |
Batch execution | /api/v1/batch-executions (ZIP endpoints keep the bat_ prefix) |
emb_ |
Embed token or editor embed token (jti) |
/api/v1/embed/tokens, /api/v1/embed/editor/tokens |
tpl_ |
Document template (DesignDocument) | /api/v1/design-templates; template_id of template nodes in workflow definitions |
tplv_ |
Immutable published revision of a template | returned by publish, /versions and contracts |
fld_ |
Workflow folder (root = the top level) |
/api/v1/workflow-folders |
IDs are opaque — pass back exactly what the API returned.
6. Status enums
Section titled “6. Status enums”Execution status (status on an execution; terminal values marked ✅):
pending · running · completed✅ · partial_success✅ · failed✅ · cancelled✅
Batch status (status on a batch):
pending · running · completed · partial_success · failed · cancelled
Batch item status (status on a batch item): mirrors execution states (pending, running,
completed, failed, cancelled).
Design template status (status on a template): draft (never published, or returned to draft) ·
published (selectable; its draft stays editable for the next revision) · archived (hidden from
selection; pinned workflows keep their revision). List filter: published (default), draft,
archived, all.
7. Plan, limits, and usage
Section titled “7. Plan, limits, and usage”GET {BASE_URL}/api/v1/planReports your organization’s plan, its limits, and current usage — useful for showing remaining credits or guarding against limits before submitting work.
{ "plan_code": "growth", "plan_name": "Growth", "limits": { "max_credits_per_month": 5000, "max_workflows": 100, "max_users": 25, "max_workspaces": 10, "max_storage_bytes": 53687091200, "max_concurrent_executions": 20, "output_retention_days": 90, "api_access": "enabled", "embed_access": "enabled" }, "usage": { "current_workflows": 12, "current_users": 4, "current_storage_bytes": 1048576000, "credits_used_this_month": 1840 }}
output_retention_daysmatters for integrators: generated outputs (and their download URLs) are retained for this many days. Download and persist anything you need to keep beyond that window.
8. Storage usage and quota
Section titled “8. Storage usage and quota”GET {BASE_URL}/api/v1/storage/usage{ "total_bytes": 1048576000, "file_count": 1342, "breakdown": [ { "category": "uploads", "total_bytes": 524288000, "file_count": 420 }, { "category": "outputs", "total_bytes": 524288000, "file_count": 922 } ]}GET {BASE_URL}/api/v1/storage/quota{ "current_bytes": 1048576000, "max_bytes": 53687091200, // null if no quota configured "usage_percent": 1.95, "is_warning": false, // approaching the limit "is_blocked": false // at/over the limit — uploads may be refused (402)}When is_blocked is true, uploads can be rejected with 402 (quota_exceeded). Watch
is_warning to clean up or upgrade before you hit the wall.
9. Pagination recap
Section titled “9. Pagination recap”The paged lists (workflows, executions, assets, batches and the catalog) share the same envelope and cursor mechanics:
{ "data": [ … ], "has_more": true, "next_cursor": "…", "total_count": 137 }limit1–100 (default 25; batch items default 50).- Pass
next_cursorasstarting_afterfor the next page. - Stop when
has_moreisfalse. - Catalog endpoints (node types, models, presets, effects, capabilities) return the full
catalog in one response when
limitis omitted — paging there is opt-in (09-catalog.md). - Short workspace lists come back whole, without the envelope (no
has_more,next_cursorortotal_count): design templates{ "data": [ … ] }, a template’s revisions{ "template_id": "tpl_…", "data": [ … ] }, fonts{ "fonts": [ … ] }, workflow folders{ "folders": [ … ] }. Read the array; there is no next page.
10. Machine discovery (/.well-known/)
Section titled “10. Machine discovery (/.well-known/)”The API describes itself to agents and tooling through standard discovery documents
(RFC 8615). All of them are anonymous (no token needed — discovery happens before you
hold a credential) and cacheable (Cache-Control: public, max-age=3600):
| Document | URL | Format |
|---|---|---|
| API catalog | GET /.well-known/api-catalog |
RFC 9727 linkset (application/linkset+json): service-desc → the OpenAPI document at /swagger/public-v1/swagger.json, service-doc → the interactive docs at /docs. |
| Agent-skills index | GET /.well-known/agent-skills/index.json |
Cloudflare Agent Skills Discovery RFC v0.2.0: the published skills with sha256:{hex} content digests. |
madoo-workflows skill |
GET /.well-known/agent-skills/madoo-workflows/SKILL.md |
text/markdown — agent-oriented instructions for the full lifecycle (auth → catalog discovery → authoring → publish → execute → outputs), condensed from these docs. Verify the bytes against the index digest. |
An agent runtime that supports skill discovery can point at the host and pick up the
madoo-workflows skill with zero configuration; everything in it links back to the chapters
in this guide for depth.
That’s the whole Public API v1. If something here disagrees with the live
{BASE_URL}/swagger/public-v1/swagger.json, trust the spec (it is generated from the running code)
and let us know so we can update this guide.