# Madoo - integration guide for this Lovable project

This app uses **Madoo** (AI workflows for images, video, audio, text, documents). Two jobs, never mixed:

1. **Build time - you (the agent) author the Madoo workflow**, from your own work area, never from the app.
2. **Run time - the app runs the published workflow** through Madoo's **REST API v1**, only from
   **server-side code** (a server function of the app, or a Lovable Cloud function) - never from the browser.

Credentials: the project Secrets `MADOO_BASE_URL`, `MADOO_CLIENT_ID`, `MADOO_CLIENT_SECRET` (a Madoo API key).
Docs written for agents: `https://testing-docs.madoo.ai/llms.txt` (every page is also `<page URL>.md`); the workflow
authoring guide is `https://testing-docs.madoo.ai/public-api/authoring.md`. OpenAPI:
`{MADOO_BASE_URL}/.well-known/api-catalog`. If this guide and the OpenAPI disagree, follow the OpenAPI.

## 0. Conventions
- IDs are opaque strings: workflows `wf_...`, runs `run_...`. Never parse or invent them.
- Every call: `Authorization: Bearer <token>`. Token: `POST {MADOO_BASE_URL}/api/v1/auth/token`, HTTP Basic
  `MADOO_CLIENT_ID:MADOO_CLIENT_SECRET`, form body `grant_type=client_credentials` -> `{ access_token,
  expires_in: 3600 }`. **Cache it** until ~1 minute before it expires: the token endpoint is rate-limited.
- Input `value`s are **JSON-encoded strings**: text `JSON.stringify("hello")` -> `"\"hello\""`, number `"5"`,
  boolean `"true"`. File inputs use `asset_path` instead of `value`.

## 1. Build time - author the workflow (from your work area, with the Secrets)
Use the REST authoring API (or, if a Madoo MCP connector is connected, its tools of the same names:
`search_node_types`, `get_node_type`, `create_workflow_draft`, `validate_workflow`, `publish_workflow`,
`get_workflow`). Never put authoring code in the app.

With the MCP connector, three differences:
- `publish_workflow` and `update_workflow` need `if_match` = the `etag` returned by create/update/`get_workflow`.
- A check run with `execute_workflow` needs a fresh UUID `idempotency_key`.
- The contract comes from `get_workflow`, where the interface is `primary.interface_id`; in the app's REST run
  body the same value goes in the `"interface"` field.

1. Find nodes: `GET /api/v1/node-types?search=<english words>`, then `GET /api/v1/node-types/{category}/{name}`
   for exact inputs, outputs and parameters (e.g. `/api/v1/node-types/ai/lifestyle`).
2. Create a draft: `POST /api/v1/workflows` `{ "name", "description", "definition": { "schema_version": "1.0",
   "nodes": [...], "connections": [...] } }` with header `Idempotency-Key: <uuid>`. Blocking problems come back
   as 422 with `errors[]` (each with `path` and `suggestion`): fix and resend.
3. Check: `POST /api/v1/workflows/{id}/validate` until `valid: true`; `POST /api/v1/workflows/{id}/estimate`
   tells the credits per run.
4. Publish: `POST /api/v1/workflows/{id}/publish`.
5. Read the contract: `GET /api/v1/workflows/{id}` -> `execution_contract`: `primary.inputs[]` (key, type,
   required), `primary.interface` (usually absent), `outputs[]` (each `key` + `read_from`). The app uses exactly
   these keys and paths.

Rules:
- **Never guess** node types, port names or parameter values: read them from the node type.
- **Leave the AI model out** unless the user asks for one: each node has a tested default model.
- **Name things for the app.** An input node's label becomes the run input key (`Product Photo` ->
  `product_photo`); an output node's `output_name` becomes the output key. One output node per value the app
  shows (e.g. `headline`, `body`, `cta` as three `output/text`), or one `output/json` for an object.
- **Changing a published workflow:** `GET /api/v1/workflows/{id}/definition` (keep its `ETag`), then
  `PUT /api/v1/workflows/{id}/definition` with `If-Match: <ETag>`: a new version the app uses **at once**. If
  inputs or outputs changed, update the app's server code in the same step.
