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

# SDKs and tools

One key, one SDK, one CLI and one MCP server for every B3OS product, with method names generated from the OpenAPI document.

Every B3OS product is reachable through the same four clients, all with the same key:

| Client | Install | Use it for |
|---|---|---|
| [TypeScript SDK](/sdks/typescript) | `npm install @b3os/sdk` | Code on Node 20.16 or later, Bun and Cloudflare Workers |
| [Python SDK](/sdks/python) | `pip install b3os` | Python 3.10 or later, sync or async |
| [CLI](/sdks/cli) | `npx b3os` | Your terminal, scripts and CI |
| [MCP server](/sdks/mcp) | `npx -y @b3os/mcp` locally, or `https://beta-api.b3os.org/mcp` | Agents in Claude, Cursor, Codex and other MCP clients |

All of them read your key from `B3OS_API_KEY`. Inside a B3OS sandbox they find `B3OS_API_URL` (`http://127.0.0.1:7700`) and call the node directly, with no key.

## One naming rule

The SDKs are generated from the OpenAPI document, so their names follow the API exactly. Each operation belongs to a namespace (the product, or for account operations `keys`, `usage`, `limits`, `requests`, `webhooks`, `orgs` or `projects`) and its method is the operation ID without the product prefix, starting lowercase:

| Operation ID | SDK method |
|---|---|
| `visionScrape` | `vision.scrape` |
| `sandboxesCreateSandbox` | `sandboxes.createSandbox` |
| `routinesCreateRun` | `routines.createRun` |
| `platformListRequests` | `requests.listRequests` |

