> ## Documentation Index
> Fetch the complete documentation index at: https://docs.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows as Agent Tools

Give any AI agent reliable crypto skills by exposing B3OS workflows as tool calls — mint a scoped key, run a workflow, track it to completion, and read run history over the REST API.

B3OS workflows are durable, auditable automations that move real value on-chain, call dozens of integrations, and wait for human approval when needed. Any of them can be exposed to an AI agent as a **tool call**: your agent hands B3OS a payload, B3OS runs the workflow, and your agent tracks the run to completion and reads the result.

This guide is the end-to-end path for an agent builder: mint a least-privilege key, run a published workflow, follow it to a terminal status, and list run history — all over the REST API, from any agent framework.

**This is the run lifecycle, not workflow authoring**

To *create* workflows from code (define → validate → publish), see the [API Quickstart](/examples/api-quickstart).
This page is about *invoking and tracking* workflows your org already owns, which is what an agent does at runtime.

## The loop

**Mint a scoped key**

Create a **service account** with only the permissions an agent needs, then mint an API key for it.

**Run a published workflow**

`POST /v1/workflows/:id/run` with a payload. Returns a `runId` immediately — execution is asynchronous.

**Track it to a terminal status**

Poll `GET /v1/runs/:id`, or stream `GET /v1/runs/:id/stream` (SSE), until the run reaches `success`, `partial`,
`failure`, or `cancelled`.

**Read the result and history**

Read per-node outputs from `executionState`, and list prior runs with `GET /v1/workflows/:id/runs`.

## 1. Mint a least-privilege key

API keys use the `b3sk_` prefix and are sent as `Authorization: Bearer b3sk_...`. A key is permanently bound to its organization — it can never read or run another org's data.

For an agent, prefer a **service account** over a coarse `read-write` user key. A service account carries an _explicit_ permission list, so you can grant exactly "run workflows + read runs" and nothing else. (A `read-write` user key also holds `apikey:create`/`apikey:revoke`, so it can mint more keys — avoid handing that to an agent.)

**Create the service account**

```bash
curl -sS -X POST "$B3OS_API_URL/v1/service-accounts" \
  -H "Authorization: Bearer $B3OS_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trading-agent",
    "description": "Runs market + swap workflows for the trading agent",
    "permissions": ["workflow:read", "workflow:execute", "run:read"]
  }'
```

You can only grant permissions you hold yourself. `workflow:read` + `workflow:execute` + `run:read` is the complete set for the agent loop — discover, run, poll, stream, cancel, and read history. No extra permissions are needed: SSE streaming is gated on `run:read`, and cancelling a run is gated on `workflow:execute`, both already in the set.

**Mint a key for it**

```bash
curl -sS -X POST "$B3OS_API_URL/v1/api-keys" \
  -H "Authorization: Bearer $B3OS_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trading-agent key",
    "serviceAccountId": "sa_123"
  }'
```

The plaintext key (`key` in the response) is shown **once** — store it in a secret manager. Hand this key to the agent; keep `B3OS_ADMIN_KEY` (the owner/developer key that created the account) on your provisioning path only.

**No public self-serve onboarding yet**

Today a service account and its key are created by an existing org owner or developer (in the B3OS app or via the API
above). There is no anonymous "sign up and get a key" flow — an agent always acts inside a B3OS organization that
someone provisioned. Plan your integration as "my org runs my workflows on behalf of my users."

## 2. Run a published workflow

The workflow must be **published** (status `active`). Pass run-time inputs in `payload`; everything is optional.

```bash
curl -sS -X POST "$B3OS_API_URL/v1/workflows/$WORKFLOW_ID/run" \
  -H "Authorization: Bearer $B3OS_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": { "asset": "USDC", "amount": "100" }
  }'
```

```json
{ "data": { "runId": "run_c9i6j8k2l0m3" }, "code": 200, "message": "success" }
```

The call returns as soon as the run is queued — it does **not** wait for completion. Useful options in the body:

| Field           | Type    | Purpose                                                                                   |
| --------------- | ------- | ----------------------------------------------------------------------------------------- |
| `payload`       | object  | Run-time inputs, read inside the workflow via `{{$trigger.body.*}}`                       |
| `runId`         | string  | Pre-choose the run id (must start `run_`) so you can open the SSE stream _before_ running |
| `simulate`      | boolean | Dry-run — executes logic without sending on-chain transactions                            |
| `version`       | integer | Run a specific published version instead of the default                                   |
| `triggerNodeId` | string  | For multi-trigger workflows, which trigger to attribute the run to                        |

**Webhook alternative**

A manual workflow also exposes an unauthenticated webhook: `POST /v1/workflows/:id/webhook?secret=...`, where the secret comes from `GET /v1/workflows/:id/webhook-secret`. It fires a run and returns only `{ action, runId }` — handy for "trigger and forget", but it exposes no run internals, so use the authenticated path above when your agent needs the result.

## 3. Track the run to a terminal status

A run's `status` is one of `running`, `waiting`, `success`, `partial`, `failure`, or `cancelled`. The last four are **terminal**. (`waiting` means a node is paused — e.g. a human-approval or wait-for-event gate.)

### Poll

```bash
curl -sS "$B3OS_API_URL/v1/runs/$RUN_ID?summary=true" \
  -H "Authorization: Bearer $B3OS_AGENT_KEY"
```