- **Check run (optional):** one run with small inputs as in section 2. It **spends credits**: never in a loop.

## 2. Run time - REST v1 from server-side code
- Secrets only on the server; never in browser code or `VITE_...` variables.
- **Workflow ids are config, not secrets**: keep the `wf_...` id(s) in the server code (a constant, or a map
  action -> `wf_...`). The browser sends an action name, never a `wf_...` id.
- **Never wait for a run inside a server call** (a generation can take minutes). One call starts the run and
  returns the `run_...` id; the page then polls a second call.

### 2.1 Flow
1. **Files** (only for `image`/`video`/`audio`/`document`... inputs) - `POST /api/v1/assets`,
   `multipart/form-data`, field `file` -> `{ path }` (max 250 MB). A file on a public URL:
   `POST /api/v1/assets/import` with `{ "url": "..." }`. Use `path` as `asset_path`.
2. **Start** - `POST /api/v1/executions`, `Content-Type: application/json`, header
   **`Idempotency-Key: <new UUID per user click>`** (a retry with the same key returns the same run, never a
   second charge):
   ```json
   { "workflow": "wf_...",
     "inputs": { "<text key>": { "value": "\"the user's text\"" },
                 "<file key>": { "asset_path": "<path from step 1>" } } }
   ```
   Keys = `execution_contract.primary.inputs[].key`. Omit optional inputs the user left empty. Add
   `"interface": "<id>"` only if `primary.interface` is set. -> **202** `{ "id": "run_...", "status" }`.
3. **Poll** (every 3-5 s, or `next_poll_after_ms`) - `GET /api/v1/executions/{run_id}/result`. It answers 200
   while running; stop when `is_terminal` is `true`. Progress bar: `GET /api/v1/executions/{run_id}` ->
   `status`, `progress` (0-100), `progress_message`. Terminal: `completed`, `partial_success`, `failed`,
   `cancelled`.

### 2.2 Reading outputs
Each `outputs[].read_from` is a path from the **root of the `/result` response body**, which itself has a
`result` field:
```js
const body = await res.json();                     // the /result response
const text = body.result.by_key["headline"].value; // read_from: result.by_key.headline.value
const url  = body.result.by_key["hero_image"].url; // read_from: result.by_key.hero_image.url
```
- Not `body.by_key...` (undefined). Use **bracket notation**: keys may contain `-` or spaces.
- Text/JSON -> `.value`, **already parsed** (a string, or an object for JSON). Never `JSON.parse` it.
- Files -> `.url`. Per output `.state`: `present` (use it), `absent` (optional branch skipped: show nothing),
  `missing` (a step failed: show a problem), `not_ready` (keep polling).
- `url`s are not permanent (public CDN ~60 days; private storage ~1 hour). To keep a file, copy it to the
  app's storage, or read `/result` again for a fresh link.

### 2.3 Errors to handle
- `400 validation_error` - wrong input key or a value not JSON-encoded; `detail` says what to fix.
- `402 insufficient_credits` - the workspace has no credits left: tell the user.
- `404 workflow_not_found`, `400 workflow_not_published` - the `wf_...` id or its state is wrong.
- `409 idempotency_conflict` - same `Idempotency-Key` with a different body: use a new key per click.
- `429 too_many_requests` - too many calls (default 60 per minute): wait a few seconds; poll less often.
- A run that ends `failed`: show `error_message` from `GET /api/v1/executions/{run_id}`.

### 2.4 A published app spends credits
Every run spends the workspace's credits. Before sharing the app, ask the user whether to add sign-in, a
daily cap on generations, or both.

## 3. Never
- NO: Call Madoo from the browser or expose `MADOO_CLIENT_SECRET`.
- NO: Put workflow-authoring code (create, update, publish) in the app.
- NO: Wait or poll inside one server call.
- NO: Send raw text as `value` (it must be JSON-encoded) or invent input keys.
- NO: Read `/outputs` by name or `JSON.parse` a `value` - read `/result` with `read_from`.
- NO: Request a new token on every call.
