Documentation
API reference
Agent API keys use Authorization: Bearer bhf_sk_xxx. Dashboard routes use developer session cookies.
/api/agentsAdd a native or connected agent and return the API key once.
/api/permissionsCreate a permission for the authenticated agent.
/api/verifyEvaluate whether an agent can perform an action.
/api/actions/executeExecute an allowed safe public web read through the Action Gateway MVP.
/api/logs/[agentId]Read filtered verification logs and summaries for the authenticated agent.
/api/agents/[agentId]/rotate-keyRotate an agent API key and return the new key once.
Key management
Agent API keys and developer API tokens are shown only once when they are created or rotated. BehalfID stores hashes and safe metadata such as created date, last-used date, and rotation date. List and detail views show short previews where available, never full raw keys.
Successful agent-key or developer-token authentication updates lastUsedAt on a best-effort basis. Invalid, malformed, missing, or previously rotated keys do not update it. Rotating an agent key invalidates the old key immediately, clears last-used metadata for the newly active key, and returns the new key once.
API errors, webhook payloads, worker summaries, SDK errors, and CLI errors are expected to redact bearer tokens, agent keys, developer tokens, passport tokens, and webhook signing secrets.
Plans and quota errors
Free accounts include 5 agents, 10,000 verifications per UTC calendar month, no dashboard webhooks, and 7-day log retention. Pro includes 50 agents, 250,000 verifications per month, dashboard webhooks, and 90-day log retention. Enterprise treats agent and verification quotas as unlimited, enables webhooks, and keeps 365 days of logs.
Limit failures return stable codes with safe plan context, for example AGENT_LIMIT_REACHED, VERIFICATION_LIMIT_REACHED, or WEBHOOKS_REQUIRE_PRO. Responses include the current plan, relevant limit, and upgrade hint, but not Stripe customer IDs, subscription IDs, price IDs, or secrets.
Verification logs
Verification logs record the decision fields developers need to debug agent activity: requestId, agent, permission when matched, action, vendor or resource, amount when supplied, allowed/denied status, reason, risk, and timestamp. Optional metadata is only persisted when BEHALFID_LOG_METADATA is not false, and current list/export endpoints omit metadata.
Log endpoints support filters for agent, action, vendor/resource, allowed/denied, risk, request ID, date range, limit, and page. Dashboard reads are scoped to the authenticated developer. Console reads are admin-only and account-scoped. format=csv exports the selected safe fields.
Use requestId to connect the verify response, dashboard or console log row, CLI output, and verification webhook payload. Raw bearer tokens, agent keys, developer tokens, passport tokens, and webhook signing secrets are redacted from log responses and exports.
Agent metadata
POST /api/agents remains compatible with { "name": "Jasper Shopping Agent" }. It also accepts optional connected-agent metadata.
{
"name": "Ollie",
"agentType": "connected",
"provider": "ollie",
"externalAgentId": "optional",
"externalAgentLabel": "Jasper's Ollie assistant",
"description": "Family/personal assistant used for daily planning"
}Permission shape
A permission is an action plus constraints. The current public API keeps vendor and allowedVendors for compatibility; resourceis also accepted by /api/verify and passport preview routes as a clearer alias. amount is optional and mainly relevant to transaction-like permissions.
Agent descriptions are informational. Permissions are the source of truth for what an agent may do. Use allowedActions and blockedActions to make permissions explicit so external agents can read them from the passport page.
Enforcement is strict. Active blockedActions override allows globally for the same agent. A non-empty allowedActions list narrows the permission to those exact action strings, so verifying the broad parent action does not bypass the narrowed list. Resource and vendor constraints must match when present; missing vendor, resource, or amount values do not bypass those constraints.
{
"agentId": "agent_xxx",
"action": "access_data",
"resource": "gmail.com",
"scope": "read-only gmail access",
"allowedActions": ["read labels", "summarize messages", "provide pricing metrics"],
"blockedActions": ["send email", "delete messages", "schedule events"],
"requiresApproval": true,
"template": "access_data",
"constraints": {
"allowedVendors": ["gmail.com"],
"expiresAt": "2099-05-01T23:59:59Z"
}
}Manual passport tests
Passport routes use a separate tokenized link. Send the bhf_pass_token as a bearer token; generated UI links keep it in the URL fragment. Passport links intentionally expose the agent's allowed permission scopes so external agents can read what they are permitted to do. They cannot create permissions, rotate keys, read logs, or expose API keys, webhook secrets, developer identity, or internal IDs.
A passport token is not an API key. It only allows viewing the scoped passport and running manual preview checks for one agent. Treat it like a secret — anyone with the token can view the allowed scopes.
/api/passport/[agentId]Read safe public passport data: agent metadata and active permission scopes. Returns passportVersion, mode, agent, permissions, and limitations. Never returns API keys, logs, developer identity, or internal IDs.
/api/passport/[agentId]Run a manual allow/deny preview without exposing the agent API key. Does not write logs or trigger webhooks.
curl -X POST "$BASE_URL/api/verify" \
-H "Authorization: Bearer $BEHALFID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agentId":"agent_xxx","action":"access_data","resource":"gmail.com"}'Action Gateway
POST /api/actions/execute uses the same agent API key pattern as verify. The MVP only executes browse_web on web by fetching a public URL with GET. It verifies first, then executes only if the decision is allowed. Unsupported, denied, approval-required, or verification-error actions fail closed and are not executed.
{
"agentId": "agent_xxx",
"action": "browse_web",
"resource": "web",
"input": {
"url": "https://example.com"
}
}