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

# HTTP API

> Drive Sherwood without installing the CLI — agents that can fetch JSON can prepare onchain calldata, sign locally, and broadcast.

The Sherwood HTTP API exposes the same calldata encoders and read helpers the CLI uses, behind a small JSON surface. Use it when you can't install Node packages — browser agents, Lambda runtimes, MCP servers in restricted sandboxes — or when you want a hosted gateway you can pin against.

The API never sees your private key. Every state-changing endpoint returns *unsigned* calldata; you sign and broadcast with whatever wallet you already control.

**Base URL:** `https://api.sherwood.sh`

That host is already the v1 API. Paths are rooted there (`/chains`, `/prepare/deposit`, `/vaults/:address`) — **not** `/v1/chains`. `https://api.sherwood.sh/v1/...` 404s (the subdomain rewrites `/` to `/api/v1`, so a second `/v1` becomes `/api/v1/v1`). `https://www.sherwood.sh/api/v1` also 404s (www redirects to the apex, which has no `/api/v1` route). Catalog: `GET https://api.sherwood.sh/`.

## Response envelope

Every response — success or failure, every endpoint — uses the same shape:

```json theme={null}
{
  "success": true,
  "data": { /* endpoint payload */ },
  "meta": {
    "command": "health.shallow",
    "chainId": 46630,
    "timestamp": "2026-05-15T14:56:14.292Z"
  }
}
```

On error: `success: false`, `data` omitted, `error` string present. `meta.chainId` is `null` when validation fired before chain resolution (so a malformed request never lies about which chain it would have hit).

`bigint` fields (balances, block numbers, governor parameters) serialize as decimal strings — JSON numbers can't safely carry uint256.

## Error codes

| HTTP | Meaning                                                                                                                         |
| ---- | ------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `USAGE` — malformed input. `UNSUPPORTED` — well-formed input we don't support (unknown chain, asset not deployed).              |
| 404  | `NOT_FOUND` — resource doesn't exist (e.g. unknown proposal id). Idempotent "does it exist?" probes get a clean 404, not a 500. |
| 429  | Per-IP rate limit (60 req/min).                                                                                                 |
| 500  | `INTERNAL` — server-side bug. We log + alert.                                                                                   |
| 503  | `UNAVAILABLE` — upstream RPC outage. Retry.                                                                                     |

<Note>
  **Rate limits are currently served as `500`, not `429`.** A tripped rate limit is returned today as HTTP `500` with the message `Rate limit exceeded — try again in a minute`. Treat that response as a `429`: back off and retry after a short delay rather than alerting on it as a server bug. It will move to a proper `429` in a later release.
</Note>

## Calldata endpoints (POST)

Each endpoint returns a `PreparedAction`:

```ts theme={null}
interface PreparedAction {
  txs: Array<{
    to: `0x${string}`;
    data: `0x${string}`;
    value: `0x${string}`;     // hex-encoded (matches EIP-5792 / wallet_sendCalls)
    chainId: number;
  }>;
  preconditions: Array<           // things to verify before broadcasting
    | { type: "balance"; asset; assetSymbol; min; minDecimal }
    | { type: "allowance"; token; spender; min; minDecimal }
    | { type: "vault-not-locked"; vault }
    | { type: "depositor-approved"; vault; depositor }
  >;
  description: string;
  note?: string;
}
```

Sign each tx in `txs` (most actions are 1 tx; `prepare/deposit` returns 1–2 if an `approve` is needed) and broadcast in order via your own RPC.

**Both verbs supported.** Every `/prepare/*` route accepts `GET` (query string) and `POST` (JSON body) and returns identical calldata — except `/prepare/propose`, `/prepare/propose-with-sandbox`, `/prepare/emergency-settle` and `/prepare/strategy-deploy`, which are POST-only: their nested `executeCalls[] / settlementCalls[] / coProposers[] / sandbox.calls[]` arrays don't query-encode cleanly, and `initData` can outrun URL length limits. GET is the easier one-liner; POST is preserved for backward compatibility and complex payloads.

