Skip to content

Agent Tool API

A metered HTTP API built for autonomous agents. Mint a key, discover the catalog, call tools. No human in the loop except for side-effecting actions, which require an explicit per-call confirmation header.

Base URL

https://3bi.ai/api/public/v1

Quickstart in four calls

The whole loop, copy-pasteable. Every step below is documented in detail further down this page.

# 1. mint a key + 500 free credits (no auth)
curl -X POST https://3bi.ai/api/public/v1/signup -H "content-type: application/json" -d '{"label":"my agent"}'

# 2. discover the catalog (typed JSON Schema per tool)
curl https://3bi.ai/api/public/v1/tools

# 3. call a live read-only tool (2 credits)
curl -X POST https://3bi.ai/api/public/v1/tools/fetch_url \
  -H "Authorization: Bearer $RELAY_KEY" -H "content-type: application/json" \
  -d '{"url":"https://example.com"}'

# 4a. call a side-effecting tool — returns 428 with a preview + confirmationToken
curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_send_email \
  -H "Authorization: Bearer $RELAY_KEY" -H "content-type: application/json" \
  -d '{"to":"ops@example.com","subject":"hi","body":"hello"}'

# 4b. human approves the preview, then resend the IDENTICAL body with the token
curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_send_email \
  -H "Authorization: Bearer $RELAY_KEY" -H "content-type: application/json" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" -H "idempotency-key: $(uuidgen)" \
  -d '{"to":"ops@example.com","subject":"hi","body":"hello"}'

0. Agents sign themselves up

No browser, no password, no email verification loop. One unauthenticated POST creates a workspace, returns an API key (shown once) and grants 500 free credits.

curl -X POST https://3bi.ai/api/public/v1/signup \
  -H "content-type: application/json" \
  -d '{"label":"my agent"}'
{
  "ok": true,
  "orgId": "…",
  "apiKey": "sk_agent_xxxxxxxx_…",
  "credits": { "granted": 500, "balance": 500 },
  "claim": { "url": "https://3bi.ai/claim?token=…", "expiresAt": "…" }
}

Signup is limited to 3 workspaces per address per 24h (429 signup_rate_limited). When credits run out, call POST /api/public/v1/claim for a fresh one-hour claim URL and hand it to your human operator: they sign in, take ownership of the workspace and buy credits. The agent's key keeps working throughout. Rotate a key with POST /api/public/v1/keys/rotate — the old key stays valid for 10 more minutes. Machine-readable version of this page: https://3bi.ai/llms.txt.

0b. Agents pay for themselves (x402)

When the balance runs out, the 402 is machine-payable. The body follows the x402 spec: settle accepts[0] in USDC on Base, then retry the same request with an X-PAYMENT header. Credits are added and the call executes in that one retry.

{
  "x402Version": 1,
  "error": "insufficient_credits",
  "accepts": [{
    "scheme": "exact",
    "network": "base",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "maxAmountRequired": "900000",
    "payTo": "0x…",
    "resource": "https://3bi.ai/api/public/v1/tools/crm.update_contact",
    "maxTimeoutSeconds": 120
  }],
  "payment": { "credits": 100, "amountUsd": 0.9, "asset": "USDC", "network": "base" }
}

To top up ahead of time instead of on the failing call:

curl -X POST https://3bi.ai/api/public/v1/credits/purchase \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -d '{"credits":1000}'          # → 402 with the offer, then retry with X-PAYMENT

Payments are credited once per settled transaction, so a replayed X-PAYMENT never double-charges. If on-chain payment is not available to you, fall back to POST /api/public/v1/claim and a human buys credits with a card.

0c. Spend guardrails

Every key can carry owner-set limits, enforced server-side before a tool runs: max credits per call, a rolling 24-hour cap, a lifetime cap, an expiry date and a tool allowlist. Exceeding one returns 403 budget_exceeded, 403 tool_not_allowed or 401 key_expired with the limit and spend-to-date in the body — nothing is executed or charged. Configure them per key in the console.

1. Discovery

Machine-readable entry points. All are unauthenticated so an agent can plan — and price the job — before it has a key.

GET /api/public/v1/tools             # catalog: JSON Schemas, credits and usdPerCall
GET /api/public/v1/pricing           # USD per credit, per tool and per pack
GET /api/public/v1/openapi.json      # OpenAPI 3.1 document
GET /.well-known/agents.json         # agent-native discovery document
GET /.well-known/ai-plugin.json      # OpenAI-style plugin manifest
GET /.well-known/agent-manifest.json # legacy alias of agents.json

2. Authentication

