Quickstart

Documentation

Quickstart

Create an agent, add one permission, verify before execution, and prove both allowed and denied actions in about five minutes.

The five-minute model

BehalfID sits between the AI agent and the tool it wants to run. Your code asks BehalfID first. If the decision is not allowed, the executor does not run.

  1. Create an agent. Use /dashboard/onboarding or behalf agents create. Store the one-time bhf_sk_... API key as BEHALFID_API_KEY.
  2. Create a permission. Start with one clear rule, such as browse_web on web, read_calendar on google-calendar, or purchase on amazon.com with maxAmount: 25.
  3. Install the SDK. Add the published Node SDK to the app that owns the tool execution.
  4. Call verify before the action. The SDK requires agentId, action, and the API key. Pass vendor or resource when a permission is scoped to a service.
  5. Show an allowed request. Call verify with an action and resource covered by an active permission.
  6. Show a denied request. Try a blocked action, a missing permission, a missing constrained vendor/resource, or an amount over the limit.
  7. Fail closed. Throw or return before the executor. Never run the tool when decision.allowed is false.
terminal
npm install @behalfid/sdk

Copy-paste executor pattern

purchase.ts
import { BehalfID } from "@behalfid/sdk";

const behalf = new BehalfID({
  apiKey: process.env.BEHALFID_API_KEY!,
});

const agentId = process.env.BEHALFID_AGENT_ID!;

async function purchase(vendor: string, amount: number) {
  const decision = await behalf.verify({
    agentId,
    action: "purchase",
    vendor,
    amount,
  });

  if (!decision.allowed) {
    throw new Error(`Blocked by BehalfID: ${decision.reason}`);
  }

  return runPurchase({ vendor, amount });
}

Allowed request

This succeeds when the agent has an active browse_web permission for web and no matching blocked action.

allowed.ts
const decision = await behalf.verify({
  agentId: process.env.BEHALFID_AGENT_ID!,
  action: "browse_web",
  resource: "web",
});

if (decision.allowed) {
  await runBrowserRead("https://example.com");
}
allowed response
{
  "requestId": "req_xxx",
  "allowed": true,
  "reason": "Action allowed by active permission.",
  "risk": "low"
}

Denied request

This fails closed when the purchase permission is missing, the vendor does not match, blockedActions includes purchase, approval is required, or the amount exceeds the permission limit.

denied.ts
const decision = await behalf.verify({
  agentId: process.env.BEHALFID_AGENT_ID!,
  action: "purchase",
  vendor: "shop.example",
  amount: 742,
});

if (!decision.allowed) {
  throw new Error(`Blocked by BehalfID: ${decision.reason}`);
}

await runCheckout(); // not reached when denied
denied response
{
  "requestId": "req_xxx",
  "allowed": false,
  "reason": "Amount exceeds maxAmount constraint.",
  "risk": "high"
}

Create permission with the SDK

permission.ts
await behalf.createPermission({
  agentId: process.env.BEHALFID_AGENT_ID!,
  action: "purchase",
  resource: "shop.example",
  allowedActions: ["purchase"],
  blockedActions: ["checkout without approval"],
  requiresApproval: false,
  constraints: {
    maxAmount: 25,
    allowedVendors: ["shop.example"],
  },
});

Manual mode vs enforcement

Passport links and manual preview forms help existing assistants understand the rules, but they do not control a provider directly. Automatic enforcement happens when your app, MCP server, or Action Gateway calls BehalfID before the action and refuses to run denied tools.

For a runnable end-to-end version of this loop, use examples/enforcement-demo. It creates demo permissions, runs allowed and denied actions, and checks the resulting audit log entries.