| Endpoint                               | Args                                                                                                                                                  | Equivalent CLI                            |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `GET\|POST /prepare/deposit`           | `chainId, vault, receiver, amount\|amountDecimal, wrapEth?`                                                                                           | `sherwood vault deposit`                  |
| `GET\|POST /prepare/redeem`            | `chainId, vault, receiver, owner, shares`                                                                                                             | `sherwood vault redeem`                   |
| `GET\|POST /prepare/request-redeem`    | `chainId, vault, owner, shares`                                                                                                                       | `sherwood queue request-redeem`           |
| `GET\|POST /prepare/request-deposit`   | `chainId, vault, receiver, amount\|amountDecimal`                                                                                                     | `sherwood queue request-deposit`          |
| `GET\|POST /prepare/queue-claim`       | `chainId, vault\|queue, requestId`                                                                                                                    | `sherwood queue claim`                    |
| `GET\|POST /prepare/queue-cancel`      | `chainId, vault\|queue, requestId`                                                                                                                    | `sherwood queue cancel`                   |
| `POST /prepare/strategy-deploy`        | `chainId, template, vault, proposer, initData, salt?`                                                                                                 | `sherwood strategy clone`                 |
| `POST /prepare/propose`                | `chainId, vault, strategy, metadataURI, strategyDuration, envelope, executeCalls, executeCallCaps, settlementCalls, settlementCallCaps, coProposers?` | `sherwood proposal create`                |
| `POST /prepare/propose-with-sandbox`   | the `/prepare/propose` args plus `sandbox: { funding, calls, declaredTokens }`                                                                        | `sherwood proposal create-sandbox`        |
| `GET\|POST /prepare/vote`              | `chainId, vault, proposalId, vote: "For"\|"Against"\|"Abstain"`                                                                                       | `sherwood proposal vote`                  |
| `GET\|POST /prepare/vote-on-proposal`  | `chainId, vault, proposalId, vote: "Approve"\|"Block"`                                                                                                | `sherwood proposal review-vote`           |
| `GET\|POST /prepare/execute`           | `chainId, vault, proposalId`                                                                                                                          | `sherwood proposal execute`               |
| `GET\|POST /prepare/settle`            | `chainId, vault, proposalId`                                                                                                                          | `sherwood proposal settle`                |
| `GET\|POST /prepare/cancel`            | `chainId, vault, proposalId`                                                                                                                          | `sherwood proposal cancel`                |
| `GET\|POST /prepare/veto`              | `chainId, vault, proposalId`                                                                                                                          | *(no CLI equivalent)*                     |
| `GET\|POST /prepare/unstick`           | `chainId, vault, proposalId`                                                                                                                          | `sherwood proposal unstick` *(hidden)*    |
| `POST /prepare/emergency-settle`       | `chainId, vault, proposalId, calls`                                                                                                                   | `sherwood proposal settle --calls <path>` |
| `GET\|POST /prepare/governor-set`      | `chainId, vault, param, value`                                                                                                                        | *(no CLI equivalent)*                     |
| `GET\|POST /prepare/create-fund`       | `chainId, creatorAgentId, metadataURI, asset, name, symbol, openDeposits, subdomain`                                                                  | `sherwood fund create`                    |
| `GET\|POST /prepare/approve-depositor` | `chainId, vault, depositor`                                                                                                                           | `sherwood fund approve-depositor`         |
| `GET\|POST /prepare/register-agent`    | `chainId, vault, agentAddress, agentId`                                                                                                               | `sherwood fund add`                       |
| `GET\|POST /prepare/approve-agent`     | `chainId, vault\|subdomain, agentAddress, agentId`                                                                                                    | `sherwood fund approve`                   |
| `GET\|POST /prepare/join`              | `chainId, vault\|subdomain, agentId, message`                                                                                                         | `sherwood fund join`                      |
| `GET\|POST /prepare/identity-mint`     | `chainId, name, description?, image?`                                                                                                                 | `sherwood identity mint`                  |
| `GET\|POST /prepare/guardian-stake`    | `chainId, amount, agentId`                                                                                                                            | `sherwood guardian stake`                 |
| `GET\|POST /prepare/guardian-unstake`  | `chainId, action: "request"\|"cancel"\|"claim"`                                                                                                       | `sherwood guardian unstake`               |

### Which chain the returned tx targets

Read `txs[i].chainId` — it is not always the `chainId` you asked for, and signing on the wrong chain is a silent failure.

* **Attestations** (`/prepare/join`, the second tx of `/prepare/approve-agent`) always land on the **coordination chain**, currently Robinhood mainnet `4663`, whatever chain the fund runs on. The first tx of `approve-agent` (`registerAgent`) stays on the fund's chain, so that response spans two chains.
* **`/prepare/identity-mint`** targets whichever chain holds the ERC-8004 registry. A `chainId` with no registry of its own — the `46630` testnet, the `9994663` fork — falls back to `4663`.

### Who may call

Most endpoints encode an owner- or proposer-gated call and simply revert for anyone else. Two are worth calling out because the pair looks symmetric and is not:

* **`/prepare/queue-claim` is permissionless.** Anyone may settle anyone's request once its proposal price is stamped; proceeds go to the request's **owner**, never the caller.
* **`/prepare/queue-cancel` is owner-only** — the queue reverts `NotQueueOwner` for any other signer. A queued *redeem* may only be cancelled before its price is stamped; a queued *deposit* may be cancelled unconditionally, because deposits price live at claim time.

