{"_id":"@canopy-ai/sdk","_rev":"5-314ad487d9ccbd4df2bf0b0271ccb839","name":"@canopy-ai/sdk","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.2":{"name":"@canopy-ai/sdk","version":"0.0.2","keywords":["canopy","agent","wallet","x402","ai","llm"],"license":"MIT","_id":"@canopy-ai/sdk@0.0.2","maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"homepage":"https://github.com/canopyio/sdk/tree/main/typescript","bugs":{"url":"https://github.com/canopyio/sdk/issues"},"dist":{"shasum":"af428a33840f035823dbaf0a2029d3111f44209a","tarball":"https://registry.npmjs.org/@canopy-ai/sdk/-/sdk-0.0.2.tgz","fileCount":16,"integrity":"sha512-MxCZbJh+wxsfGVVgXtFkwEqZyYJJFYoMTMtslGt5bFx0s1FVL3PGVonuzZVzzQEBVU6ufwii7cnyqY5IrkOsDg==","signatures":[{"sig":"MEUCIHzsM08+Nx4E64+pITBTeL0ycVJ0X+liR34cB+n3v0MNAiEA47bEFmDIKi2jTi39R+UpEf6J2p59V/Ww7QAdzUWN9FI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":242224},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./langchain":{"types":"./dist/langchain.d.ts","import":"./dist/langchain.js","require":"./dist/langchain.cjs"}},"gitHead":"1d1ad008138a2b136afd02a10aea02cc6eba4fb0","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"atifazam","email":"atif@dusk.so"},"repository":{"url":"git+https://github.com/canopyio/sdk.git","type":"git","directory":"typescript"},"_npmVersion":"10.9.4","description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","directories":{},"_nodeVersion":"22.22.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@langchain/core":"^0.3.80"},"peerDependencies":{"@langchain/core":"^0.3.0"},"peerDependenciesMeta":{"@langchain/core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.0.2_1777559386825_0.04917956628146891","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@canopy-ai/sdk","version":"0.0.3","keywords":["canopy","agent","wallet","x402","ai","llm"],"license":"MIT","_id":"@canopy-ai/sdk@0.0.3","maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"homepage":"https://github.com/canopyio/sdk/tree/main/typescript","bugs":{"url":"https://github.com/canopyio/sdk/issues"},"dist":{"shasum":"186569611e4f25b371bf25894ad38eb633d9ee9f","tarball":"https://registry.npmjs.org/@canopy-ai/sdk/-/sdk-0.0.3.tgz","fileCount":16,"integrity":"sha512-SEYNztX/LY6ruT8HVtDj48xlamPCV+3G9IxGpLftuy0lm03PJYHtYhgacucANLYrPjh52NqTMWGj2ijlIlWNog==","signatures":[{"sig":"MEYCIQDowet+J7jxoKFT+y9zJQSdHPrIIGxox6EnyAnX2500pAIhAN/UGg/3SJkM4+5gd2BhyMq4/9QGU+tSZ4egut9r6Elg","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":293877},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./langchain":{"types":"./dist/langchain.d.ts","import":"./dist/langchain.js","require":"./dist/langchain.cjs"}},"gitHead":"474dfc2b4bf538d8bcc49e46e8959cf79d204d17","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"atifazam","email":"atif@dusk.so"},"repository":{"url":"git+https://github.com/canopyio/sdk.git","type":"git","directory":"typescript"},"_npmVersion":"10.9.4","description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","directories":{},"_nodeVersion":"22.22.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@langchain/core":"^0.3.80"},"peerDependencies":{"@langchain/core":"^0.3.0"},"peerDependenciesMeta":{"@langchain/core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.0.3_1777601316259_0.7890612352742665","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@canopy-ai/sdk","version":"0.0.4","keywords":["canopy","agent","wallet","x402","ai","llm"],"license":"MIT","_id":"@canopy-ai/sdk@0.0.4","maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"homepage":"https://github.com/canopyio/sdk/tree/main/typescript","bugs":{"url":"https://github.com/canopyio/sdk/issues"},"bin":{"canopy-sdk":"dist/cli.js"},"dist":{"shasum":"3695b3eeb899a75f94ce9775ee7ac77efe96b89e","tarball":"https://registry.npmjs.org/@canopy-ai/sdk/-/sdk-0.0.4.tgz","fileCount":18,"integrity":"sha512-c+VzGFqB4HLr6tE+ae2GR9Xj671OYxgAva8Nk5+z21CTtVw7HWA9dMAOXfvH1yECePJ0Go1aRdLoOxPnFDjPmg==","signatures":[{"sig":"MEUCIF2B6v6DShGOFgHAD+c9t2nS8MsAP3nyO3iai3iEWkZ0AiEAkcpMAAq71Ds7LsCdlHDxfj8cpWFr6cdR146frRpJe2c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":373767},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./langchain":{"types":"./dist/langchain.d.ts","import":"./dist/langchain.js","require":"./dist/langchain.cjs"}},"gitHead":"3e8a20c29ce36883c4494a9ba87a3e7457b38da4","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"atifazam","email":"atif@dusk.so"},"repository":{"url":"git+https://github.com/canopyio/sdk.git","type":"git","directory":"typescript"},"_npmVersion":"10.9.4","description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","directories":{},"_nodeVersion":"22.22.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@langchain/core":"^0.3.80"},"peerDependencies":{"@langchain/core":"^0.3.0"},"peerDependenciesMeta":{"@langchain/core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.0.4_1777919219850_0.5036746587986185","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@canopy-ai/sdk","version":"0.0.5","keywords":["canopy","agent","wallet","x402","ai","llm"],"license":"MIT","_id":"@canopy-ai/sdk@0.0.5","maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"homepage":"https://github.com/canopyio/sdk/tree/main/typescript","bugs":{"url":"https://github.com/canopyio/sdk/issues"},"bin":{"canopy-sdk":"dist/cli.js"},"dist":{"shasum":"32c2af74f5d42b3139b9fb4449f42f8e1eccfaab","tarball":"https://registry.npmjs.org/@canopy-ai/sdk/-/sdk-0.0.5.tgz","fileCount":18,"integrity":"sha512-byWtYnfULnyttXPUqbLrEKMpfriHlOCCDpgePc7xGdzZ/4uB2cMkG9yGE4G5eVp1XboYCYuSJq1cZzMF6B47Tg==","signatures":[{"sig":"MEUCIEj6L+zpUgdXCWgOhCq+orxNl4yNGVKwJFm6pBE9h1IcAiEAtEDCHkkqhpadOZgR6rlNEbvkPr2VAU1SFiAvUiY+IC4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":420581},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./langchain":{"types":"./dist/langchain.d.ts","import":"./dist/langchain.js","require":"./dist/langchain.cjs"}},"gitHead":"80cf5f801fa3f35ae528a0c89f926f2588c2faf1","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"atifazam","email":"atif@dusk.so"},"repository":{"url":"git+https://github.com/canopyio/sdk.git","type":"git","directory":"typescript"},"_npmVersion":"10.9.4","description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"yaml":"^2.6.1","@iarna/toml":"^2.2.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.10.0","@langchain/core":"^0.3.80"},"peerDependencies":{"@langchain/core":"^0.3.0"},"peerDependenciesMeta":{"@langchain/core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.0.5_1778172690004_0.6787841428163564","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@canopy-ai/sdk","version":"0.0.6","description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/canopyio/sdk.git","directory":"typescript"},"homepage":"https://github.com/canopyio/sdk/tree/main/typescript","bugs":{"url":"https://github.com/canopyio/sdk/issues"},"publishConfig":{"access":"public"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"canopy-sdk":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./langchain":{"types":"./dist/langchain.d.ts","import":"./dist/langchain.js","require":"./dist/langchain.cjs"}},"dependencies":{"@iarna/toml":"^2.2.5","yaml":"^2.6.1"},"peerDependencies":{"@langchain/core":"^0.3.0"},"peerDependenciesMeta":{"@langchain/core":{"optional":true}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"engines":{"node":">=18"},"keywords":["canopy","agent","wallet","x402","ai","llm"],"devDependencies":{"@langchain/core":"^0.3.80","@types/node":"^22.10.0","tsup":"^8.3.5","typescript":"^5.7.0","vitest":"^2.1.0"},"_id":"@canopy-ai/sdk@0.0.6","gitHead":"d50f0ac560a216ca492dd190cbda978ed85a20db","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-UU/cwB9LrVLaeVxzVUu7jMAX/poFbWNX4Qg6DUvPV8kov2qHUkouFqIMnbRxU7LkMfQTAzVQNDXELq+o35JZgw==","shasum":"406ea80f91172b9a32008c52550ae9ca7ef3b890","tarball":"https://registry.npmjs.org/@canopy-ai/sdk/-/sdk-0.0.6.tgz","fileCount":18,"unpackedSize":429719,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCiastT0RWVolXoVlSEA1a2tnwnkfcWUWsZL8RH//Pk6wIhAIVlVTt6axH/Y1X0zTi8YdefogRJEbuilee0v91vlE/l"}]},"_npmUser":{"name":"atifazam","email":"atif@dusk.so"},"directories":{},"maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.0.6_1778240774017_0.04556548248464698"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-30T14:29:46.689Z","modified":"2026-05-08T11:46:14.329Z","0.0.2":"2026-04-30T14:29:47.016Z","0.0.3":"2026-05-01T02:08:36.413Z","0.0.4":"2026-05-04T18:27:00.006Z","0.0.5":"2026-05-07T16:51:30.187Z","0.0.6":"2026-05-08T11:46:14.188Z"},"bugs":{"url":"https://github.com/canopyio/sdk/issues"},"license":"MIT","homepage":"https://github.com/canopyio/sdk/tree/main/typescript","keywords":["canopy","agent","wallet","x402","ai","llm"],"repository":{"type":"git","url":"git+https://github.com/canopyio/sdk.git","directory":"typescript"},"description":"Client library for Canopy — org treasury wallets with agent-level policy-gated spending.","maintainers":[{"name":"atifazam","email":"atif@dusk.so"}],"readme":"# @canopy-ai/sdk\n\nTypeScript / Node.js client for [Canopy](https://trycanopy.ai). Give your AI agent a USDC treasury on Base and Tempo, gated by a policy you set in the dashboard. Speaks both x402 (Base) and MPP (Tempo) — the SDK picks the funded rail per call.\n\n```bash\nnpm install @canopy-ai/sdk\n```\n\nNode 18+. Ships ESM + CJS. Zero runtime dependencies.\n\n## Setup in 30 seconds\n\nAfter you've signed up at <https://trycanopy.ai> and added an agent, pick one path:\n\n**Fast — single command (also configures detected MCP clients):**\n\n```bash\nnpx @canopy-ai/sdk connect\n```\n\nOpens your browser, you confirm an agent, the CLI writes credentials to `~/.config/canopy/credentials` and merges a `canopy` server entry into Claude Code, Cursor, Claude Desktop, Windsurf, Cline, VS Code, and Zed if installed. Pass `--no-write` to print snippets instead.\n\n**Manual — copy/paste:**\n\n1. Dashboard → **Settings** → copy your org API key (`ak_live_…`).\n2. Dashboard → **Agents** → copy the agent's `agt_…` id.\n3. Drop both into your project's `.env`:\n\n```bash\nCANOPY_API_KEY=ak_live_xxxxxxxxxxxxxxxx\nCANOPY_AGENT_ID=agt_xxxxxxxx\n```\n\n## Hello world\n\n```ts\nimport { Canopy } from \"@canopy-ai/sdk\";\n\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: process.env.CANOPY_AGENT_ID!,\n});\n\nconst result = await canopy.pay({\n  to: \"0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97\", // any 0x… recipient\n  amountUsd: 0.10,\n});\n\nswitch (result.status) {\n  case \"allowed\":\n    console.log(\"paid:\", result.txHash);\n    break;\n  case \"pending_approval\":\n    // Three options here — see \"Human-in-the-loop approvals\" below:\n    //   1. tell the user (LLM picks a phrase using `result.recipientName`,\n    //      `result.amountUsd`, etc.) and call canopy.approve() / .deny()\n    //      when they reply\n    //   2. canopy.waitForApproval(result.approvalId) — block-poll\n    //   3. let it ride — agent moves on, dashboard handles decision\n    console.log(`Pending: $${result.amountUsd} to ${result.recipientName}`);\n    break;\n  case \"denied\":\n    console.log(\"denied:\", result.reason);\n    break;\n}\n```\n\n## Discover paid services at runtime\n\nDon't hardcode URLs. Let the agent find paid services it can call:\n\n```ts\nconst services = await canopy.discover({ category: \"data\", query: \"orderbook\" });\n// → [{ slug, name, description, category, paymentMethods, endpoints,\n//     preferredBaseUrl, policyAllowed, ... }]\n\nconst feed = services[0];\nif (feed?.policyAllowed && feed.preferredBaseUrl) {\n  const path = feed.endpoints[0]?.path ?? \"/\";\n  const data = await canopy.fetch(feed.preferredBaseUrl + path);\n  // 402 → auto-paid → 200 with content\n}\n```\n\n`discover()` queries Canopy's registry of x402-on-Base and MPP-on-Tempo services. The agent's policy filters the results by service slug — if the policy has an allowlist, only services on that list are returned. Set `includeBlocked: true` to see blocked services too (with `policyAllowed: false`). `preferredBaseUrl` is picked by treasury balance: the rail whose chain currently has positive USDC.\n\n### Quote a URL before paying it\n\n`canopy.check(url)` is the URL-driven counterpart to `preview()` — it probes the URL, parses the 402, runs the agent's policy in dry-run mode, and returns price + recipient + verdict, without signing. Use it as the second leg of a `discover → check → fetch` chain when the LLM should weigh the cost before committing:\n\n```ts\nconst quote = await canopy.check(serviceUrl);\nif (quote.status === \"denied\") return; // tell the user, no point trying\nif (quote.status === \"pending_approval\") return askForApproval(quote);\nconst res = await canopy.fetch(quote.resourceUrl); // safe to commit\n```\n\nReturns `{ status: \"allowed\" | \"pending_approval\" | \"denied\", rail, chainId, amountUsd, recipient: { address, slug, name }, resourceUrl, ... }`. Cached per (org, url) for 60 seconds so rapid-fire agent loops don't hammer provider gateways.\n\n## Human-in-the-loop approvals\n\nWhen the policy is configured with `approval_required: true`, payments above the threshold come back as `status: \"pending_approval\"` instead of settling. There are three places the human can decide. All hit the same backend, so any one of them resolves the approval:\n\n| Where | Best when |\n|---|---|\n| **Dashboard** (already built — `pending-approvals-section`, activity drawer) | Org admin already on the dashboard |\n| **In chat** — the LLM calls `canopy.approve(id)` / `canopy.deny(id)` when the user replies \"yes\" / \"no\" | The user is mid-conversation with the agent |\n| **`canopy.fetch(..., { waitForApproval: true })`** | The agent is auto-paying an x402 endpoint and wants to block until decided |\n\n### Chat-native (recommended for conversational agents)\n\n`getTools()` includes `canopy_approve` and `canopy_deny`. The LLM calls them when the user gives explicit consent in chat. Reads naturally:\n\n> **You:** Find a data feed and pull BTC depth.\n> **LLM:** *[calls `canopy_pay({ to: \"0x…Alchemy\", amountUsd: 5 })`]*\n> *[returns `{ status: \"pending_approval\", approvalId: \"ar_x9\", recipientName: \"Alchemy\", amountUsd: 5 }`]*\n> **LLM:** I'd like to pay $5 to Alchemy for compute. Reply 'approve' or 'deny'.\n> **You:** approve\n> **LLM:** *[calls `canopy_approve({ approval_id: \"ar_x9\" })`]*\n> **LLM:** Approved — sent. tx 0x123… on Base.\n\nThe pending result carries everything the LLM needs to phrase the question — `recipientName`, `amountUsd`, `agentName`, `expiresAt`. No follow-up call needed.\n\nTo turn this off, uncheck \"Allow approval from chat\" in the policy. Then `canopy.approve()` throws `CanopyChatApprovalDisabledError` and the LLM should redirect the user to the dashboard.\n\n### Block-and-retry on `fetch()`\n\nIf your agent calls `canopy.fetch(url)` against an x402 endpoint and the policy gates it, the default behavior is to throw `CanopyApprovalRequiredError`. To wait instead:\n\n```ts\nconst res = await canopy.fetch(\"https://paid-api.example.com/generate\", undefined, {\n  waitForApproval: 60_000, // ms; or `true` for default 5 min\n});\n// On approve: SDK retries the URL with the recovered X-PAYMENT header.\n// On deny / expiry: throws CanopyApprovalDeniedError / CanopyApprovalExpiredError.\n```\n\n### Manual polling\n\n`canopy.waitForApproval(approvalId)` polls every 2 seconds (default 5-min timeout) and returns when the status leaves `pending`. `canopy.getApprovalStatus(approvalId)` is the one-shot version if you want to drive the polling yourself.\n\n## Plug Canopy into your agent\n\n**Already running an MCP-aware agent?** Skip this section — paste `https://mcp.trycanopy.ai/mcp` into the host's Custom Connectors or `mcpServers` config and your agent gets all ten canonical Canopy tools through MCP. That's the right path for claude.ai, ChatGPT, Claude Agent SDK, Claude Desktop, Cursor, VS Code, Zed, Cline, Windsurf, and any other MCP host. The native adapters below are for direct LLM-API flows where MCP isn't a fit (backend scripts, x402 auto-paying via `canopy.fetch()`, raw `chat.completions.create` / `messages.create` loops, edge runtimes).\n\n| Framework | Helper | Lines of glue |\n|---|---|---|\n| MCP host (claude.ai, ChatGPT, Claude Desktop, Cursor, VS Code, Zed, Cline, Windsurf) | paste `https://mcp.trycanopy.ai/mcp` | 0 |\n| Claude Agent SDK | Remote MCP + `allowedTools: [\"mcp__canopy__*\"]` | 0 Canopy code |\n| Vercel AI SDK (v3+) | `canopy.vercel.tools()` | 1 |\n| OpenAI Chat Completions / Responses | `canopy.openai.tools()` + `canopy.openai.dispatch()` | 2 |\n| Anthropic Messages | `canopy.anthropic.tools()` + `canopy.anthropic.dispatch()` | 2 |\n| LangChain JS (v0.3+) | `import { toLangChainTools } from \"@canopy-ai/sdk/langchain\"` | 1 |\n\n`canopy.getTools()` is still available as the canonical, framework-agnostic shape (`{ name, description, parameters: JSONSchema, execute }[]`) for any framework not listed.\n\nClaude Agent SDK uses MCP for external tools — prefer the remote MCP URL over the Anthropic Messages adapter there. `canopy.anthropic` is for direct `@anthropic-ai/sdk` Messages API loops.\n\n### Vercel AI SDK\n\n```ts\nimport { generateText } from \"ai\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { Canopy } from \"@canopy-ai/sdk\";\n\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: process.env.CANOPY_AGENT_ID!,\n});\n\nconst { text } = await generateText({\n  model: openai(\"gpt-4o\"),\n  tools: canopy.vercel.tools(),\n  prompt: \"Find me an orderbook feed and pull BTC depth.\",\n});\n```\n\n### OpenAI (Chat Completions)\n\n`canopy.openai.tools()` returns the `[{ type: \"function\", function: { ... } }]` shape OpenAI expects. `canopy.openai.dispatch(toolCalls)` runs them and returns tool messages already shaped for the next turn.\n\n```ts\nimport OpenAI from \"openai\";\nimport { Canopy } from \"@canopy-ai/sdk\";\n\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: process.env.CANOPY_AGENT_ID!,\n});\nconst openai = new OpenAI();\n\nconst messages: OpenAI.ChatCompletionMessageParam[] = [\n  { role: \"user\", content: \"Find data feeds I can pay for and use the cheapest.\" },\n];\n\nconst completion = await openai.chat.completions.create({\n  model: \"gpt-4o\",\n  messages,\n  tools: canopy.openai.tools(),\n});\n\nconst toolMessages = await canopy.openai.dispatch(\n  completion.choices[0].message.tool_calls,\n);\nif (toolMessages.length) {\n  messages.push(completion.choices[0].message);\n  messages.push(...toolMessages);\n  // Loop back into chat.completions.create with the updated messages.\n}\n```\n\n`dispatch` skips tool calls that aren't Canopy's (the host loop dispatches those) and embeds errors as `{ error }` JSON in the tool message so the LLM can react instead of crashing the loop. Pending-approval results land in the tool message with `recipientName`, `amountUsd`, `expiresAt`, `chatApprovalEnabled` — the LLM can ask the user and call `canopy_approve` / `canopy_deny` next turn.\n\n### Anthropic (Messages)\n\nSame pattern, Anthropic-shaped: `canopy.anthropic.tools()` produces `[{ name, description, input_schema }]`. `canopy.anthropic.dispatch(content)` consumes assistant content blocks and returns `tool_result` blocks ready to wrap in a user message.\n\n```ts\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { Canopy } from \"@canopy-ai/sdk\";\n\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: process.env.CANOPY_AGENT_ID!,\n});\nconst client = new Anthropic();\n\nconst messages: Anthropic.MessageParam[] = [\n  { role: \"user\", content: \"Discover x402 data feeds and pay for one.\" },\n];\n\nconst reply = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 1024,\n  tools: canopy.anthropic.tools(),\n  messages,\n});\n\nconst toolResults = await canopy.anthropic.dispatch(reply.content);\nif (toolResults.length) {\n  messages.push({ role: \"assistant\", content: reply.content });\n  messages.push({ role: \"user\", content: toolResults });\n  // Loop back into messages.create with the updated messages.\n}\n```\n\n### LangChain\n\n```ts\nimport { Canopy } from \"@canopy-ai/sdk\";\nimport { toLangChainTools } from \"@canopy-ai/sdk/langchain\";\n\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: process.env.CANOPY_AGENT_ID!,\n});\n\nconst lcTools = toLangChainTools(canopy);  // DynamicStructuredTool[]\n```\n\n`@canopy-ai/sdk/langchain` is a subpath import — `@langchain/core` is an optional peer dep, so installs that don't use LangChain don't pay for it.\n\n### Pay paywalled APIs (x402)\n\n`canopy.fetch()` is a drop-in replacement for global `fetch` that auto-pays [x402](https://x402.org) endpoints:\n\n```ts\nconst res = await canopy.fetch(\"https://paid-api.example.com/generate-image\");\n// On HTTP 402, Canopy signs the payment and retries. You see the eventual 200.\n```\n\nSubject to the same agent policy as `pay()`. Non-402 responses pass through untouched.\n\n## Reference\n\n### `new Canopy(config)`\n\n```ts\nnew Canopy({\n  apiKey: string;          // required\n  agentId?: string;        // required for pay/preview/fetch/discover/ping/budget\n  baseUrl?: string;        // default: https://trycanopy.ai\n})\n```\n\n### `canopy.pay({ to, amountUsd, idempotencyKey?, chainId? })`\n\nIssues a payment. Returns a discriminated union — never throws on policy outcomes:\n\n```ts\ntype PayResult =\n  | { status: \"allowed\"; txHash: string | null; signature: string | null;\n      transactionId: string | null; costUsd: number | null;\n      idempotent?: boolean; dryRun?: boolean; }\n  | { status: \"pending_approval\"; approvalId: string; transactionId: string;\n      reason: string;\n      recipientName: string | null;   // resolved from registry — \"Alchemy\" etc.\n      recipientAddress: string | null;\n      amountUsd: number | null;\n      agentName: string | null;\n      expiresAt: string | null;       // ISO; auto-cancelled after this\n      chatApprovalEnabled: boolean;   // false → canopy.approve() throws\n    }\n  | { status: \"denied\"; reason: string; transactionId: string; };\n```\n\n- **`to`**: a `0x…` recipient address. For paid-service interactions, use `canopy.fetch(serviceUrl)` — `pay()` is for direct transfers.\n- **`amountUsd`**: USD as a number (e.g. `0.10` for ten cents).\n- **`idempotencyKey`** *(optional)*: pass a stable string for retries you don't fully control (webhooks, framework retries). Same `(agentId, idempotencyKey)` returns the cached result with `idempotent: true`.\n\n### `canopy.preview({ to, amountUsd })`\n\nSame shape and return as `pay()`, but evaluates the policy without signing or persisting. Use it to ask \"would this go through?\" before committing.\n\n### `canopy.check(url)`\n\nURL-driven counterpart to `preview()`. Probes the URL, parses the 402 (x402 or MPP), runs the agent's policy in dry-run mode, and returns the parsed offer plus an `allowed` / `pending_approval` / `denied` verdict — without signing.\n\n```ts\ntype CheckResult =\n  | { status: \"allowed\";          /* …offer fields… */ }\n  | { status: \"pending_approval\"; reason: string; approvalThresholdUsd: number | null; /* …offer… */ }\n  | { status: \"denied\";           reason: string; /* …offer… */ };\n\n// Offer fields present on every variant:\n//   rail: \"x402\" | \"mpp\"\n//   chainId: number\n//   amountUsd: number\n//   recipient: { address, slug, name }\n//   resourceUrl: string\n//   scheme: string | null     // x402 only\n//   network: string           // \"base\", \"tempo\", \"eip155:8453\", …\n//   realm: string | null      // mpp only\n//   cached: boolean           // true when served from the 60s probe cache\n```\n\nServer-side probe — Canopy's egress, not yours. Cached per (org, url) for 60 seconds.\n\n### `canopy.fetch(url, init?, opts?)`\n\nLike global `fetch`, but auto-pays HTTP 402 responses per the x402 spec. Same agent policy applies.\n\n```ts\nconst res = await canopy.fetch(url, init, {\n  waitForApproval: 60_000,  // ms, or `true` for default 5 min\n                            // omit/false (default): throws CanopyApprovalRequiredError on pending\n});\n```\n\nWithout `waitForApproval`, a payment that goes pending throws a typed error you can catch and handle yourself.\n\n### `canopy.discover(opts?)`\n\nFind paid services the agent can call (x402-on-Base + MPP-on-Tempo).\n\n```ts\nconst services = await canopy.discover({\n  category: \"data\",         // optional, e.g. \"data\", \"api\", \"compute\"\n  query: \"orderbook\",       // optional free-text match\n  limit: 20,                // optional, default 20, capped at 50\n  includeBlocked: false,    // include policy-blocked services with policyAllowed=false\n  includeUnverified: false, // include long-tail unverified entries\n});\n// → DiscoveredService[]: { slug, name, description, category, logoUrl, docsUrl,\n//                          paymentMethods: [{ realm, baseUrl, protocol }],\n//                          endpoints: [{ method, path, description, priceAtomic,\n//                                        currency, pricingModel, protocol }],\n//                          preferredBaseUrl, policyAllowed }\n```\n\nWhen the agent's policy has an allowlist, results are filtered to allowed services (by slug) by default. Pass `includeBlocked: true` to see blocked services too (each marked `policyAllowed: false`) — useful when you want the LLM to reason about why something isn't available. `preferredBaseUrl` is picked by treasury funding: the rail whose chain currently has positive USDC. Concatenate it with an endpoint `path` and pass the result to `canopy.fetch()`.\n\n### `canopy.ping()`\n\nHealth check. Confirms the API key + agent are valid and returns a structured snapshot:\n\n```ts\nconst ping = await canopy.ping();\n// { ok: true,\n//   agent: { id, name, status, policyId, policyName },\n//   org:   { name, treasuryAddress },\n//   latencyMs }\n```\n\nRun on app startup to fail-fast on bad config.\n\n### `canopy.budget()`\n\nPre-flight cap snapshot. Useful for LLM planning (\"I have $4.30 left, defer the expensive call\"):\n\n```ts\nconst b = await canopy.budget();\n// { agentId, capUsd, spentUsd, remainingUsd, periodHours, periodResetsAt }\n```\n\n`capUsd` and `remainingUsd` are `null` when no policy is bound.\n\n### `canopy.approve(approvalId)` / `canopy.deny(approvalId)`\n\nMark a pending approval decided. Call from agent code when the user gives explicit consent in chat (`approve` for \"yes\", `deny` for \"no\"). The org's policy must have `chat_approval_enabled = true` (default true), or these throw `CanopyChatApprovalDisabledError` and the LLM should redirect the user to the dashboard.\n\n```ts\nconst result = await canopy.pay({ to: \"0x…\", amountUsd: 5 });\nif (result.status === \"pending_approval\") {\n  // ...the LLM asks the user, the user replies \"approve\", the LLM calls:\n  const decided = await canopy.approve(result.approvalId);\n  // decided: { decision, transactionId, txHash, signature }\n}\n```\n\n### `canopy.waitForApproval(approvalId, opts?)`\n\nPolls until the approval leaves `pending` or the timeout elapses (default 5 min, 2s polling). Use when the agent should block on the human deciding via the dashboard or chat.\n\n```ts\nconst decided = await canopy.waitForApproval(result.approvalId, {\n  timeoutMs: 60_000,\n  pollIntervalMs: 1_000,\n});\n// decided.status: \"approved\" | \"denied\" | \"expired\"\n// decided.xPaymentHeader is populated for x402 transactions on approve\n```\n\nThrows `CanopyApprovalTimeoutError` on timeout.\n\n### `canopy.getApprovalStatus(approvalId)`\n\nOne-shot read of the same status. Use this when you want to poll on your own cadence.\n\n### `canopy.getTools()`\n\nReturns the canonical tool list as `CanopyTool[]`:\n\n```ts\ntype CanopyTool = {\n  name: string;            // \"canopy_pay\" | \"canopy_discover_services\" | \"canopy_approve\" | \"canopy_deny\"\n  description: string;\n  parameters: Record<string, unknown>;  // JSON Schema\n  execute: (args: any) => Promise<unknown>;\n};\n```\n\nFive tools by default:\n\n| Tool | Purpose |\n|---|---|\n| `canopy_pay` | Send a payment from the org treasury, gated by the agent's policy. |\n| `canopy_check_url` | Quote a paywalled URL (price + recipient + verdict) without spending. |\n| `canopy_discover_services` | Find paid services the agent can call (x402 on Base, MPP on Tempo). |\n| `canopy_approve` | Mark a pending approval approved. The LLM calls this when the user replies \"yes\" / \"approve\". |\n| `canopy_deny` | Mark a pending approval denied. The LLM calls this when the user replies \"no\" / \"cancel\". |\n\nFilter the array if you only want a subset:\n\n```ts\nconst payOnly = canopy.getTools().filter((t) => t.name === \"canopy_pay\");\n```\n\n## Errors\n\nHTTP and network errors throw. Policy outcomes (`denied`, `pending_approval`) are return values.\n\n| Error | When | Useful field |\n|---|---|---|\n| `CanopyConfigError` | Missing `apiKey`, missing `agentId`, etc. | `dashboardUrl` (jump to the page that fixes it) |\n| `CanopyApiError` | Server returned an unexpected status | `status`, `body`, `dashboardUrl` |\n| `CanopyNetworkError` | DNS / TLS / timeout | `cause` |\n| `CanopyApprovalTimeoutError` | `waitForApproval` exhausted its timeout | `approvalId` |\n| `CanopyApprovalRequiredError` | `canopy.fetch()` hit a payment that needs approval and `waitForApproval` was off | `approvalId`, `recipientName`, `amountUsd`, `agentName`, `expiresAt`, `chatApprovalEnabled` |\n| `CanopyApprovalDeniedError` | The user denied while `waitForApproval` was blocking | `approvalId`, `transactionId` |\n| `CanopyApprovalExpiredError` | The approval expired (24h default) before a decision | `approvalId`, `transactionId` |\n| `CanopyChatApprovalDisabledError` | `canopy.approve()` / `.deny()` against a policy with `chat_approval_enabled=false` | `approvalId` |\n\nAll inherit from `CanopyError`. Most actionable errors include a `dashboardUrl` field pointing at the page that fixes them — the message includes the URL inline too.\n\n```ts\nimport { CanopyError, CanopyApiError } from \"@canopy-ai/sdk\";\n\ntry {\n  await canopy.pay({ to, amountUsd });\n} catch (err) {\n  if (err instanceof CanopyApiError && err.status === 401) {\n    console.error(\"Bad API key. Open:\", err.dashboardUrl);\n  } else if (err instanceof CanopyError) {\n    console.error(\"Canopy:\", err.message);\n  } else {\n    throw err;\n  }\n}\n```\n\n## Local development\n\nPoint at a locally-running canopy-app:\n\n```ts\nconst canopy = new Canopy({\n  apiKey: process.env.CANOPY_API_KEY!,\n  agentId: \"agt_…\",\n  baseUrl: \"http://localhost:3000\",\n});\n```\n\nUse a test-mode key (`ak_test_…`) so prod data stays clean.\n\n## Troubleshooting\n\n- **`401 Invalid API key`** — regenerate in Dashboard → Settings.\n- **`agentId is required for pay()`** — pass `agentId` to the constructor or set `CANOPY_AGENT_ID`.\n- **`denied: Recipient is not in the allowlist`** — edit the agent's policy to add the recipient, or pick a different one. `discover()` will respect the same allowlist.\n- **`denied: Spend cap exceeded`** — wait out the cap window or raise it in the dashboard. Run `canopy.budget()` to see remaining headroom.\n- **`pending_approval` and your script just sits there** — for chat agents, surface `result.recipientName` / `result.amountUsd` to the user and call `canopy.approve(id)` / `.deny(id)` when they reply. For scripted agents, call `canopy.waitForApproval(id)` to block, or `getApprovalStatus(id)` to poll on your own cadence.\n- **`CanopyChatApprovalDisabledError` when calling `approve()`** — the agent's policy has chat-based approval turned off. The user must approve in the dashboard.\n- **`discover()` returns an empty array** — the registry might not have services in that category yet. Try without `category`, or pass `includeUnverified: true` to see the long tail.\n\n## Version\n\n`0.0.1` — alpha. Wire format is stable; small refinements possible before `1.0`.\n\n## License\n\n[MIT](../LICENSE)\n","readmeFilename":"README.md"}