Every call carries a workspace key as a bearer token. Keys are shown once at creation and stored hashed; revoke them any time from the console. Invoking tools requires the tools:invoke scope. New workspaces start with 500 free credits.

Authorization: Bearer sk_agent_xxxxxxxx_...
# fallback for clients that cannot set Authorization:
x-api-key: sk_agent_xxxxxxxx_...

3. Invoke a tool

curl -X POST https://3bi.ai/api/public/v1/tools/fetch_url \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: 9f1c-run-42" \
  -d '{"url":"https://example.com"}'

Arguments are validated against the tool's JSON Schema. Invalid input returns 422 invalid_input with the failing field paths, and no credits are charged. A successful call responds with:

{
  "ok": true,
  "requestId": "b0e1…",
  "tool": "fetch_url",
  "demo": false,
  "credits": { "charged": 2, "balance": 498 },
  "result": { … }
}

Responses also carry x-request-id and x-credits-charged headers. Fetch a single tool's schema without a key with GET /api/public/v1/tools/{name}. CORS is open, so browser-based agents can call the API directly.

4. Side effects require confirmation

Tools that send email, write to a CRM, create payments or delete records return 428 confirmation_required with a preview of the exact arguments that would run, the credit cost, and a single-use confirmationToken. Show the preview to your operator; once they approve, resend the identical body with the token to execute. The rejected attempt is logged in your usage history and charges nothing.

-H "x-confirmation-token: $CONFIRMATION_TOKEN"

The token is bound to the exact arguments that were previewed, is valid for 10 minutes and can be redeemed once. Changing any field returns 409 confirmation_mismatch; reusing a token returns 409 confirmation_used; letting it lapse returns 410 confirmation_expired. There is no header an agent can set on its first call to skip the preview — that is the point.

5. Idempotency

Send an idempotency-key header on any call. The key is scoped to your API key. Repeating a completed call with the same key returns the original stored response with "replayed": true and is not charged again — safe for agent retry loops and timeouts. If the first call is still running, the retry gets 409 request_in_progress; failed calls release the key so you can retry.

6. Rate limits

60 calls per minute per API key, counted over a rolling window. Over the limit you get 429 rate_limited. Mint additional keys for parallel workers.

7. Error code reference

Every failure uses the same envelope: { "ok": false, "error": { "code", "message", … } }. This table is the single reference for the whole API — the OpenAPI document is generated from it.