The Python SDK uses the same namespaces with snake_case methods (`sandboxes.create_sandbox`). Compatibility paths (Firecrawl, and OpenAI's `/v1/infer` beyond these methods) are meant for their upstream SDKs, and dashboard-only operations are not in the SDKs.

The SDKs, CLI and MCP server are version 0.1.0, built against the v0.1 API contract. The examples on this site are type-checked against the SDKs' source. Option names can still change before the packages are published.

## Shared behavior

- **Jobs.** Operations that start a job (crawls, batches, videos, routine runs) can wait for the finished job or stream its events: `.wait()` and `.events()` in TypeScript, `wait=True` and `stream=True` in Python. Event streams resume from the last event seen if the connection drops.
- **Errors.** Failures raise `B3OSError`, carrying the problem's `code`, `status`, `type` and the request ID. Failures no server reported, such as a refused connection or a client timeout, carry client codes of their own.
- **Retries.** Requests the server refused before doing any work (`429`, `503` unavailable) are retried with backoff; other retryable `5xx` answers only when the call is safe to repeat. Operations that take an idempotency key get one per call, reused by every retry.
- **Timing.** Every result's response carries the parsed `Server-Timing` phases.
- **Account operations from a sandbox.** Keys, usage, limits, organizations, projects and the pack registry live at `beta-api.b3os.org` only; from inside a sandbox the SDK reports `edge_only` and says so.

## Method index

| Method | Operation | Notes |
|---|---|---|
| `vision.act` | [`POST /v1/vision/sessions/{session_id}/act`](/api/post-v1-vision-sessions-session-id-act) |  |
| `vision.batchEvents` | [`GET /v1/vision/batches/{batch_id}/events`](/api/get-v1-vision-batches-batch-id-events) | event stream |
| `vision.cancelBatch` | [`POST /v1/vision/batches/{batch_id}/cancel`](/api/post-v1-vision-batches-batch-id-cancel) |  |
| `vision.cancelCrawl` | [`POST /v1/vision/crawls/{crawl_id}/cancel`](/api/post-v1-vision-crawls-crawl-id-cancel) |  |
| `vision.cancelExtract` | [`POST /v1/vision/extracts/{job_id}/cancel`](/api/post-v1-vision-extracts-job-id-cancel) | planned |
| `vision.closeSession` | [`DELETE /v1/vision/sessions/{session_id}`](/api/delete-v1-vision-sessions-session-id-) |  |
| `vision.connectCdp` | [`GET /v1/vision/sessions/{session_id}/cdp`](/api/get-v1-vision-sessions-session-id-cdp) | WebSocket |
| `vision.crawlEvents` | [`GET /v1/vision/crawls/{crawl_id}/events`](/api/get-v1-vision-crawls-crawl-id-events) | event stream |
| `vision.createBatch` | [`POST /v1/vision/batches`](/api/post-v1-vision-batches) | job: `.wait()`, `.events()` |
| `vision.createCrawl` | [`POST /v1/vision/crawls`](/api/post-v1-vision-crawls) | job: `.wait()`, `.events()` |
| `vision.createExtract` | [`POST /v1/vision/extracts`](/api/post-v1-vision-extracts) | job: `.wait()`, `.events()`; planned |
| `vision.createProfile` | [`POST /v1/vision/profiles`](/api/post-v1-vision-profiles) |  |
| `vision.createSession` | [`POST /v1/vision/sessions`](/api/post-v1-vision-sessions) |  |
| `vision.createSessionTicket` | [`POST /v1/vision/sessions/{session_id}/ticket`](/api/post-v1-vision-sessions-session-id-ticket) |  |
| `vision.deleteProfile` | [`DELETE /v1/vision/profiles/{profile_id}`](/api/delete-v1-vision-profiles-profile-id-) |  |
| `vision.exportProfileState` | [`GET /v1/vision/profiles/{profile_id}/state`](/api/get-v1-vision-profiles-profile-id-state) |  |
| `vision.extractEvents` | [`GET /v1/vision/extracts/{job_id}/events`](/api/get-v1-vision-extracts-job-id-events) | event stream; planned |
| `vision.getBatch` | [`GET /v1/vision/batches/{batch_id}`](/api/get-v1-vision-batches-batch-id-) |  |
| `vision.getCrawl` | [`GET /v1/vision/crawls/{crawl_id}`](/api/get-v1-vision-crawls-crawl-id-) |  |
| `vision.getExtract` | [`GET /v1/vision/extracts/{job_id}`](/api/get-v1-vision-extracts-job-id-) | planned |
| `vision.getProfile` | [`GET /v1/vision/profiles/{profile_id}`](/api/get-v1-vision-profiles-profile-id-) |  |
| `vision.getSession` | [`GET /v1/vision/sessions/{session_id}`](/api/get-v1-vision-sessions-session-id-) |  |
| `vision.listBatches` | [`GET /v1/vision/batches`](/api/get-v1-vision-batches) |  |
| `vision.listBatchPages` | [`GET /v1/vision/batches/{batch_id}/pages`](/api/get-v1-vision-batches-batch-id-pages) |  |
| `vision.listCrawlPages` | [`GET /v1/vision/crawls/{crawl_id}/pages`](/api/get-v1-vision-crawls-crawl-id-pages) |  |
| `vision.listCrawls` | [`GET /v1/vision/crawls`](/api/get-v1-vision-crawls) |  |
| `vision.listProfiles` | [`GET /v1/vision/profiles`](/api/get-v1-vision-profiles) |  |
| `vision.listSessions` | [`GET /v1/vision/sessions`](/api/get-v1-vision-sessions) |  |
| `vision.map` | [`POST /v1/vision/map`](/api/post-v1-vision-map) |  |
| `vision.readUrl` | [`GET /v1/vision/r/{url}`](/api/get-v1-vision-r-url-) |  |
| `vision.saveSessionProfile` | [`POST /v1/vision/sessions/{session_id}/profile`](/api/post-v1-vision-sessions-session-id-profile) |  |
| `vision.scrape` | [`POST /v1/vision/scrape`](/api/post-v1-vision-scrape) |  |
| `vision.updateProfile` | [`PATCH /v1/vision/profiles/{profile_id}`](/api/patch-v1-vision-profiles-profile-id-) |  |
| `render.cancelVideo` | [`POST /v1/render/videos/{video_id}/cancel`](/api/post-v1-render-videos-video-id-cancel) |  |
| `render.createScreenshot` | [`POST /v1/render/screenshots`](/api/post-v1-render-screenshots) |  |
| `render.createVideo` | [`POST /v1/render/videos`](/api/post-v1-render-videos) | job: `.wait()`, `.events()` |
| `render.deleteScreenshot` | [`DELETE /v1/render/screenshots/{screenshot_id}`](/api/delete-v1-render-screenshots-screenshot-id-) |  |
| `render.deleteVideo` | [`DELETE /v1/render/videos/{video_id}`](/api/delete-v1-render-videos-video-id-) |  |
| `render.getArtifact` | [`GET /v1/render/artifacts/{artifact_id}`](/api/get-v1-render-artifacts-artifact-id-) |  |
| `render.getScreenshot` | [`GET /v1/render/screenshots/{screenshot_id}`](/api/get-v1-render-screenshots-screenshot-id-) |  |
| `render.getVideo` | [`GET /v1/render/videos/{video_id}`](/api/get-v1-render-videos-video-id-) |  |
| `render.getVideoTimeline` | [`GET /v1/render/videos/{video_id}/timeline`](/api/get-v1-render-videos-video-id-timeline) |  |
| `render.listPresets` | [`GET /v1/render/presets`](/api/get-v1-render-presets) |  |
| `render.listScreenshots` | [`GET /v1/render/screenshots`](/api/get-v1-render-screenshots) |  |
| `render.listVideos` | [`GET /v1/render/videos`](/api/get-v1-render-videos) |  |
| `render.restyleVideo` | [`POST /v1/render/videos/{video_id}/restyle`](/api/post-v1-render-videos-video-id-restyle) | job: `.wait()`, `.events()` |
| `render.videoEvents` | [`GET /v1/render/videos/{video_id}/events`](/api/get-v1-render-videos-video-id-events) | event stream |
| `sandboxes.createPortUrl` | [`POST /v1/sandboxes/{sandbox_id}/ports/{port}/url`](/api/post-v1-sandboxes-sandbox-id-ports-port-url) |  |
| `sandboxes.createSandbox` | [`POST /v1/sandboxes`](/api/post-v1-sandboxes) |  |
| `sandboxes.createSnapshot` | [`POST /v1/sandboxes/{sandbox_id}/snapshot`](/api/post-v1-sandboxes-sandbox-id-snapshot) |  |
| `sandboxes.createTicket` | [`POST /v1/sandboxes/{sandbox_id}/ticket`](/api/post-v1-sandboxes-sandbox-id-ticket) |  |
| `sandboxes.deleteSandbox` | [`DELETE /v1/sandboxes/{sandbox_id}`](/api/delete-v1-sandboxes-sandbox-id-) |  |
| `sandboxes.deleteSnapshot` | [`DELETE /v1/sandboxes/snapshots/{snapshot_id}`](/api/delete-v1-sandboxes-snapshots-snapshot-id-) |  |
| `sandboxes.exec` | [`POST /v1/sandboxes/{sandbox_id}/exec`](/api/post-v1-sandboxes-sandbox-id-exec) | event stream |
| `sandboxes.forkSandbox` | [`POST /v1/sandboxes/{sandbox_id}/fork`](/api/post-v1-sandboxes-sandbox-id-fork) | planned |
| `sandboxes.getSandbox` | [`GET /v1/sandboxes/{sandbox_id}`](/api/get-v1-sandboxes-sandbox-id-) |  |
| `sandboxes.getSnapshot` | [`GET /v1/sandboxes/snapshots/{snapshot_id}`](/api/get-v1-sandboxes-snapshots-snapshot-id-) |  |
| `sandboxes.getTemplate` | [`GET /v1/sandboxes/templates/{template}`](/api/get-v1-sandboxes-templates-template-) |  |
| `sandboxes.killProcess` | [`POST /v1/sandboxes/{sandbox_id}/processes/{pid}/kill`](/api/post-v1-sandboxes-sandbox-id-processes-pid-kill) |  |
| `sandboxes.listProcesses` | [`GET /v1/sandboxes/{sandbox_id}/processes`](/api/get-v1-sandboxes-sandbox-id-processes) |  |
| `sandboxes.listSandboxes` | [`GET /v1/sandboxes`](/api/get-v1-sandboxes) |  |
| `sandboxes.listSnapshots` | [`GET /v1/sandboxes/snapshots`](/api/get-v1-sandboxes-snapshots) |  |
| `sandboxes.listTemplates` | [`GET /v1/sandboxes/templates`](/api/get-v1-sandboxes-templates) |  |
| `sandboxes.openPty` | [`GET /v1/sandboxes/{sandbox_id}/pty`](/api/get-v1-sandboxes-sandbox-id-pty) | WebSocket |
| `sandboxes.pauseSandbox` | [`POST /v1/sandboxes/{sandbox_id}/pause`](/api/post-v1-sandboxes-sandbox-id-pause) |  |
| `sandboxes.readFile` | [`GET /v1/sandboxes/{sandbox_id}/files`](/api/get-v1-sandboxes-sandbox-id-files) |  |
| `sandboxes.resumeSandbox` | [`POST /v1/sandboxes/{sandbox_id}/resume`](/api/post-v1-sandboxes-sandbox-id-resume) |  |
| `sandboxes.runCode` | [`POST /v1/sandboxes/{sandbox_id}/run_code`](/api/post-v1-sandboxes-sandbox-id-run-code) |  |
| `sandboxes.setTimeout` | [`POST /v1/sandboxes/{sandbox_id}/timeout`](/api/post-v1-sandboxes-sandbox-id-timeout) |  |
| `sandboxes.streamLogs` | [`GET /v1/sandboxes/{sandbox_id}/logs`](/api/get-v1-sandboxes-sandbox-id-logs) | event stream |
| `sandboxes.writeFile` | [`PUT /v1/sandboxes/{sandbox_id}/files`](/api/put-v1-sandboxes-sandbox-id-files) |  |
| `routines.cancelRun` | [`POST /v1/routines/runs/{run_id}/cancel`](/api/post-v1-routines-runs-run-id-cancel) |  |
| `routines.createRoutine` | [`POST /v1/routines`](/api/post-v1-routines) |  |
| `routines.createRun` | [`POST /v1/routines/{routine}/runs`](/api/post-v1-routines-routine-runs) | job: `.wait()`, `.events()` |
| `routines.deleteRoutine` | [`DELETE /v1/routines/{routine}`](/api/delete-v1-routines-routine-) |  |
| `routines.getRoutine` | [`GET /v1/routines/{routine}`](/api/get-v1-routines-routine-) |  |
| `routines.getRun` | [`GET /v1/routines/runs/{run_id}`](/api/get-v1-routines-runs-run-id-) |  |
| `routines.getVersion` | [`GET /v1/routines/{routine}/versions/{version}`](/api/get-v1-routines-routine-versions-version-) |  |
| `routines.importRoutine` | [`POST /v1/routines/import`](/api/post-v1-routines-import) |  |
| `routines.listRoutines` | [`GET /v1/routines`](/api/get-v1-routines) |  |
| `routines.listRuns` | [`GET /v1/routines/{routine}/runs`](/api/get-v1-routines-routine-runs) |  |
| `routines.listRunSteps` | [`GET /v1/routines/runs/{run_id}/steps`](/api/get-v1-routines-runs-run-id-steps) |  |
| `routines.listVersions` | [`GET /v1/routines/{routine}/versions`](/api/get-v1-routines-routine-versions) |  |
| `routines.publishRoutine` | [`PUT /v1/routines/{routine}`](/api/put-v1-routines-routine-) |  |
| `routines.rollbackRoutine` | [`POST /v1/routines/{routine}/rollback`](/api/post-v1-routines-routine-rollback) |  |
| `routines.rotateHookSecret` | [`POST /v1/routines/{routine}/hooks/{hook_id}/rotate`](/api/post-v1-routines-routine-hooks-hook-id-rotate) |  |
| `routines.runEvents` | [`GET /v1/routines/runs/{run_id}/events`](/api/get-v1-routines-runs-run-id-events) | event stream |
| `routines.signalRun` | [`POST /v1/routines/runs/{run_id}/signal`](/api/post-v1-routines-runs-run-id-signal) |  |
| `routines.updateRoutine` | [`PATCH /v1/routines/{routine}`](/api/patch-v1-routines-routine-) |  |
| `routines.validateRoutine` | [`POST /v1/routines/validate`](/api/post-v1-routines-validate) |  |
| `actions.call` | [`POST /v1/actions/call`](/api/post-v1-actions-call) |  |
| `actions.describe` | [`GET /v1/actions/describe`](/api/get-v1-actions-describe) |  |
| `actions.downloadPack` | [`GET /v1/actions/registry/packs/{namespace}/{name}/versions/{version}/download`](/api/get-v1-actions-registry-packs-namespace-name-versions-version-download) |  |
| `actions.getPack` | [`GET /v1/actions/packs/{namespace}/{name}`](/api/get-v1-actions-packs-namespace-name-) |  |
| `actions.getPackManifest` | [`GET /v1/actions/packs/{namespace}/{name}/versions/{version}/manifest`](/api/get-v1-actions-packs-namespace-name-versions-version-manifest) |  |
| `actions.getRegistryPack` | [`GET /v1/actions/registry/packs/{namespace}/{name}`](/api/get-v1-actions-registry-packs-namespace-name-) |  |
| `actions.getRegistryPackVersion` | [`GET /v1/actions/registry/packs/{namespace}/{name}/versions/{version}`](/api/get-v1-actions-registry-packs-namespace-name-versions-version-) |  |
| `actions.getSubscription` | [`GET /v1/actions/subscriptions/{subscription_id}`](/api/get-v1-actions-subscriptions-subscription-id-) |  |
| `actions.listPacks` | [`GET /v1/actions/packs`](/api/get-v1-actions-packs) |  |
| `actions.listRegistryPacks` | [`GET /v1/actions/registry/packs`](/api/get-v1-actions-registry-packs) |  |
| `actions.listSubscriptions` | [`GET /v1/actions/subscriptions`](/api/get-v1-actions-subscriptions) |  |
| `actions.listTriggers` | [`GET /v1/actions/triggers`](/api/get-v1-actions-triggers) |  |
| `actions.publishPack` | [`POST /v1/actions/registry/packs`](/api/post-v1-actions-registry-packs) |  |
| `actions.search` | [`GET /v1/actions/search`](/api/get-v1-actions-search) |  |
| `vault.createConnection` | [`POST /v1/vault/connections`](/api/post-v1-vault-connections) |  |
| `vault.deleteConnection` | [`DELETE /v1/vault/connections/{connection_id}`](/api/delete-v1-vault-connections-connection-id-) |  |
| `vault.getConnection` | [`GET /v1/vault/connections/{connection_id}`](/api/get-v1-vault-connections-connection-id-) |  |
| `vault.listConnections` | [`GET /v1/vault/connections`](/api/get-v1-vault-connections) |  |
| `vault.updateConnection` | [`PATCH /v1/vault/connections/{connection_id}`](/api/patch-v1-vault-connections-connection-id-) |  |
| `infer.countMessageTokens` | [`POST /v1/infer/v1/messages/count_tokens`](/api/post-v1-infer-v1-messages-count-tokens) | planned |
| `infer.createChatCompletion` | [`POST /v1/infer/chat/completions`](/api/post-v1-infer-chat-completions) | event stream |
| `infer.createEmbeddings` | [`POST /v1/infer/embeddings`](/api/post-v1-infer-embeddings) | planned |
| `infer.createMessage` | [`POST /v1/infer/v1/messages`](/api/post-v1-infer-v1-messages) | event stream; planned |
| `infer.createResponse` | [`POST /v1/infer/responses`](/api/post-v1-infer-responses) | event stream; planned |
| `infer.createSpeech` | [`POST /v1/infer/audio/speech`](/api/post-v1-infer-audio-speech) |  |
| `infer.getModel` | [`GET /v1/infer/models/{model}`](/api/get-v1-infer-models-model-) |  |
| `infer.listModels` | [`GET /v1/infer/models`](/api/get-v1-infer-models) |  |
| `infer.listVoices` | [`GET /v1/infer/audio/voices`](/api/get-v1-infer-audio-voices) |  |
| `keys.createKey` | [`POST /v1/keys`](/api/post-v1-keys) |  |
| `keys.getCurrentKey` | [`GET /v1/keys/current`](/api/get-v1-keys-current) |  |
| `keys.getKey` | [`GET /v1/keys/{key_id}`](/api/get-v1-keys-key-id-) |  |
| `keys.listKeys` | [`GET /v1/keys`](/api/get-v1-keys) |  |
| `keys.revokeKey` | [`DELETE /v1/keys/{key_id}`](/api/delete-v1-keys-key-id-) |  |
| `keys.updateKey` | [`PATCH /v1/keys/{key_id}`](/api/patch-v1-keys-key-id-) |  |
| `usage.getUsage` | [`GET /v1/usage`](/api/get-v1-usage) |  |
| `usage.listUsageRecords` | [`GET /v1/usage/records`](/api/get-v1-usage-records) |  |
| `limits.getLimits` | [`GET /v1/limits`](/api/get-v1-limits) |  |
| `requests.getRequest` | [`GET /v1/requests/{request_id}`](/api/get-v1-requests-request-id-) |  |
| `requests.listRequests` | [`GET /v1/requests`](/api/get-v1-requests) |  |
| `webhooks.getSecret` | [`POST /v1/webhooks/secret`](/api/post-v1-webhooks-secret) |  |
| `webhooks.listDeliveries` | [`GET /v1/webhooks/deliveries`](/api/get-v1-webhooks-deliveries) |  |
| `webhooks.rotateSecret` | [`POST /v1/webhooks/secret/rotate`](/api/post-v1-webhooks-secret-rotate) |  |
| `webhooks.sendTest` | [`POST /v1/webhooks/test`](/api/post-v1-webhooks-test) |  |
| `orgs.getOrg` | [`GET /v1/orgs/{org_id}`](/api/get-v1-orgs-org-id-) |  |
| `orgs.listMembers` | [`GET /v1/orgs/{org_id}/members`](/api/get-v1-orgs-org-id-members) |  |
| `orgs.listOrgs` | [`GET /v1/orgs`](/api/get-v1-orgs) |  |
| `projects.createProject` | [`POST /v1/projects`](/api/post-v1-projects) |  |
| `projects.deleteProject` | [`DELETE /v1/projects/{project_id}`](/api/delete-v1-projects-project-id-) |  |
| `projects.getProject` | [`GET /v1/projects/{project_id}`](/api/get-v1-projects-project-id-) |  |
| `projects.listProjects` | [`GET /v1/projects`](/api/get-v1-projects) |  |
| `projects.updateProject` | [`PATCH /v1/projects/{project_id}`](/api/patch-v1-projects-project-id-) |  |