`?summary=true` drops the heavy `executionState` and `workflowDefinition` so a status poll stays light; omit it (or pass `?nodeIds=a,b`) when you want per-node results. Poll until `status` is terminal, then read the result (below).

### Stream (SSE)

For push updates instead of polling, open the Server-Sent Events stream. It needs `run:read` (a `read`-scope key can stream). The stream closes itself when the run completes.

```bash curl
curl -N "$B3OS_API_URL/v1/runs/$RUN_ID/stream" \
  -H "Authorization: Bearer $B3OS_AGENT_KEY"
```

```javascript node.js
const res = await fetch(`${process.env.B3OS_API_URL}/v1/runs/${runId}/stream`, {
  headers: { Authorization: `Bearer ${process.env.B3OS_AGENT_KEY}` },
});
for await (const chunk of res.body) process.stdout.write(Buffer.from(chunk));
```

```python python
import os, requests
with requests.get(
    f"{os.environ['B3OS_API_URL']}/v1/runs/{run_id}/stream",
    headers={"Authorization": f"Bearer {os.environ['B3OS_AGENT_KEY']}"},
    stream=True,
) as r:
    for line in r.iter_lines():
        if line:
            print(line.decode())
```

**Don't miss the first event**

To guarantee you see `run_started`, choose the `runId` yourself, open the stream first, then run with that same
`runId` in the body. The stream lets you subscribe to a run id before it exists.

### Read the result

`GET /v1/runs/:id` returns the full run, including `executionState` — a map of `nodeId → { status, input, result, startedAt, finishedAt }`. Read the `result` of the node whose output your agent needs; that's the tool's return value. Secrets are masked in the response.

## 4. Read run history

| Want                     | Endpoint                     |
| ------------------------ | ---------------------------- |
| Runs for one workflow    | `GET /v1/workflows/:id/runs` |
| All runs in the org      | `GET /v1/runs`               |
| Counts grouped by status | `GET /v1/runs/status-counts` |

All list endpoints take `limit` (default 20, max 100), `offset`, and an optional `status` filter, and return `{ data: { items, limit, offset, hasMore } }`. There is no `total` — page with `offset` until `hasMore` is `false`.

## Permissions at a glance

Grant a service account exactly the rows the agent uses. (For a legacy user key, `read` scope covers the read/stream rows but **not** `workflow:execute`; `read-write` covers everything.)

| Operation              | Endpoint                     | Permission         | `read` key | `read-write` key |
| ---------------------- | ---------------------------- | ------------------ | :--------: | :--------------: |
| Run a workflow         | `POST /v1/workflows/:id/run` | `workflow:execute` |     —      |        ✅        |
| Get a run / its result | `GET /v1/runs/:id`           | `run:read`         |     ✅     |        ✅        |
| List run history       | `GET /v1/runs`, `.../runs`   | `run:read`         |     ✅     |        ✅        |
| Stream a run (SSE)     | `GET /v1/runs/:id/stream`    | `run:read`         |     ✅     |        ✅        |
| Cancel a run           | `POST /v1/runs/:id/cancel`   | `workflow:execute` |     —      |        ✅        |
| Discover workflows     | `GET /v1/workflows`          | `workflow:read`    |     ✅     |        ✅        |

## Wire it into your agent

Expose each workflow as one tool. The tool's input schema mirrors the workflow's manual-trigger `inputSchema`; the implementation is the run-then-track loop above.

```json
{
  "name": "run_treasury_rebalance",
  "description": "Rebalance the org treasury to target allocation. Returns the per-swap results.",
  "input_schema": {
    "type": "object",
    "properties": {
      "maxSlippageBps": { "type": "integer", "description": "Max slippage in basis points" }
    }
  }
}
```

```text
on tool call(args):
  runId = POST /v1/workflows/{id}/run { payload: args }      # → run_...
  loop:
    run = GET /v1/runs/{runId}?summary=true
    if run.status in {success, partial, failure, cancelled}: break
    sleep(1s)                                                 # or use the SSE stream
  full = GET /v1/runs/{runId}                                 # read executionState[node].result
  return summarize(full)
```

**Using Claude / MCP**

If your agent speaks MCP, the [B3OS MCP server](/platform/mcp-server) wraps these same endpoints as tools so Claude
can discover, run, and inspect workflows without hand-writing HTTP. The REST path on this page is the
framework-agnostic equivalent and the source of truth for scopes.

## What's supported today

**Your org's published workflows — fully supported**

Run, track, and read history for any `active` workflow in your org with a service-account key. This is the primary
path.

**Owner-published public workflows — anonymous run**

A workflow whose owner set visibility to `public_execute` can be triggered with no auth via `POST
/v1/public/workflows/:id/run`. The run is billed to the **owner's** org and uses the owner's connectors and wallets;
the caller only supplies a payload. The consumer cannot opt a workflow into this — only the owner can.

**Public templates — adopt, then run**

Library [templates](/platform/templates-blocks) are *definitions*, not runnable endpoints. To use one, read it (`GET
/v1/templates/:id`), create a workflow in your org from its `definition`, publish it, then run your copy. Your org
supplies the connectors and CU — credentials are never carried over from the template.

## Read next

**[Authentication](/api/authentication)**

API keys, scopes, service accounts, and organization context.

**[Runs and Executions](/api/reference/runs-executions)**

Exact request/response schemas for every run and execution endpoint.

**[Execution Hooks](/api/execution-hooks)**

Push signed run events to your backend instead of polling.