StatusCodeCauseWhat to doRetry
401missing_api_keyNo Authorization: Bearer header on the request.Send Authorization: Bearer sk_agent_… , or POST /api/public/v1/signup to mint a key.no
401invalid_api_keyThe key is unknown, revoked, or past its rotation grace window.Rotate with POST /api/public/v1/keys/rotate, or create a new key in the console.no
402insufficient_credits+ required, balance, accepts, paymentThe workspace balance is below this tool's price. Nothing was executed or charged.Pay machine-to-machine: the body carries an x402 accepts[] offer — settle it and retry the same request with an X-PAYMENT header. Humans can instead buy credits via POST /api/public/v1/claim.no
402payment_failed+ required, balanceAn X-PAYMENT header was supplied but the facilitator could not verify or settle it.Re-read accepts[0] from a fresh 402, rebuild the payment payload for the exact amount, asset and network, and retry.no
401key_expired+ expiredAtThe key passed the expiry date set by its owner.Rotate the key or issue a new one in the console.no
403budget_exceeded+ spent, required, limit, windowThe call would exceed a per-call, 24-hour, or lifetime credit cap set on this key.Wait for the window to roll over, or ask the key owner to raise the cap in the console.no
403tool_not_allowed+ allowedToolsThis key has a tool allowlist that does not include the requested tool.Call an allowed tool, or ask the key owner to widen the allowlist.no
403tool_disabledThe workspace owner has disabled this tool for the org.Enable the tool in the console, or call a different tool.no
403insufficient_scopeThe key is valid but does not carry the tools:invoke scope.Issue a key with tools:invoke from the console and retry with it.no
404unknown_toolNo public tool with that name exists in the catalog.Re-read GET /api/public/v1/tools and use an exact name from the catalog.no
409request_in_progressAnother call with the same idempotency-key is still executing, or an approved confirmation token is already running.Wait and retry the same idempotency-key to receive the stored response; do not change the body. If the original request is known to have failed, retry with a new idempotency-key. For a confirmation token, call the tool again with no token to get a fresh preview.yes
422invalid_jsonThe request body was not valid JSON.Send a JSON object with content-type: application/json.no
422invalid_input+ issuesArguments failed the tool's JSON Schema. No credits were charged.Fix the fields listed in error.issues[].path and resend.no
428confirmation_required+ tool, credits, preview, confirmationToken, expiresAtA side-effecting tool was called without a confirmation token. Nothing was executed or charged.Show error.preview to your operator. Once they approve, repeat the identical request with header x-confirmation-token: <error.confirmationToken>.yes
403confirmation_invalidThe x-confirmation-token header is unknown or belongs to another workspace.Call the tool without a token to receive a fresh preview and token, then retry.no
409confirmation_mismatchThe token is bound to a specific tool and argument set; this request differs from what was previewed.Send the exact body that was previewed, or request a new confirmation for the new body.no
409confirmation_usedThat confirmation token was already redeemed. Tokens are single-use.Request a new confirmation for the next call.no
410confirmation_expiredThe confirmation token passed its 10-minute validity window.Call again without a token to get a new preview and token.yes
429rate_limitedOver 60 calls per minute for this API key.Back off ~1 minute, or mint additional keys for parallel workers.yes
429signup_rate_limitedMore than 3 workspaces created from this address in 24 hours (signup only).Reuse the key you already have, or have your operator claim an existing workspace.no
502tool_failedThe upstream tool threw while executing. Reserved credits are refunded automatically.Retry with backoff; if it persists the integration is down — check status and report.yes
503metering_unavailableCredit metering could not be reached, so the call was not authorized or charged.Retry with backoff; the same idempotency-key is safe to reuse.yes
404approval_intent_not_foundNo approval intent with that id exists in this workspace.Use the intent_id from the 428 approval object; intents expire after 24 hours.no
409oauth_connection_required+ provider, connectThis tool needs a connected third-party account, and none is linked for this workspace.POST the connect URL from the body with your API key to get an authorization URL, have your operator complete the OAuth flow, then retry the call.no
404oauth_provider_unknownNo OAuth provider with that slug is enabled.GET /api/public/v1/oauth/providers for the supported catalog.no
502oauth_callback_failedThe OAuth provider refused the code exchange or the profile lookup.Restart the connect flow; if it persists the provider app credentials may be misconfigured.no
502oauth_provider_not_configuredThe OAuth provider is enabled in the catalog but its app credentials are missing on RELAY.The workspace owner should contact support; agents cannot fix this themselves.no
500oauth_authorize_failedThe OAuth authorization URL could not be generated.Retry; if it persists, check that the provider is still enabled.yes
500catalog_unavailableThe OAuth provider catalog could not be loaded.Retry shortly.yes
500connections_unavailableThe workspace's OAuth connections could not be listed.Retry shortly.yes
404connection_not_foundNo OAuth connection with that id exists in this workspace.List connections with GET /api/public/v1/oauth/connections and use a current id.no
500revoke_failedThe OAuth connection could not be revoked.Retry; the connection remains active until revocation succeeds.yes
{"ok":false,"error":{"code":"insufficient_credits","message":"Not enough credits for this call","required":5,"balance":2}}

8. Try a tool

Pick a tool, edit the example body, and send it live from your browser. Leave the key blank to see the real 401, or flip the confirmation switch off on a side-effecting tool to see the 428 preview payload.

Fetch a public web page or JSON endpoint over HTTPS and return its readable text content plus metadata. Real network call, not a simulation. Read-only. Costs 2 credit(s).

curl -X POST https://3bi.ai/api/public/v1/tools/fetch_url \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{ "url": "https://example.com" }'
Runs against the live API from your browser. Demo tools only touch fixtures.
Expected outputs for fetch_url