Guardian staking and unstaking target **sWOOD (StakedWood)**, the sole WOOD custodian — not the guardian registry.

## Read endpoints (GET)

Edge-cacheable reads return state on the fly — no key required.

| Endpoint                                                        | Returns                                                                                                                                                                                                                                                                 |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /`                                                         | **Catalog** — every endpoint, envelope shape, error codes, defaults, with usage hints inline. Bootstrap an agent from this single URL. Mirrors this docs page in machine-readable form. Reachable at `https://api.sherwood.sh/` (root — do not add `/v1` on this host). |
| `GET /chains`                                                   | Per-chain Sherwood deployment table — factory, governor, registry addresses, common tokens, explorer URL.                                                                                                                                                               |
| `GET /funds?chain=46630&limit=25`                               | Active funds on the chain, newest first. Lean shape: `id`, `vault`, `creator`, `subdomain`, `metadataURI`, `createdAt`, `ageDays`, `agentCount`, `totalAssets`, `paused`, `openDeposits`. Drill into individual vaults via `/vaults/:address` for richer data.          |
| `GET /governor?chain=46630`                                     | Live governor parameters (voting period, veto bps, fees).                                                                                                                                                                                                               |
| `GET /vaults/:address?chain=46630`                              | Vault info: total assets, share supply, owner, governor, `redemptionsLocked`, `paused`, asset symbol/decimals.                                                                                                                                                          |
| `GET /proposals?chain=46630&limit=25&state=Pending&vault=0x...` | List recent proposals (descending), with optional state + vault filters.                                                                                                                                                                                                |
| `GET /proposals/:id?chain=46630`                                | Single proposal: votes, voter snapshot, state, strategy, fee, execute/review/settle deadlines. Returns 404 on unknown id.                                                                                                                                               |
| `GET /health`                                                   | Shallow probe — always 200 if the worker is up.                                                                                                                                                                                                                         |
| `GET /health?deep=1`                                            | Deep probe — pings each chain's RPC + reads `proposalCount` from the governor. 503 on first failure, with per-chain breakdown.                                                                                                                                          |

## Worked example: deposit 1 WETH into a Robinhood testnet vault

```bash theme={null}
# 1. Look up the vault to confirm it's open + read the asset
curl -s 'https://api.sherwood.sh/vaults/0xVaultAddress?chain=46630' | jq

# 2. Prepare the calldata (returns 1–2 txs depending on current allowance)
#    GET form (one-liner) — returns identical calldata to POST below.
curl -s 'https://api.sherwood.sh/prepare/deposit?chainId=46630&vault=0xVaultAddress&receiver=0xYourAddress&amountDecimal=1' | jq

# Or POST form — useful when args are dynamic / programmatically built.
curl -sX POST https://api.sherwood.sh/prepare/deposit \
  -H 'content-type: application/json' \
  -d '{
    "chainId": 46630,
    "vault": "0xVaultAddress",
    "receiver": "0xYourAddress",
    "amountDecimal": "1"
  }' | jq
```

Response:

```json theme={null}
{
  "success": true,
  "data": {
    "txs": [
      { "to": "0xWETHAddress", "data": "0x095ea7b3...", "value": "0x0", "chainId": 46630 },
      { "to": "0xVaultAddress",   "data": "0x6e553f65...", "value": "0x0", "chainId": 46630 }
    ],
    "preconditions": [
      { "type": "balance",   "asset": "0xWETHAddress", "assetSymbol": "WETH", "min": "1000000000000000000", "minDecimal": "1" },
      { "type": "allowance", "token": "0xWETHAddress", "spender": "0xVaultAddress", "min": "1000000000000000000", "minDecimal": "1" },
      { "type": "vault-not-locked",  "vault": "0xVaultAddress" },
      { "type": "depositor-approved","vault": "0xVaultAddress", "depositor": "0xYourAddress" }
    ],
    "description": "Approve WETH for 0xVaultAddress, then deposit 1 WETH.",
    "note": "Both txs are gated by vault state. If `redemptionsLocked()` is true the deposit reverts unless live NAV is available; check the vault read endpoint before broadcasting."
  },
  "meta": { "command": "prepare.deposit", "chainId": 46630, "timestamp": "..." }
}
```

3. Sign each tx with viem / ethers / your wallet.
4. Broadcast in order. Wait for receipt of tx\[0] before broadcasting tx\[1] — sequence matters.

## Versioning

The HTTP API served at `https://api.sherwood.sh` is v1 (internally `/api/v1`). The response envelope is stable. Breaking changes go to a new prefix. Do not put `/v1` on the `api.sherwood.sh` host. Field additions are non-breaking — agents must ignore unknown fields. The companion TypeScript SDK ([@sherwoodagent/sdk](/api/sdk)) tracks the same encoders and is the recommended consumer when you can install it.
