Search

Search the documentation

Protobox

Platform Tooling

Run your whole tool layer on Protobox: batch execution for agent turns, per-workspace catalog curation, per-user tool attribution, scoped keys, and browser tokens.

You run a SaaS product whose users create tools, connect apps, and talk to agents — and you want Protobox to be the engine behind all of it. This page covers the surface built for that shape of customer: your backend is the principal (one API key = one workspace), each of your users is an actor, and every capability below keeps their data partitioned by that actor.

Coming from the agent side? Framework adapters covers handing tools to a model; this page covers running the platform around it.

Execute a whole model turn in one round-trip

When a model emits several tool calls in one turn, send them as one batch instead of N serial requests. Items fail independently — a bad call lands in its own slot and its siblings still run — and results keep input order, each carrying actionId so you can pair them back to the model's tool_use blocks.

const protobox = new ProtoboxSDK({ apiKey: process.env.PROTOBOX_API_KEY! });

const results = await protobox.tools.executeBatch(
  [
    { name: 'GITHUB_CREATE_ISSUE', arguments: { title: 'Ship it' } },
    { name: 'GMAIL_SEND', arguments: { to: 'team@acme.dev', subject: 'Shipped' } },
  ],
  { entityId: 'user_123' }, // the actor whose credentials execute every item
);

for (const result of results) {
  console.log(result.actionId, result.success ? 'ok' : result.error);
}

Curate which apps your users see

Your workspace decides which catalog connectors exist for your product. Disabling a connector keeps it listed (so your admin surface can re-enable it) but flags it enabled: false and refuses its actions at execute time with CONNECTOR_DISABLED — before any upstream call.

await protobox.connectors.disable('salesforce');   // curated off, not deleted

const { items } = await protobox.connectors.list();
items.find((c) => c.slug === 'salesforce')?.enabled; // → false

await protobox.connectors.enable('salesforce');    // back in the catalog

Remember which user created a tool

When your users author tools in your UI, register each one with the creating actor. The connector reads back ownerActorId, and the list filters by it — so "your tools" views and scoped deletion need no bookkeeping on your side.

await protobox.connectors.registerOpenApi({
  slug: 'acme-crm',
  spec: openApiSpec,
  actorId: 'user_123', // who created it
});

const mine = await protobox.connectors.list({ actorId: 'user_123' });
// only user_123's connectors — the catalog and other users' tools are excluded

Scope a key to a job

Scoping is opt-in: a key carrying any connectors:* verb — or a connectorAllowlist — is scoped, and the platform then requires the matching verb per route family, so an agent-runtime key can execute but never register. The allowlist pins a key to specific connectors (CONNECTOR_NOT_ALLOWED on anything else). Keys minted before these verbs existed — including tools:read/tools:execute-only keys — behave exactly as before.

// POST /api/v1/api-keys (with your admin credentials)
{
  "name": "agent-runtime",
  "permissions": ["tools:read", "tools:execute", "connectors:read"],
  "connectorAllowlist": ["github", "gmail"]
}
VerbGrants
tools:readList/search actions, read executions
tools:executeExecute actions (single + batch)
connectors:readRead the connector catalog
connectors:writeRegister, sync, unregister, enable/disable connectors and toolsets

Let the browser read directly

List-the-catalog and show-the-logs reads don't need to proxy through your backend. Mint a short-lived (≤15 min), read-only token bound to one actor and hand it to your frontend; it calls Protobox directly with the X-Gateway-Token header. Writes are refused, and execution-log reads are pinned to the bound actor — asking for another user's history is a 403.

// Your backend (private API key):
// POST /api/v1/actor-tokens  { "userId": "user_123", "ttlSeconds": 300 }
// → { token, expiresAt, scopes: ["tools:read", "connectors:read"] }

// Your frontend:
const res = await fetch('https://platform.protobox.ai/api/v1/tools/integrations', {
  headers: { 'X-Gateway-Token': token },
});

Revocation is by expiry only — that is why the TTL is capped. Mint per page-load, not per session.

Hear about changes instead of polling

Register one webhook endpoint and receive signed events: connection.activated / connection.revoked / connection.expired when users connect apps, execution.completed / execution.failed for run outcomes, and connector.registered when a connector lands in your workspace (your cue to invalidate any cached tool definitions). Verify every delivery:

import { verifyWebhook } from '@protoboxai/sdk';

app.post('/webhooks/protobox', (req, res) => {
  const event = verifyWebhook(req.rawBody, req.headers['x-protobox-signature'], {
    secret: process.env.PROTOBOX_WEBHOOK_SECRET!, // returned once by webhooks.register
  });
  if (event.type === 'connector.registered') invalidateToolCache(event.workspaceId);
  res.sendStatus(200);
});

Python: the execute-core

Python agent runtimes (voice bots, workers) get the same execute contract from the protobox package — stdlib-only, so a container adopts it without new dependencies. Full parity follows; today it covers list, search, execute, and batch.

from protobox import Protobox

protobox = Protobox(api_key=os.environ["PROTOBOX_API_KEY"])
user = protobox.entity("user_123")   # in-band actor scoping, same as TypeScript

tools = user.tools.list_tools(integration_id="github")
result = user.tools.execute(name=tools[0]["name"], arguments={"title": "Hi"})

results = user.tools.execute_batch([
    {"name": "GITHUB_CREATE_ISSUE", "arguments": {"title": "Hi"}},
    {"name": "GMAIL_SEND", "arguments": {"to": "a@b.c"}},
])

Errors are typed (AuthenticationError, RateLimitError, …) in both languages, and neither SDK retries — your runtime owns backoff.

On this page