200 — success envelope

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "fetch_url",
  "demo": false,
  "credits": {
    "charged": 2,
    "balance": 498
  },
  "result": {
    "ok": true,
    "url": "https://example.com/",
    "status": 200,
    "contentType": "text/html",
    "title": "Example Domain",
    "text": "Example Domain. This domain is for use in illustrative examples in documents...",
    "chars": 208,
    "truncated": false,
    "fetchedAt": "2026-08-09T20:15:18.358Z"
  }
}
  • 401 missing_api_key No Authorization: Bearer header on the request. Send Authorization: Bearer sk_agent_… , or POST /api/public/v1/signup to mint a key.
  • 401 invalid_api_key The key is unknown, revoked, or past its rotation grace window. Rotate with POST /api/public/v1/keys/rotate, or create a new key in the console.
  • 402 insufficient_credits The workspace balance is below this tool's price. Nothing was executed or charged. Pay machine-to-machine: the body carries an x402 accepts[] offer — settle it and retry the same request with an X-PAYMENT header. Humans can instead buy credits via POST /api/public/v1/claim.
  • 402 payment_failed An X-PAYMENT header was supplied but the facilitator could not verify or settle it. Re-read accepts[0] from a fresh 402, rebuild the payment payload for the exact amount, asset and network, and retry.
  • 401 key_expired The key passed the expiry date set by its owner. Rotate the key or issue a new one in the console.
  • 403 budget_exceeded The call would exceed a per-call, 24-hour, or lifetime credit cap set on this key. Wait for the window to roll over, or ask the key owner to raise the cap in the console.
  • 403 tool_not_allowed This key has a tool allowlist that does not include the requested tool. Call an allowed tool, or ask the key owner to widen the allowlist.
  • 403 tool_disabled The workspace owner has disabled this tool for the org. Enable the tool in the console, or call a different tool.
  • 403 insufficient_scope The key is valid but does not carry the tools:invoke scope. Issue a key with tools:invoke from the console and retry with it.
  • 404 unknown_tool No public tool with that name exists in the catalog. Re-read GET /api/public/v1/tools and use an exact name from the catalog.
  • 409 request_in_progress Another call with the same idempotency-key is still executing, or an approved confirmation token is already running. Wait and retry the same idempotency-key to receive the stored response; do not change the body. If the original request is known to have failed, retry with a new idempotency-key. For a confirmation token, call the tool again with no token to get a fresh preview.
  • 422 invalid_json The request body was not valid JSON. Send a JSON object with content-type: application/json.
  • 422 invalid_input Arguments failed the tool's JSON Schema. No credits were charged. Fix the fields listed in error.issues[].path and resend.
  • 429 rate_limited Over 60 calls per minute for this API key. Back off ~1 minute, or mint additional keys for parallel workers.
  • 502 tool_failed The upstream tool threw while executing. Reserved credits are refunded automatically. Retry with backoff; if it persists the integration is down — check status and report.
  • 503 metering_unavailable Credit metering could not be reached, so the call was not authorized or charged. Retry with backoff; the same idempotency-key is safe to reuse.
  • 404 approval_intent_not_found No approval intent with that id exists in this workspace. Use the intent_id from the 428 approval object; intents expire after 24 hours.
  • 409 oauth_connection_required This tool needs a connected third-party account, and none is linked for this workspace. POST the connect URL from the body with your API key to get an authorization URL, have your operator complete the OAuth flow, then retry the call.
  • 404 oauth_provider_unknown No OAuth provider with that slug is enabled. GET /api/public/v1/oauth/providers for the supported catalog.
  • 502 oauth_callback_failed The OAuth provider refused the code exchange or the profile lookup. Restart the connect flow; if it persists the provider app credentials may be misconfigured.
  • 502 oauth_provider_not_configured The OAuth provider is enabled in the catalog but its app credentials are missing on RELAY. The workspace owner should contact support; agents cannot fix this themselves.
  • 500 oauth_authorize_failed The OAuth authorization URL could not be generated. Retry; if it persists, check that the provider is still enabled.
  • 500 catalog_unavailable The OAuth provider catalog could not be loaded. Retry shortly.
  • 500 connections_unavailable The workspace's OAuth connections could not be listed. Retry shortly.
  • 404 connection_not_found No OAuth connection with that id exists in this workspace. List connections with GET /api/public/v1/oauth/connections and use a current id.
  • 500 revoke_failed The OAuth connection could not be revoked. Retry; the connection remains active until revocation succeeds.

9. Starter catalog, examples & credit prices

The catalog has two tiers. Live tools (fetch_url, crawl_site, extract_structured, search_web, search_knowledge_base, execute_code, browse_page) do real network, model, vector search, code execution and remote browser work, cost credits and return "demo": false. search_knowledge_base runs semantic search over documents uploaded to the workspace knowledge base. execute_code runs Python or JavaScript in an isolated E2B sandbox. browse_page renders a URL in a Browserbase cloud browser and returns the markdown text. Every sandbox_* tool is free (0 credits), returns fixture data with "demo": true and changes nothing — use them to rehearse auth, schemas, idempotency and the confirmation gate. Workspace owners can disable any tool in the console.

fetch_url2 cr

Fetch a public web page or JSON endpoint over HTTPS and return its readable text content plus metadata. Real network call, not a simulation. Read-only.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/fetch_url \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"url":"https://example.com"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "fetch_url",
  "demo": false,
  "credits": {
    "charged": 2,
    "balance": 498
  },
  "result": {
    "ok": true,
    "url": "https://example.com/",
    "status": 200,
    "contentType": "text/html",
    "title": "Example Domain",
    "text": "Example Domain. This domain is for use in illustrative examples in documents...",
    "chars": 208,
    "truncated": false,
    "fetchedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → fetch_url
arguments: {"url":"https://example.com"}
structuredContent: {"ok":true,"url":"https://example.com/","status":200,"contentType":"text/html","title":"Example Domain","text":"Example Domain. This domain is for use in illustrative examples in documents...","chars":208,"truncated":false,"fetchedAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":2,"balance":498}}
crawl_site6 cr

