For AI agents: a documentation index is available at https://www.plungeai.com/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://www.plungeai.com/docs/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

Run multi-agent CNL workflows: inline (send the workflow document in the request), saved (reference a workflow id saved in Ocean Studio), each with an optional SSE streaming variant, plus a results-redemption route.

Auth: Authorization: Bearer ozk_YOUR_KEY on every route here. Tenancy is automatic — the engine keys every run to your key's identity; saved workflows and results resolve only within it.

Authoring CNL YAML itself (task types, parallel/sequential, harness missions) is this plungeai-workflows skill's job; this file covers the HTTP surface for executing it.

POST /v1/workflows/execute — inline execution

Two body formats:

JSON — {"workflow": <object or YAML string>, "input"?: string, "inputs"?: object, "user_context"?: object, "format"?: "json"|"yaml"|"markdown"|"text"}. Bare root name + tasks nest into a workflow, and a bare agent: <id> with that agent's own fields lifts to a one-task workflow (agent next to workflow or tasks is 400 ambiguous_body; a field named like an engine task key is 400 reserved_field). format (or Accept) picks how the answer is written; raw YAML in gets YAML out:

curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": {
      "name": "demo",
      "tasks": [
        {"type": "task", "id": "t1", "agent": "one-agent", "prompt": "Say: ok"}
      ]
    },
    "input": "x"
  }'

Raw YAML — send the whole workflow document as the body with Content-Type: text/yaml:

curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: text/yaml" \
  --data-binary @- <<'YAML'
workflow:
  name: demo
  tasks:
    - type: task
      id: t1
      agent: one-agent
      prompt: "Say: ok"
YAML

