Skip to content

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).


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.

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 on detail.
  • type is a URI built from the code (code with underscores turned into dashes), under https://docs.madoo.ai/public-api/errors/. It identifies the error type; branch on code.
  • errors[] appears only for field-level validation failures (code: validation_error).
  • request_id is 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) and ai_spend (503 ai_spend_paused, Executions §1.4).

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).

Batch / Embed (06, 07)

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.

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 an X-RateLimit-Limit header. 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).

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.


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.


GET {BASE_URL}/api/v1/plan

Reports 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_days matters 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.


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.


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 }
  • limit 1–100 (default 25; batch items default 50).
  • Pass next_cursor as starting_after for the next page.
  • Stop when has_more is false.
  • Catalog endpoints (node types, models, presets, effects, capabilities) return the full catalog in one response when limit is omitted — paging there is opt-in (09-catalog.md).
  • Short workspace lists come back whole, without the envelope (no has_more, next_cursor or total_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.

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.