Crawl a public site starting from one https:// URL and return readable text for that page plus up to nine same-origin pages it links to. Real network calls. Read-only.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/crawl_site \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"url":"https://example.com","maxPages":3}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "crawl_site",
  "demo": false,
  "credits": {
    "charged": 6,
    "balance": 494
  },
  "result": {
    "ok": true,
    "seed": "https://example.com",
    "pageCount": 2,
    "pages": [
      {
        "url": "https://example.com/",
        "status": 200,
        "title": "Example Domain",
        "text": "Example Domain. This domain is for use in illustrative examples...",
        "chars": 208
      }
    ],
    "crawledAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → crawl_site
arguments: {"url":"https://example.com","maxPages":3}
structuredContent: {"ok":true,"seed":"https://example.com","pageCount":2,"pages":[{"url":"https://example.com/","status":200,"title":"Example Domain","text":"Example Domain. This domain is for use in illustrative examples...","chars":208}],"crawledAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":6,"balance":494}}
extract_structured8 cr

Extract named fields as JSON from a public URL or supplied text, using a server-side model. Returns one value per requested field, or null when absent. Read-only.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/extract_structured \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"url":"https://example.com","fields":["title","purpose"]}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "extract_structured",
  "demo": false,
  "credits": {
    "charged": 8,
    "balance": 492
  },
  "result": {
    "ok": true,
    "sourceUrl": "https://example.com/",
    "fields": {
      "title": "Example Domain",
      "purpose": "Illustrative examples in documents"
    },
    "missing": [],
    "extractedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → extract_structured
arguments: {"url":"https://example.com","fields":["title","purpose"]}
structuredContent: {"ok":true,"sourceUrl":"https://example.com/","fields":{"title":"Example Domain","purpose":"Illustrative examples in documents"},"missing":[],"extractedAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":8,"balance":492}}
search_web4 cr

Search the public web and return ranked results with titles, snippets, and source URLs. Uses a server-side search API. Read-only.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/search_web \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"query":"x402 payment protocol summary"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "search_web",
  "demo": false,
  "credits": {
    "charged": 4,
    "balance": 496
  },
  "result": {
    "ok": true,
    "query": "x402 payment protocol summary",
    "results": [
      {
        "title": "x402 - Machine-payable HTTP",
        "url": "https://x402.org",
        "content": "x402 is a protocol for machine payments over HTTP using stablecoins...",
        "score": 0.94
      }
    ],
    "answer": "x402 lets servers request on-chain payment by returning HTTP 402.",
    "searchedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → search_web
arguments: {"query":"x402 payment protocol summary"}
structuredContent: {"ok":true,"query":"x402 payment protocol summary","results":[{"title":"x402 - Machine-payable HTTP","url":"https://x402.org","content":"x402 is a protocol for machine payments over HTTP using stablecoins...","score":0.94}],"answer":"x402 lets servers request on-chain payment by returning HTTP 402.","searchedAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":4,"balance":496}}
search_knowledge_base3 cr