Response — HTTP 200 (an acknowledgement + pointer; once the synchronous run has stored its result, content carries the final task's output too):

{
  "success": true,
  "status": "completed",
  "workflow_id": "00000000-0000-4000-8000-000000000001",
  "final_task_id": "t1",
  "request_id": "00000000-0000-4000-8000-000000000004",
  "content": "ok"
}

status is completed, or incomplete when the final task stopped at its turn budget (the result is partial). Redeem any task's output with workflow_id + that task id (below); for a multi-task workflow final_task_id is the last task and intermediate results are redeemable by their own task ids. The workflow_id is the execution id: it is your x-trace-id when you sent one, and every /v1/executions/{id} route takes it.

Errors (envelope {"error":{"code","message"}}, header X-Error-Code): 400 missing_workflow (none of workflow, agent, tasks, or an empty YAML body), ambiguous_body, reserved_field, invalid_json, invalid_yaml, invalid_format, invalid_workflow (the engine's structural check; the message names the problem); 403 agent_not_active and 404 unknown_agent (a named agent is parked or not in the registry — refused before dispatch, re-discover the id); 409 duplicate_execution_id (a reused x-trace-id); 413 payload_too_large (over 1 MiB); 424 (a connection-tier agent with no connection, error.connect says where to make it); 429 rate_limited; 502 engine_error.

POST /v1/workflows/{id}/execute — saved workflow

Runs a workflow, agent or bot saved under your account (the id shown in Ocean Studio, or returned by POST /v1/workflows). Body is optional: {"input"?: string, "inputs"?: object, "user_context"?: object, "format"?}.

curl -s -X POST https://api.plungeai.com/v1/workflows/wf-morning-brief/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "today"}'
{
  "success": true,
  "status": "completed",
  "workflow_id": "00000000-0000-4000-8000-000000000001",
  "final_task_id": "t3",
  "request_id": "...",
  "content": "…"
}

Errors: 404 workflow_not_found (no such id under your identity — GET /v1/workflows lists yours), 404 unknown_agent / 403 agent_not_active (the saved YAML names an agent that is gone or parked), 400 invalid_format, 409 duplicate_execution_id, 413, 429, 502 engine_error. on_behalf_of is refused here (400 invalid_body): only POST /v1/execute takes it.

SSE streaming — live workflow events

Two variants, same event stream:

  • POST /v1/workflows/{id}/execute-stream — saved workflow; body {input?, inputs?, user_context?}
  • POST /v1/workflows/execute-stream — inline body ({workflow, input?, inputs?, user_context?} or raw YAML), same shapes as /v1/workflows/execute; all of input, inputs and user_context are forwarded.
curl -N -X POST https://api.plungeai.com/v1/workflows/wf-morning-brief/execute-stream \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "today"}'

Response is text/event-stream with named events — each frame is event: <type> + data: <json>, and the data payload always carries {workflow_id, execution_id, timestamp, type, data, elapsed_ms, duration_ms?} (the event-specific fields live under data). Event sequence:

request_received → workflow_loaded → workflow_started
  → task_dispatched → task_completed   (per task)
  → heartbeat                          (every ~25s during a task >20s — expect it,
                                        or idle timeouts will kill long streams)
  → workflow_completed                 (success only; execution summary + metrics)
  → workflow_result                    (terminal, ALWAYS check its success field)

On failure the engine emits workflow_error (error + progress counters) and the terminal workflow_result carries success: false + error — code that only checks for the workflow_result event name reports failed runs as done. Other event types appear for the matching task types (parallel_start, batch_progress, debate_*, …) — switch on the ones you need, ignore the rest.

A refusal before the first frame (a body that does not parse, an unknown or parked agent, a missing saved workflow) is a plain JSON error envelope with the matching status (400 invalid_workflow / invalid_yaml / missing_workflow, 403 agent_not_active, 404 workflow_not_found / unknown_agent, 413, 429, 502 engine_error), never an SSE frame — check the status before treating the body as a stream.

Consume with any SSE client (EventSource won't do — it's a POST; use fetch + a stream reader in JS, or curl -N / httpx.stream elsewhere) and parse the event: lines, not just data: lines. The stream is pass-through and unbuffered; x-request-id is stamped on the response headers. Don't rely on a dropped stream's run surviving — the engine runs the workflow inside the stream; for durability use /v1/workflows/execute + the results route.

GET /v1/workflows/results/{workflowId}/{taskId} — redeem a task result

curl -s https://api.plungeai.com/v1/workflows/results/00000000-0000-4000-8000-000000000005/t1 \
  -H "Authorization: Bearer ozk_YOUR_KEY"

Ready — HTTP 200:

{
  "content": "ok",
  "content_type": "text/plain",
  "workflow_id": "00000000-0000-4000-8000-000000000001",
  "task_id": "t1"
}

Not ready — HTTP 404 {"error":{"code":"not_ready",...}}. Poll with backoff (2s+, growing); not_ready while a workflow runs is normal. A run that failed is 409 execution_failed (the message is the run's error); one that finished with no visible content is 422 empty_completion (raise max_tokens to 64 or more). ?format= (json, yaml, markdown, text) picks the wire.

POST /v1/workflows/executions/{id}/cancel — cancel a running execution

Cooperative cancel: writes the SharedMemory cancel marker the engine and agent loops poll at their next turn/tool boundary, then stamps the execution row cancelled. {id} is the execution id — the same value returned as workflow_id from /v1/workflows/execute (or the x-trace-id you sent on that call). Since /v1/workflows/execute is synchronous, send the cancel from a second connection while the first is still running.

curl -s -X POST https://api.plungeai.com/v1/workflows/executions/00000000-0000-4000-8000-000000000005/cancel \
  -H "Authorization: Bearer ozk_YOUR_KEY"
{
  "success": true,
  "cancelled": "00000000-0000-4000-8000-000000000005",
  "markers": ["00000000-0000-4000-8000-000000000005"]
}

Errors: 404 not_found (no such execution under your identity — wrong owner never confirms the execution exists), 409 not_running (already finished or already cancelled). POST /v1/executions/{id}/cancel is the same operation.

Saved workflows, versions and bots — /v1/workflows CRUD (One API 2.3.0)

The same rows Studio's sidebar and MCP plungeai_workflow show:

RouteDoes
POST /v1/workflowssave {name, kind, yaml, description?, folder?, campaign_config?}; kind is workflow, agent, bot or campaign (campaign_config with kind: campaign sets up its ledger and schedule); 201 {workflow, warnings}
GET /v1/workflowslist yours {workflows, total, limit, offset, has_more} (kind, search, folder, limit 1–200 default 50, offset)
GET|PATCH|DELETE /v1/workflows/{id}read (row + live YAML), change name/description/yaml/kind/folder (null clears description and folder), delete (204)
GET|POST /v1/workflows/{id}/versionslist versions; save the current YAML as one {label?}
POST /v1/workflows/{id}/versions/{versionId}/restoremake a version live again (recorded as a new version)

The YAML is validated against the CNL schema and the kind's lint BEFORE anything is written (422 invalid_workflow with errors and warnings, row unchanged) — also on PATCH and restore. A bot also has …/permissions, …/workspace-files, …/inbox and …/bindings (policy, files, inbox, chats) and /v1/bot-groups: see the verb table in SKILL.md and plungeai-api-setup → references/bots-and-door.md. Running a saved bot by id through POST /v1/execute with agent: <id>@n is the Agent Call door (plungeai-api-setup → references/bots-and-door.md).

Fire-and-redeem pattern (Python)

import time, requests

BASE = "https://api.plungeai.com"
H = {"Authorization": "Bearer ozk_YOUR_KEY"}

ack = requests.post(f"{BASE}/v1/workflows/execute", headers=H, json={
    "workflow": {"name": "demo", "tasks": [
        {"type": "task", "id": "t1", "agent": "one-agent", "prompt": "Say: ok"}]},
}).json()

wf, task = ack["workflow_id"], ack["final_task_id"]
for i in range(1, 9):
    r = requests.get(f"{BASE}/v1/workflows/results/{wf}/{task}", headers=H)
    if r.status_code == 200:
        print(r.json()["content"]); break
    time.sleep(2 * i)

Trace correlation

Send x-trace-id: <your-id> on any execute call; every engine span of the run (dispatches, completions, the result) lands under that id at GET /v1/traces/<your-id> — see plungeai-results-traces.

Which route for what

CNL YAML (inline or saved) runs on POST /v1/workflows/execute, POST /v1/workflows/{id}/execute and their -stream twins. POST /v1/execute is the Agent Call door, not a workflow alias: the body is the program (a bare task, a workflow:, or agent: <saved id>@n), current and supported — plungeai-api-setup → references/bots-and-door.md. The pre-router CNL aliases of the SDK era are deprecated and not in the spec; new and generated code uses the routes above.

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.