{"openapi":"3.0.3","info":{"title":"Reduck REST API","version":"1.0.0","description":"\n# API overview\n\nThe Reduck API is a RESTful API at `https://mcp.reduck.ai`. It allows to programmatically call browser automation scripts using an API key.\n\n# Prerequisites\n\n- A [Reduck account](https://reduck.ai/signin)\n- An [API key](https://reduck.ai/api-keys)\n"},"servers":[{"url":"https://mcp.reduck.ai"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{},"parameters":{}},"paths":{"/run":{"post":{"operationId":"run","summary":"Run script","description":"Run a saved script. `browser` chooses where: omit it (or pass `\"managed\"`) for a Reduck managed browser, `\"extension\"` / `\"local\"` to auto-pick your single paired device of that kind, or `{\"deviceId\":\"…\"}` to pin one exact device. Running on your own browser already has cookies for a `loggedIn` script — it is already signed in. `region`/`country` apply to the managed browser only.\n\nWhat to run goes in `script` — `{host, slug, handle?, args?, files?}` — or, to run several at once, in `scripts`: a list of those same objects. Pass exactly one of the two. **A script's `args` live inside its own entry**, never at the top level of the request — the top level configures the run (`browser`, `region`, `waitForSeconds`), not what to run.\n\n`scripts` runs its entries **in parallel, each on its own browser**, all on the target you chose. They share nothing — separate cookie jars, separate pages, no ordering — and one entry failing does not stop the others, so the response is an array with one outcome per entry, in the order you passed them. Each is its own run and takes its own concurrency slot, so a list wider than the target's budget simply queues and drains.\n\nSet **`sequential: true`** to run the same list as a **chain** instead: ONE browser, in the given order, so each script inherits the previous one's cookies, DOM and navigation (never its return value — outputs are not piped between scripts). A chain is fail-fast: it stops at the first script that errors, and every entry after that one reports that it never ran — you always get one outcome per script. Use it when a script depends on the browser state another one leaves behind (log in, then act); leave it off otherwise, since a chain runs one script at a time on one browser.\n\nSynchronous by default: the response carries the outcome. A run still going after `waitForSeconds` returns `202` with a `runId` to poll via `GET /runs/{runId}` — for `scripts`, one handle per script plus a `batchId` to poll them all at once via `GET /batches/{batchId}`. A run id and a batch id are distinct: a run id always answers for that one run, even when it belongs to a batch. Both targets meter concurrent browsers and queue the rest FIFO, so a `queued` run is waiting for a slot — busy, not offline. Pass `waitForSeconds: 0` to fire and forget.\n\n**Files.** A script's file inputs go in its `files`, keyed by the input's name. To send a file's bytes, send the request as `multipart/form-data`: the JSON body above goes in a `spec` field, and each file in a part named `<step>:<file input>` — `0:attachment` for a single `script`, the entry's index in `scripts` otherwise — up to 50 MB each. A file already on your own machine can instead be named in JSON with `files.<input>.relativePath` (your paired Chrome only). Inline base64 in JSON works only while the whole body stays under 6 MB. Bytes reach the page named after the input, so pass the script's own name argument if it has one. Guide: https://docs.reduck.ai/upload-files","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"example":{"script":{"host":"doctolib.fr","slug":"search_practitioners","handle":"@reduck","args":{"specialty":"dentiste","location":"lyon","page":0}}},"schema":{"type":"object","properties":{"script":{"type":"object","properties":{"host":{"type":"string","description":"Script host, e.g. \"doctolib.fr\"."},"slug":{"type":"string","description":"Script slug, e.g. \"search_practitioners\"."},"handle":{"type":"string","description":"Owner handle for a public script: \"@user\" for a user, or a project handle (without @) — e.g. \"reduck\" for the official catalogue. Omit to run your own script."},"args":{"type":"object","additionalProperties":{"nullable":true},"description":"Arguments for the script, shaped by its own input schema (see the script's contract)."},"files":{"type":"object","additionalProperties":{"anyOf":[{"type":"object","properties":{"relativePath":{"type":"string"}},"required":["relativePath"]},{"type":"object","properties":{"objectRef":{"type":"string"}},"required":["objectRef"]},{"type":"object","properties":{"b64":{"type":"string"},"mime":{"type":"string"}},"required":["b64"]}]},"description":"File inputs, keyed by the script's file input name (its input-schema property marked format: \"file\", e.g. \"attachment\"). Leave out an optional one to send no file. Each value is one of: { relativePath } — a file in the \"reduck\" folder on the Desktop of the machine running the browser, named relative to it (\"invoice.pdf\", \"2026/invoice.pdf\"); it keeps its own name. Needs an extension device with \"Allow access to file URLs\" on (`local files on` in list_devices). If you cannot put the file there yourself, ask the user to, and use the name they give. { b64, mime? } — the bytes inline; small files only, as the whole request body must stay under 100 KB (6 MB on POST /run). { objectRef } — set by the server from a multipart POST /run upload (up to 50 MB; the reduck CLI sends files this way); do not send it yourself. Bytes reach the page named after the input, not the file, so pass the script's own name argument if it has one (send_email: attachmentName). Guide: https://docs.reduck.ai/upload-files"},"version_id":{"type":"string","description":"Run a specific version instead of the current one. Get IDs from list_script_versions."}},"required":["host","slug"],"additionalProperties":false,"description":"The script to run and its arguments: {host, slug, handle?, args?, files?}. e.g. {\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{…}}. Use `scripts` instead to run several at once."},"scripts":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string","description":"Script host, e.g. \"doctolib.fr\"."},"slug":{"type":"string","description":"Script slug, e.g. \"search_practitioners\"."},"handle":{"type":"string","description":"Owner handle for a public script: \"@user\" for a user, or a project handle (without @) — e.g. \"reduck\" for the official catalogue. Omit to run your own script."},"args":{"type":"object","additionalProperties":{"nullable":true},"description":"Arguments for the script, shaped by its own input schema (see the script's contract)."},"files":{"type":"object","additionalProperties":{"anyOf":[{"type":"object","properties":{"relativePath":{"type":"string"}},"required":["relativePath"]},{"type":"object","properties":{"objectRef":{"type":"string"}},"required":["objectRef"]},{"type":"object","properties":{"b64":{"type":"string"},"mime":{"type":"string"}},"required":["b64"]}]},"description":"File inputs, keyed by the script's file input name (its input-schema property marked format: \"file\", e.g. \"attachment\"). Leave out an optional one to send no file. Each value is one of: { relativePath } — a file in the \"reduck\" folder on the Desktop of the machine running the browser, named relative to it (\"invoice.pdf\", \"2026/invoice.pdf\"); it keeps its own name. Needs an extension device with \"Allow access to file URLs\" on (`local files on` in list_devices). If you cannot put the file there yourself, ask the user to, and use the name they give. { b64, mime? } — the bytes inline; small files only, as the whole request body must stay under 100 KB (6 MB on POST /run). { objectRef } — set by the server from a multipart POST /run upload (up to 50 MB; the reduck CLI sends files this way); do not send it yourself. Bytes reach the page named after the input, not the file, so pass the script's own name argument if it has one (send_email: attachmentName). Guide: https://docs.reduck.ai/upload-files"},"version_id":{"type":"string","description":"Run a specific version instead of the current one. Get IDs from list_script_versions."}},"required":["host","slug"],"additionalProperties":false},"minItems":1,"maxItems":20,"description":"Run several scripts at once (up to 20), each on its own browser, in parallel. Each entry is its own {host, slug, handle?, args?, files?} — arguments belong to the entry that uses them, never to the run. Use this instead of `script` (exactly one of the two). They share nothing (no cookies, no DOM, no order) and one failing does not stop the others, so you always get one outcome per entry, in the order you passed them. Each is its own run and takes its own slot on the target's concurrency budget."},"sequential":{"type":"boolean","description":"Run `scripts` as a chain instead of in parallel: one browser, in the given order, so each script sees the previous one's cookies, DOM and navigation (never its return value). Stops at the first script that errors: you still get one outcome per entry, but each one after the failure reports that it never ran. Only valid with `scripts`. Default: false."},"browser":{"anyOf":[{"type":"string","enum":["managed"]},{"type":"string","enum":["extension"]},{"type":"string","enum":["local"]},{"type":"object","properties":{"deviceId":{"type":"string"}},"required":["deviceId"],"additionalProperties":false}],"description":"Where to run: \"managed\" (a Reduck managed browser — the only target that accepts region/country), \"extension\" (your paired browser extension — already logged in, so a loggedIn script needs no connector), \"local\" (a paired `reduck local` device), or {\"deviceId\":\"<list_devices id>\"} to pin one exact device. \"extension\"/\"local\" require exactly one paired device of that kind, else pass a deviceId."},"deviceId":{"type":"string","description":"Which paired browser to run on, by its list_devices id (a per-device UUID — you may have several paired). Shorthand for browser: {deviceId}; omit to auto-pick (see `browser`)."},"region":{"type":"string","enum":["eu-central-1","us-east-1","us-west-2","ap-southeast-1"],"description":"Datacenter region (managed browser only) — pick the one nearest your target site to cut latency. One of: eu-central-1 (Europe (Frankfurt)), us-east-1 (US East), us-west-2 (US West), ap-southeast-1 (Asia Pacific (Singapore)). Defaults to the nearest."},"country":{"type":"string","description":"ISO-3166 alpha-2 country code (e.g. \"FR\"), managed browser only — routes the session through an in-country residential proxy so it egresses from there, not the datacenter; the lever for geo-gated sites. Nearest available is used if the exact country has none. Omit for datacenter egress."},"login":{"type":"object","properties":{"sso":{"type":"object","properties":{"account":{"type":"string","description":"The SSO account (email) to sign the site in through — an existing SSO connector."},"loginUrl":{"type":"string","format":"uri","description":"Where the site's SSO sign-in starts, e.g. https://<org>.awsapps.com/start. Defaults to the host root, which is wrong for sites whose sign-in begins on another path — set it there."}},"required":["account"],"additionalProperties":false,"description":"Sign the site in through this SSO connector when it has none, and rebuild through it on a failed probe. Errors if the site's connector was created through a different SSO account."},"probe":{"type":"string","description":"A saved script, addressed \"host/slug\" (or \"@handle/host/slug\"), that reports whether the session is live — a boolean or { connected, account }. Runs on the connector BEFORE the main script; a session it reports as dead (or, with assertAccount, as the wrong account) stops the main script."},"autoreconnect":{"type":"boolean","description":"On a failed probe, clear the connector and rebuild it through SSO (`sso.account`, or the one the connector was created through), then probe once more before giving up. Needs `probe` and an SSO to rebuild from."},"assertAccount":{"type":"string","description":"Require the probe to report the session is signed in as this account (e.g. an email); any other identity is treated like a failed probe. Needs a `probe` that returns `account`."}},"additionalProperties":false,"description":"Connector login policy for a managed run: ensure/verify/repair the site's signed-in session before the main script runs. Managed browser, one script only."},"profile":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_.@:-]*$","description":"Run as one of your end-users: their connectors are used instead of your own, and never yours in their place. The name you stored their connectors under. Managed browser only."},"account":{"type":"string","maxLength":320,"description":"When a site holds several connectors, use the one whose probe reported this account (e.g. an email)."},"label":{"type":"string","maxLength":200,"description":"When a site holds several connectors, use the one with this label, or \"\" for the one without a label."},"waitForSeconds":{"type":"number","minimum":0,"maximum":110,"description":"Seconds to wait for the run to finish before returning a {runId, status} envelope to poll (default 60, max 110). Leave it unset: the default is right for nearly every call. 0 = return immediately (fan-out)."}},"additionalProperties":false}},"multipart/form-data":{"schema":{"type":"object","properties":{"spec":{"type":"string","description":"The same JSON body as the application/json form, as a text field."}},"required":["spec"],"additionalProperties":{"type":"string","format":"binary","description":"A file, in a part named \"<step>:<file input>\" — \"0:attachment\" for a single `script`, the entry's index in `scripts` otherwise. Up to 50 MB per file."},"example":{"spec":"{\"browser\":\"extension\",\"script\":{\"host\":\"mail.google.com\",\"slug\":\"send_email\",\"handle\":\"reduck\",\"args\":{\"to\":\"me@example.com\",\"subject\":\"Our new banner\",\"attachmentName\":\"banner.png\"}}}","0:attachment":"(binary)"}}}}},"responses":{"200":{"description":"Run result, or a script-level error — both carry `runId`. For `scripts`, an array of these, one per script in the order you passed them (with `sequential`, the ones after a failure report that they never ran).","content":{"application/json":{"example":{"runId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","result":{"total":1208,"page":0,"providers":[{"full_name":"Dr Marion VINCENT","specialty":"Chirurgien-dentiste","profile_path":"dentiste/paris/marion-vincent?pid=practice-561943","city":"Lyon","zipcode":"69005","online_booking":true}]}},"schema":{"anyOf":[{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},{"type":"array","items":{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},"description":"One outcome per script in `scripts`, in the order you passed them, always one each, and a script's own `error` does not stop the others. With `sequential`, the scripts after a failure report that they never ran rather than being left out."}]}}}},"202":{"description":"Still running after `waitForSeconds` — poll `GET /runs/{runId}`. `Retry-After` repeats `retryAfterMs` in seconds. For `scripts`, one handle per script plus `batchId`, which polls the whole batch in one request via `GET /batches/{batchId}`.","content":{"application/json":{"examples":{"run":{"value":{"runId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","status":"queued","retryAfterMs":15000,"queuePosition":2}},"batch":{"value":{"batchId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","status":"running","totalSteps":2,"runs":[{"runId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","status":"running","retryAfterMs":10000},{"runId":"c1f0a6d2-4d5e-4f11-9d3b-2ac7e8f41b06","status":"queued","retryAfterMs":15000,"queuePosition":1}],"retryAfterMs":10000}}},"schema":{"anyOf":[{"type":"object","properties":{"runId":{"type":"string","description":"Run id to poll with GET /runs/{runId}."},"status":{"type":"string","enum":["queued","running"],"description":"`queued` = waiting for a slot on the target's concurrency budget (busy, not offline); `running` = executing."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again."},"queuePosition":{"type":"number","description":"1-based place in line, when queued behind other runs."}},"required":["runId","status","retryAfterMs"],"description":"An in-flight run: poll GET /runs/{runId} until it reports completed/failed."},{"type":"object","properties":{"batchId":{"type":"string","description":"Handle for the whole batch: the first script's run id. Pass it to GET /runs/{runId} to get every script's state in one request."},"status":{"type":"string","enum":["queued","running","completed","failed"],"description":"The batch as a whole: in flight until every script is terminal, then `failed` if any of them failed."},"totalSteps":{"type":"number","description":"How many scripts the batch was asked to run."},"runs":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},{"type":"object","properties":{"runId":{"type":"string","description":"Run id to poll with GET /runs/{runId}."},"status":{"type":"string","enum":["queued","running"],"description":"`queued` = waiting for a slot on the target's concurrency budget (busy, not offline); `running` = executing."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again."},"queuePosition":{"type":"number","description":"1-based place in line, when queued behind other runs."}},"required":["runId","status","retryAfterMs"],"description":"An in-flight run: poll GET /runs/{runId} until it reports completed/failed."}]},"description":"One entry per script, in the order you passed them: an outcome for the ones that finished, a pollable handle for the ones still going."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again. Absent once the batch is terminal."}},"required":["batchId","status","totalSteps","runs"],"description":"An in-flight `scripts` batch: one handle per script, plus `batchId` for all of them."}]}}}},"400":{"description":"Invalid body, contradictory target (e.g. `region` with a device), or no device of the requested kind.","content":{"application/json":{"example":{"error":"doctolib.fr/search_practitioners is loggedIn — run it on your paired browser (browser:\"extension\")."},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"example":{"error":"invalid or unauthorized credential"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Out of quota for this period.","content":{"application/json":{"examples":{"runs":{"value":{"error":"Monthly script-run quota reached: 1000 of 1000 used this period (resets 2026-08-13). Upgrade for a higher limit."}},"browser_minutes":{"value":{"error":"Cloud browser time quota reached: 612.4 min of 600 min used this period (resets 2026-08-13). To keep going now, run on your paired browser instead (browser:\"extension\"); upgrade for a higher limit."}},"proxy_mb":{"value":{"error":"Residential proxy bandwidth quota reached: 410.9 MB of 250 MB used this period (resets 2026-08-13). To keep going now, drop `country` to egress from the datacenter instead; upgrade for a higher limit."}}},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Script not found or not accessible, or an unknown `deviceId`.","content":{"application/json":{"example":{"error":"script doctolib.fr/search_practitioners not found or not accessible"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The target device exists but its state conflicts with running on it: more than one paired device of the requested kind (pass an explicit `deviceId`), or a device whose browser never answered — open it and retry.","content":{"application/json":{"example":{"error":"Multiple extension devices — pass an explicit deviceId (one of: 0e2f… (Laptop), 7a91… (Desktop))."},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Too large: a JSON body over 6 MB (usually a file sent inline as base64), or a multipart file over 50 MB.","content":{"application/json":{"examples":{"json":{"value":{"error":"Request body is over this endpoint's 6144 KB limit. The usual cause is a file sent inline as base64. To send a larger file, put it in the \"reduck\" folder on the Desktop of the computer that runs the browser and give its name ({ relativePath: \"invoice.pdf\" }, Reduck extension only), or run the script with the reduck CLI, which uploads files up to 50 MB. Guide: https://docs.reduck.ai/upload-files"}},"multipart":{"value":{"error":"a file exceeds the 52428800-byte limit"}}},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Internal error.","content":{"application/json":{"example":{"error":"internal error"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl (managed)","source":"curl -X POST https://mcp.reduck.ai/run \\\n  -H \"X-API-Key: $REDUCK_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"script\":{\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{\"specialty\":\"dentiste\",\"location\":\"lyon\",\"page\":0}}}'"},{"lang":"Shell","label":"curl (your browser)","source":"curl -X POST https://mcp.reduck.ai/run \\\n  -H \"X-API-Key: $REDUCK_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"script\":{\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{\"specialty\":\"dentiste\",\"location\":\"lyon\",\"page\":0}},\"browser\":\"extension\"}'"},{"lang":"Shell","label":"curl (several at once)","source":"curl -X POST https://mcp.reduck.ai/run \\\n  -H \"X-API-Key: $REDUCK_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"browser\":\"extension\",\"scripts\":[{\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{\"specialty\":\"dentiste\",\"location\":\"lyon\",\"page\":0}},{\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{\"specialty\":\"dentiste\",\"location\":\"paris\",\"page\":0}}]}'"},{"lang":"Shell","label":"curl (chain)","source":"curl -X POST https://mcp.reduck.ai/run \\\n  -H \"X-API-Key: $REDUCK_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"browser\":\"extension\",\"sequential\":true,\"scripts\":[{\"host\":\"doctolib.fr\",\"slug\":\"login\",\"handle\":\"@reduck\"},{\"host\":\"doctolib.fr\",\"slug\":\"search_practitioners\",\"handle\":\"@reduck\",\"args\":{\"specialty\":\"dentiste\",\"location\":\"lyon\",\"page\":0}}]}'"}]}},"/runs/{runId}":{"get":{"operationId":"getRun","summary":"Get run","description":"A run's current state by id — the poll side of `POST /run`'s `202`. A finished run returns its outcome; one still in flight returns the same `{runId, status, retryAfterMs}` envelope, so you can loop on this endpoint until it settles.\n\nAlways ONE run: a run that belongs to a `scripts` batch still answers only for itself here, with its own result. Use `GET /batches/{batchId}` for the batch.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","example":"7b5598c8-9b20-4748-aa3f-8f06e59612e9"},"required":true,"name":"runId","in":"path"}],"responses":{"200":{"description":"The run's outcome, or an in-flight envelope to poll again.","content":{"application/json":{"example":{"runId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","result":{"total":1208,"page":0,"providers":[{"full_name":"Dr Marion VINCENT","specialty":"Chirurgien-dentiste","profile_path":"dentiste/paris/marion-vincent?pid=practice-561943","city":"Lyon","zipcode":"69005","online_booking":true}]}},"schema":{"anyOf":[{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},{"type":"object","properties":{"runId":{"type":"string","description":"Run id to poll with GET /runs/{runId}."},"status":{"type":"string","enum":["queued","running"],"description":"`queued` = waiting for a slot on the target's concurrency budget (busy, not offline); `running` = executing."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again."},"queuePosition":{"type":"number","description":"1-based place in line, when queued behind other runs."}},"required":["runId","status","retryAfterMs"],"description":"An in-flight run: poll GET /runs/{runId} until it reports completed/failed."},{"type":"object","properties":{"batchId":{"type":"string","description":"Handle for the whole batch: the first script's run id. Pass it to GET /runs/{runId} to get every script's state in one request."},"status":{"type":"string","enum":["queued","running","completed","failed"],"description":"The batch as a whole: in flight until every script is terminal, then `failed` if any of them failed."},"totalSteps":{"type":"number","description":"How many scripts the batch was asked to run."},"runs":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},{"type":"object","properties":{"runId":{"type":"string","description":"Run id to poll with GET /runs/{runId}."},"status":{"type":"string","enum":["queued","running"],"description":"`queued` = waiting for a slot on the target's concurrency budget (busy, not offline); `running` = executing."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again."},"queuePosition":{"type":"number","description":"1-based place in line, when queued behind other runs."}},"required":["runId","status","retryAfterMs"],"description":"An in-flight run: poll GET /runs/{runId} until it reports completed/failed."}]},"description":"One entry per script, in the order you passed them: an outcome for the ones that finished, a pollable handle for the ones still going."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again. Absent once the batch is terminal."}},"required":["batchId","status","totalSteps","runs"],"description":"An in-flight `scripts` batch: one handle per script, plus `batchId` for all of them."}],"description":"The run's current state: terminal outcome, or an in-flight handle to poll again."}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"example":{"error":"missing credential (X-API-Key or Authorization: Bearer)"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such run on this account. A `batchId` also 404s here — batch ids and run ids are distinct; use `GET /batches/{batchId}`.","content":{"application/json":{"example":{"error":"run 7b5598c8-9b20-4748-aa3f-8f06e59612e9 not found"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Internal error.","content":{"application/json":{"example":{"error":"internal error"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl \"https://mcp.reduck.ai/runs/7b5598c8-9b20-4748-aa3f-8f06e59612e9\" -H \"X-API-Key: $REDUCK_API_KEY\""}]}},"/batches/{batchId}":{"get":{"operationId":"getBatch","summary":"Get batch","description":"Every script of a `scripts` request in one call, in the order you passed them — the poll side of a batch's `202`. Each entry is the same envelope `GET /runs/{runId}` returns for one run, so you can loop on this endpoint until the batch settles.\n\nThe batch stays in flight until EVERY script is terminal, then reports `failed` if any of them failed — so one script failing never cuts the poll short while its siblings are still running. The list always holds all `totalSteps` scripts, in both modes: a `sequential` batch stops at its first failure, and the scripts after it are reported as never having run rather than omitted.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","example":"7b5598c8-9b20-4748-aa3f-8f06e59612e9"},"required":true,"name":"batchId","in":"path"}],"responses":{"200":{"description":"One envelope per script, plus the batch's derived status.","content":{"application/json":{"example":{"batchId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","status":"running","totalSteps":2,"runs":[{"runId":"7b5598c8-9b20-4748-aa3f-8f06e59612e9","status":"running","retryAfterMs":10000},{"runId":"c1f0a6d2-4d5e-4f11-9d3b-2ac7e8f41b06","status":"queued","retryAfterMs":15000,"queuePosition":1}],"retryAfterMs":10000},"schema":{"type":"object","properties":{"batchId":{"type":"string","description":"Handle for the whole batch: the first script's run id. Pass it to GET /runs/{runId} to get every script's state in one request."},"status":{"type":"string","enum":["queued","running","completed","failed"],"description":"The batch as a whole: in flight until every script is terminal, then `failed` if any of them failed."},"totalSteps":{"type":"number","description":"How many scripts the batch was asked to run."},"runs":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"result":{"nullable":true,"description":"The script's return value (its output schema). Present on success."},"error":{"type":"string","description":"Script-level error (the body threw or its output failed validation). Mutually exclusive with `result`, still returned with HTTP 200."},"runId":{"type":"string","description":"Run id for tracking/replay; present on both success and a script-level error."},"errorType":{"type":"string","enum":["script_error","user_error","internal_error","site_error","device_error"],"description":"Classification of a failed run: whether the script, the caller's setup, the target site, the caller's paired device, or infrastructure caused it. Present only with `error`."},"externalMessage":{"type":"string","description":"Actionable, user-facing explanation of the failure. Present only with `error`."}},"description":"Run outcome: `result` xor `error` (+ `errorType`/`externalMessage`), plus a `runId`."},{"type":"object","properties":{"runId":{"type":"string","description":"Run id to poll with GET /runs/{runId}."},"status":{"type":"string","enum":["queued","running"],"description":"`queued` = waiting for a slot on the target's concurrency budget (busy, not offline); `running` = executing."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again."},"queuePosition":{"type":"number","description":"1-based place in line, when queued behind other runs."}},"required":["runId","status","retryAfterMs"],"description":"An in-flight run: poll GET /runs/{runId} until it reports completed/failed."}]},"description":"One entry per script, in the order you passed them: an outcome for the ones that finished, a pollable handle for the ones still going."},"retryAfterMs":{"type":"number","description":"How long to wait before polling again. Absent once the batch is terminal."}},"required":["batchId","status","totalSteps","runs"],"description":"An in-flight `scripts` batch: one handle per script, plus `batchId` for all of them."}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"example":{"error":"missing credential (X-API-Key or Authorization: Bearer)"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such batch on this account. A run id 404s here — batch ids and run ids are distinct; use `GET /runs/{runId}`.","content":{"application/json":{"example":{"error":"batch 7b5598c8-9b20-4748-aa3f-8f06e59612e9 not found"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Internal error.","content":{"application/json":{"example":{"error":"internal error"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl \"https://mcp.reduck.ai/batches/7b5598c8-9b20-4748-aa3f-8f06e59612e9\" -H \"X-API-Key: $REDUCK_API_KEY\""}]}},"/scripts":{"get":{"operationId":"listScripts","summary":"List scripts","description":"List the scripts you can run: your own, those of projects you belong to, and the official catalogue. Without `handle` the list spans all three; with `handle` it scopes to one owner. One bounded page; `hasNextPage` flags more — bump `page`.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"handle","in":"query"},{"schema":{"type":"string"},"required":false,"name":"host","in":"query"},{"schema":{"type":"string","maxLength":512,"description":"Search query over name, slug, host, description and the input/output schemas. Both filters and ranks, so leave `sortBy` unset to get the best matches first."},"required":false,"description":"Search query over name, slug, host, description and the input/output schemas. Both filters and ranks, so leave `sortBy` unset to get the best matches first.","name":"q","in":"query"},{"schema":{"type":"string","enum":["public","private"]},"required":false,"name":"visibility","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"archived","in":"query"},{"schema":{"type":"string","enum":["relevance","name","host","runs","successRate","avgTime","lastRunAt","createdAt","updatedAt"],"description":"Leave unset unless you need a specific order: setting it alongside `q` discards the ranking and re-sorts the matches."},"required":false,"description":"Leave unset unless you need a specific order: setting it alongside `q` discards the ranking and re-sorts the matches.","name":"sortBy","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0},"required":false,"name":"page","in":"query"}],"responses":{"200":{"description":"One bounded page of scripts.","content":{"application/json":{"example":{"scripts":[{"handle":"@reduck","host":"doctolib.fr","slug":"search_practitioners","description":"Search practitioners by specialty and location."}],"hasNextPage":false,"totalPages":1},"schema":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"object","properties":{"handle":{"type":"string","nullable":true},"host":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"sideEffects":{"type":"string","enum":["none","undetermined","write"]}},"required":["handle","host","slug","description","sideEffects"]}},"hasNextPage":{"type":"boolean"},"totalPages":{"type":"integer"}},"required":["scripts","hasNextPage"]}}}},"400":{"description":"Invalid query parameter.","content":{"application/json":{"example":{"error":"page: Expected number, received nan"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"example":{"error":"missing credential (X-API-Key or Authorization: Bearer)"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Internal error.","content":{"application/json":{"example":{"error":"internal error"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl \"https://mcp.reduck.ai/scripts?handle=reduck&q=doctolib\" -H \"X-API-Key: $REDUCK_API_KEY\""}]}},"/scripts/{host}/{slug}":{"get":{"operationId":"readScript","summary":"Read script","description":"Read a script's runnable contract — name, description, visibility, loggedIn, sideEffects, and the input/output JSON Schemas (the arguments to pass to `/run`), plus the per-browser-setup success breakdown when the script has run history. Omit `handle` for your own scripts; pass a user handle (\"@user\") or project handle (\"project\") — e.g. \"reduck\" for the official catalogue — to read that owner's public script. `includeScript=true` also returns the raw code body (not available for official scripts).","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","example":"doctolib.fr"},"required":true,"name":"host","in":"path"},{"schema":{"type":"string","example":"search_practitioners"},"required":true,"name":"slug","in":"path"},{"schema":{"type":"string"},"required":false,"name":"handle","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"includeScript","in":"query"}],"responses":{"200":{"description":"The script's contract (and `code` when requested).","content":{"application/json":{"example":{"name":"Search practitioners","description":"Search practitioners by specialty and location.","visibility":"public","loggedIn":false,"sideEffects":"none","updatedAt":"2025-01-15T10:30:00.000Z","input":{"type":"object","properties":{"specialty":{"type":"string"},"location":{"type":"string"},"page":{"type":"number"}},"required":["specialty","location"]},"output":{"type":"object"},"stats":[{"browser":"managed","region":"eu-central-1","country":null,"total":12,"success":11}]},"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"visibility":{"type":"string","enum":["private","public"]},"archived":{"type":"boolean"},"loggedIn":{"type":"boolean"},"sideEffects":{"type":"string","enum":["none","undetermined","write"]},"humanRequired":{"type":"boolean"},"updatedAt":{"type":"string"},"input":{"type":"object","additionalProperties":{"nullable":true}},"output":{"type":"object","additionalProperties":{"nullable":true}},"note":{"type":"string"},"code":{"type":"string"},"stats":{"type":"array","items":{"type":"object","properties":{"browser":{"type":"string"},"region":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"total":{"type":"number"},"success":{"type":"number"}},"required":["browser","region","country","total","success"]}}},"required":["name","description","visibility","archived","loggedIn","sideEffects","humanRequired","input","output"]}}}},"400":{"description":"Invalid query parameter.","content":{"application/json":{"example":{"error":"Unrecognized key(s) in object"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"example":{"error":"missing credential (X-API-Key or Authorization: Bearer)"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused (e.g. the code of an official script).","content":{"application/json":{"example":{"error":"Can't access the code of official scripts."},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Script not found or not accessible.","content":{"application/json":{"example":{"error":"script doctolib.fr/search_practitioners not found or not accessible"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Internal error.","content":{"application/json":{"example":{"error":"internal error"},"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl \"https://mcp.reduck.ai/scripts/doctolib.fr/search_practitioners?handle=@reduck\" -H \"X-API-Key: $REDUCK_API_KEY\""}]}}}}