Search this workspace's uploaded documents using semantic similarity. Returns the most relevant text chunks with document titles and source URLs. Read-only.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/search_knowledge_base \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"query":"refund policy"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "search_knowledge_base",
  "demo": false,
  "credits": {
    "charged": 3,
    "balance": 497
  },
  "result": {
    "ok": true,
    "matches": [
      {
        "documentId": "doc_...",
        "chunkIndex": 0,
        "title": "Refund policy",
        "sourceUrl": null,
        "content": "Refunds are issued within 14 days of purchase for annual plans...",
        "similarity": 0.91
      }
    ]
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → search_knowledge_base
arguments: {"query":"refund policy"}
structuredContent: {"ok":true,"matches":[{"documentId":"doc_...","chunkIndex":0,"title":"Refund policy","sourceUrl":null,"content":"Refunds are issued within 14 days of purchase for annual plans...","similarity":0.91}],"demo":false,"credits":{"charged":3,"balance":497}}
execute_codeconfirm8 cr

Run Python or JavaScript code in a sandboxed E2B environment and return stdout, stderr, and the exit code. Files are ephemeral, but code can make external network requests with side effects. Requires the configured side-effect confirmation flow.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/execute_code \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"code":"print('hello')","language":"python"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "execute_code",
  "demo": false,
  "credits": {
    "charged": 8,
    "balance": 492
  },
  "result": {
    "ok": true,
    "language": "python",
    "stdout": "hello\n",
    "stderr": "",
    "exitCode": 0,
    "executedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → execute_code
arguments: {"code":"print('hello')","language":"python"}
structuredContent: {"ok":true,"language":"python","stdout":"hello\n","stderr":"","exitCode":0,"executedAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":8,"balance":492}}
browse_page10 cr

Open a URL in a remote Browserbase browser and return the rendered page as markdown text, including title, status code, and content type. Runs in Browserbase's cloud so it works from serverless runtimes.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/browse_page \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"url":"https://example.com"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "browse_page",
  "demo": false,
  "credits": {
    "charged": 10,
    "balance": 490
  },
  "result": {
    "ok": true,
    "url": "https://example.com/",
    "statusCode": 200,
    "contentType": "text/markdown",
    "title": "Example Domain",
    "text": "Example Domain\n\nThis domain is for use in illustrative examples...",
    "finishedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → browse_page
arguments: {"url":"https://example.com"}
structuredContent: {"ok":true,"url":"https://example.com/","statusCode":200,"contentType":"text/markdown","title":"Example Domain","text":"Example Domain\n\nThis domain is for use in illustrative examples...","finishedAt":"2026-08-09T20:15:18.358Z","demo":false,"credits":{"charged":10,"balance":490}}
sandbox_search_knowledge_base0 cr

Sandbox: searches a fixed set of fixture documents and returns simulated matches. Free — use it to exercise the API, not for real knowledge.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_search_knowledge_base \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"query":"refund policy"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_search_knowledge_base",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "matches": [
      {
        "title": "Refund policy",
        "body": "Refunds are issued within 14 days of purchase for annual plans, pro-rated after that."
      }
    ]
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → sandbox_search_knowledge_base
arguments: {"query":"refund policy"}
structuredContent: {"ok":true,"matches":[{"title":"Refund policy","body":"Refunds are issued within 14 days of purchase for annual plans, pro-rated after that."}],"demo":true,"credits":{"charged":0,"balance":500}}
sandbox_lookup_crm_contact0 cr

Sandbox: returns a fixture CRM contact by email. Free — no real CRM is queried.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_lookup_crm_contact \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"email":"dana@northwind.io"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_lookup_crm_contact",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "contact": {
      "id": "c_1024",
      "name": "Dana Whitfield",
      "email": "dana@northwind.io",
      "company": "Northwind",
      "stage": "customer",
      "mrr": 4200
    }
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → sandbox_lookup_crm_contact
arguments: {"email":"dana@northwind.io"}
structuredContent: {"ok":true,"contact":{"id":"c_1024","name":"Dana Whitfield","email":"dana@northwind.io","company":"Northwind","stage":"customer","mrr":4200},"demo":true,"credits":{"charged":0,"balance":500}}
sandbox_list_records0 cr

Sandbox: lists fixture records (contacts, invoices, tickets) with paging and filtering. Free — no real data.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_list_records \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -d '{"type":"invoices","limit":25}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_list_records",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "type": "invoices",
    "count": 2,
    "rows": [
      {
        "id": "in_881",
        "contact": "dana@northwind.io",
        "amountCents": 420000,
        "status": "paid"
      },
      {
        "id": "in_882",
        "contact": "priya@fernbrook.co",
        "amountCents": 89000,
        "status": "open"
      }
    ],
    "nextCursor": null
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing.

MCP

tools/call → sandbox_list_records
arguments: {"type":"invoices","limit":25}
structuredContent: {"ok":true,"type":"invoices","count":2,"rows":[{"id":"in_881","contact":"dana@northwind.io","amountCents":420000,"status":"paid"},{"id":"in_882","contact":"priya@fernbrook.co","amountCents":89000,"status":"open"}],"nextCursor":null,"demo":true,"credits":{"charged":0,"balance":500}}
sandbox_send_emailconfirm0 cr

Sandbox: simulates sending an email and returns a fake message id. Nothing is delivered. Free, and still side-effecting so you can exercise the confirmation flow.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_send_email \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"to":"dana@northwind.io","subject":"Your invoice is ready","body":"Hi Dana — invoice in_881 is attached. Thanks!"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_send_email",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "simulated": true,
    "messageId": "sim_4f2a91cd",
    "to": "dana@northwind.io",
    "subject": "Your invoice is ready",
    "deliveredAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → sandbox_send_email
arguments: {"to":"dana@northwind.io","subject":"Your invoice is ready","body":"Hi Dana — invoice in_881 is attached. Thanks!"}
structuredContent: {"ok":true,"simulated":true,"messageId":"sim_4f2a91cd","to":"dana@northwind.io","subject":"Your invoice is ready","deliveredAt":"2026-08-09T20:15:18.358Z","demo":true,"credits":{"charged":0,"balance":500}}
sandbox_update_crm_recordconfirm0 cr

Sandbox: simulates updating a CRM record. Nothing is written. Free, and still side-effecting so you can exercise the confirmation flow.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_update_crm_record \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"recordId":"c_1024","fields":{"stage":"churn_risk","owner":"ae_12"}}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_update_crm_record",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "simulated": true,
    "recordId": "c_1024",
    "updatedFields": {
      "stage": "churn_risk",
      "owner": "ae_12"
    },
    "updatedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → sandbox_update_crm_record
arguments: {"recordId":"c_1024","fields":{"stage":"churn_risk","owner":"ae_12"}}
structuredContent: {"ok":true,"simulated":true,"recordId":"c_1024","updatedFields":{"stage":"churn_risk","owner":"ae_12"},"updatedAt":"2026-08-09T20:15:18.358Z","demo":true,"credits":{"charged":0,"balance":500}}
sandbox_create_paymentconfirm0 cr

Sandbox: simulates creating a payment charge. No money moves. Free, and still side-effecting so you can exercise the confirmation flow.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_create_payment \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"customerId":"c_1024","amountCents":4200,"currency":"usd"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_create_payment",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "simulated": true,
    "paymentId": "pay_9c31be40",
    "customerId": "c_1024",
    "amountCents": 4200,
    "currency": "usd",
    "status": "succeeded"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → sandbox_create_payment
arguments: {"customerId":"c_1024","amountCents":4200,"currency":"usd"}
structuredContent: {"ok":true,"simulated":true,"paymentId":"pay_9c31be40","customerId":"c_1024","amountCents":4200,"currency":"usd","status":"succeeded","demo":true,"credits":{"charged":0,"balance":500}}
sandbox_delete_recordconfirm0 cr

Sandbox: simulates deleting a record. Nothing is deleted. Free, and still side-effecting so you can exercise the confirmation flow.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/sandbox_delete_record \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"type":"tickets","recordId":"t_51"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "sandbox_delete_record",
  "demo": true,
  "credits": {
    "charged": 0,
    "balance": 500
  },
  "result": {
    "ok": true,
    "simulated": true,
    "deleted": {
      "type": "tickets",
      "recordId": "t_51"
    },
    "deletedAt": "2026-08-09T20:15:18.358Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → sandbox_delete_record
arguments: {"type":"tickets","recordId":"t_51"}
structuredContent: {"ok":true,"simulated":true,"deleted":{"type":"tickets","recordId":"t_51"},"deletedAt":"2026-08-09T20:15:18.358Z","demo":true,"credits":{"charged":0,"balance":500}}
gmail_sendconfirm6 cr

Send an email through the workspace's connected Google account. Uses the managed OAuth connection — the agent never sees credentials. Requires the workspace to connect Google first (POST /api/public/v1/oauth/google/authorize). Side-effecting: requires confirmation.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/gmail_send \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"to":"dana@northwind.io","subject":"Your invoice is ready","body":"Hi Dana — invoice in_881 is attached. Thanks!"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "gmail_send",
  "demo": false,
  "credits": {
    "charged": 6,
    "balance": 494
  },
  "result": {
    "ok": true,
    "messageId": "18f2a91cd4b5e607",
    "to": "dana@northwind.io",
    "threadId": "18f2a91cd4b5e607",
    "sentAt": "2026-09-23T18:00:00.000Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → gmail_send
arguments: {"to":"dana@northwind.io","subject":"Your invoice is ready","body":"Hi Dana — invoice in_881 is attached. Thanks!"}
structuredContent: {"ok":true,"messageId":"18f2a91cd4b5e607","to":"dana@northwind.io","threadId":"18f2a91cd4b5e607","sentAt":"2026-09-23T18:00:00.000Z","demo":false,"credits":{"charged":6,"balance":494}}
slack_post_messageconfirm4 cr

Post a message to a Slack channel as the workspace's connected Slack identity. Uses the managed OAuth connection — the agent never sees credentials. Requires the workspace to connect Slack first (POST /api/public/v1/oauth/slack/authorize). Side-effecting: requires confirmation.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/slack_post_message \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"channel":"#general","text":"Deploy finished :white_check_mark:"}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "slack_post_message",
  "demo": false,
  "credits": {
    "charged": 4,
    "balance": 496
  },
  "result": {
    "ok": true,
    "channel": "C0123456789",
    "ts": "1758650400.000100",
    "messageId": "C0123456789:1758650400.000100",
    "postedAt": "2026-09-23T18:00:00.000Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → slack_post_message
arguments: {"channel":"#general","text":"Deploy finished :white_check_mark:"}
structuredContent: {"ok":true,"channel":"C0123456789","ts":"1758650400.000100","messageId":"C0123456789:1758650400.000100","postedAt":"2026-09-23T18:00:00.000Z","demo":false,"credits":{"charged":4,"balance":496}}
github_create_issueconfirm4 cr

Create an issue in a GitHub repository as the workspace's connected GitHub identity. Uses the managed OAuth connection — the agent never sees credentials. Requires the workspace to connect GitHub first (POST /api/public/v1/oauth/github/authorize). Side-effecting: requires confirmation.

Request

curl -X POST https://3bi.ai/api/public/v1/tools/github_create_issue \
  -H "Authorization: Bearer $RELAY_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: run-42" \
  -H "x-confirmation-token: $CONFIRMATION_TOKEN" \
  -d '{"owner":"octocat","repo":"hello-world","title":"Found a bug","body":"Steps to reproduce..."}'

Response — 200

{
  "ok": true,
  "requestId": "b0e1c8a2-9f4d-4d0f-9a1e-2c5d7f8e1a30",
  "tool": "github_create_issue",
  "demo": false,
  "credits": {
    "charged": 4,
    "balance": 496
  },
  "result": {
    "ok": true,
    "issueNumber": 1347,
    "url": "https://github.com/octocat/hello-world/issues/1347",
    "state": "open",
    "createdAt": "2026-09-23T18:00:00.000Z"
  }
}

Replaying the same idempotency-key returns this exact body with "replayed": true and charges nothing. Call it first without a token: the 428 response carries a preview of these arguments plus the single-use confirmationToken used above.

MCP

tools/call → github_create_issue
arguments: {"owner":"octocat","repo":"hello-world","title":"Found a bug","body":"Steps to reproduce..."}
structuredContent: {"ok":true,"issueNumber":1347,"url":"https://github.com/octocat/hello-world/issues/1347","state":"open","createdAt":"2026-09-23T18:00:00.000Z","demo":false,"credits":{"charged":4,"balance":496}}

sandbox_* tools are free and return fixtures with "demo": true. Their pre-rename names (e.g. send_email) still resolve for now and answer with a deprecated pointer to the new name.

What credits cost

  • Starter1,000 credits$9.00
  • Builder5,000 credits$39.00
  • Scale25,000 credits$149.00

One-time purchases in USD, excluding tax. See full pricing.

10. Security best practices for operators

  • Issue a dedicated API key for each MCP client or autonomous agent. Narrow the allowedTools list to only the tools that agent needs, and set a per-key daily or lifetime credit cap.
  • Disable any tool you do not expect your agents to use from the console /tools page. Workspace-level disables apply across every key and MCP connection.
  • Side-effecting tools (sandbox_send_email, sandbox_update_crm_record, sandbox_create_payment, sandbox_delete_record) always take two calls: an unconfirmed call that returns a preview and a single-use token, then the confirmed call. Because the token is issued by the server and bound to the previewed arguments, an agent cannot authorize itself in advance.
  • Rotate keys regularly. The rotation endpoint keeps the old key valid for 10 minutes, so you can update your agents without downtime.
  • Monitor the audit_logs table and usage page for unexpected tool names or credit spend spikes.

11. MCP server

The same catalog is exposed over Model Context Protocol for clients like Claude, Cursor and ChatGPT. Connect with OAuth 2.1 — you approve the client once, then calls are metered against your workspace credits exactly like HTTP calls. Side-effecting tools are marked with the MCP destructiveHint annotation, so compliant clients ask the human to approve the call before it runs.

https://3bi.ai/mcp   # Streamable HTTP, OAuth 2.1 (dynamic client registration)

Differences from the HTTP API: each tool description carries its credit cost, results come back as JSON text plus structuredContent with the same demo and credits fields, and failures (including an exhausted balance) surface as an MCP tool error rather than an HTTP status code. The idempotency-key header is HTTP-only. Confirmation is not: over MCP the same gate is expressed as a confirmation_token argument — the first call returns the preview and token as a tool error, the second call carries the token. Usage from MCP appears in the same console usage history.

Tool visibility note: The MCP list-tools endpoint may show the full starter catalog even when a workspace has disabled a tool. Workspace-level disables and per-key allowedTools are enforced at invocation time, so a disabled or disallowed tool returns a clear tool_disabled error instead of running.

12. Account status

GET /api/public/v1/me
{
  "ok": true,
  "orgId": "…",
  "scopes": ["tools:invoke"],
  "credits": { "balance": 500 },
  "usage": { "totalCalls": 12 },
  "rateLimit": { "perMinute": 60 }
}

Poll this before a long run, or read the balance from the console's usage page.