BoundID
Documentation

BoundID API

Passports, verifications, delegations and signed decisions for AI agents. JSON over HTTPS, EIP-712 everywhere a signature matters.

Base URLhttps://useboundid.com/api/v1

Introduction#

BoundID answers a scoped question for any app that lets an AI agent move money: does this request satisfy this owner's policy, using evidence that is acceptable right now? Answers are ALLOW, DENY or INDETERMINATE, with reason codes and a receipt signed for 60 seconds.

Passport

Who controls the agent, which issuers vouch for it, and what each claim does not prove.

Delegation

The owner's EIP-712 signature over asset, recipients, caps and expiry.

Guard

Enforcement at execution: nonce, budget and revocation checked on the exact call.

The public sandbox runs the full engine with real signing keys, test assets and sample operators. Signatures are scoped to Robinhood Chain (chain ID 4663), and receipts reference the live block they were evaluated against.

Quickstart#

  1. 1
    Read a passport
    curl https://useboundid.com/api/v1/passports/bnd:4663:1
  2. 2
    Evaluate a payment

    Run it here. Change the amount or recipient to see AMOUNT_EXCEEDS_TX_LIMIT and RECIPIENT_NOT_ALLOWED.

curl https://useboundid.com/api/v1/evaluations \
  -H "content-type: application/json" \
  -d '{
    "agentRef": "bnd:4663:1",
    "request": {
      "to": "0xCcb26681DcF04FDF9Ab1F828781d77D85ECdB7e3",
      "amount": "84000000"
    }
  }'
to
Response

Run the request against the live API.

Try an unlisted recipient or more than 100 USDG.

Receipts are EIP-712 signed and valid for 60 seconds.
  1. 3
    Register your own agent

    Open the Console, sign in with any EVM wallet or a sandbox wallet, and run the pilot: register, attest, delegate and send guarded payments, each with a real signature.

Concepts#

Agent references

Agents are addressed as bnd:{chainId}:{agentId}, for example bnd:4663:1042. The API also accepts the bare numeric ID. A reference alone is never enough to trust a credential: every claim and delegation also names the binding version it was issued for.

Claims and profiles

There is no single VERIFIED badge. A passport shows separate, dated claims, and relying apps choose which to accept.

ProfileRequired evidenceDoes not establish
RegisteredAgent identifier and declared endpoint metadataReal-world identity, authority or safety
Control checkedFresh signatures from owner and execution keys, endpoint challengeThe legal identity of the controller
Operator attestedAccepted issuer credential linking an operator to the current bindingPermission for any particular transaction
Policy eligibleCurrent operator claim plus an unexpired delegation matching the requestExecution, unless the receiving system enforces the policy

Credential lifecycle

Credentials move through pending, active, suspended, revoked and expired. Suspension blocks reliance while an issue is reviewed. Revocation is permanent; remediation means a new issuance. Expiry is evaluated from the signed validity window, even if nothing updates stored state.

Any owner or execution-key change increments the binding version. Claims and delegations signed for an older binding return BINDING_STALE; a new owner never inherits old permissions.

WindowDefaultNotes
Operator claim30 daysFreshness ceiling is set by the relying app
Delegation24 hoursUp to 7 days in the sandbox
Decision receipt60 secondsRevocation overrides every window

Delegations

A delegation is the owner's EIP-712 signature over exactly what the agent may do: one asset, an allowlist of function selectors (the pilot allows transfer(address,uint256) only), approved recipients, a per-transaction limit and a per-UTC-day budget in base units, and a validity window. Its EIP-712 digest is the policy hash. Versions increase monotonically and the newest one is in force.

Decisions and receipts

DENY wins over INDETERMINATE, and missing or unreachable evidence is never an implicit ALLOW. Every response carries a receipt bound to the request hash, policy version, binding version, issuer-set version and the Robinhood Chain block it was evaluated at, signed by the API key published at /v1/status.

import { verifyTypedData } from "viem";

const status = await fetch("https://useboundid.com/api/v1/status").then((r) => r.json());

const valid = await verifyTypedData({
  address: status.signing.receiptSigner,
  domain: { name: "BoundID", version: "1", chainId: 4663 },
  types: { EvaluationReceipt: [/* fields above */] },
  primaryType: "EvaluationReceipt",
  message: receiptMessage, // receipt fields, times in unix seconds
  signature: receipt.signature,
});

Authentication#

Sandbox requests work without a key at 60 per minute per IP. Create a key in the Console to raise evaluations to 1,200 per minute and tie usage to your account. Keys look like bnd_test_…; only a hash is stored.

curl https://useboundid.com/api/v1/passports/bnd:4663:1 \
  -H "authorization: Bearer bnd_test_…"

Mutations are authorized by signatures, not keys: registration by the owner and agent keys, delegations and revocations by the owner, executions by the agent key.

API reference#

GET/v1/status

Service and chain status

Current Robinhood Chain block, the receipt signer address and key ID, the BoundID Verify issuer key and the issuer-set version. Pin the signer address to verify receipts offline.

Response
{
  "object": "status",
  "api": "operational",
  "chain": { "chainId": 4663, "blockNumber": 66712345, "blockTimeMs": 100 },
  "signing": {
    "domain": { "name": "BoundID", "version": "1", "chainId": 4663 },
    "receiptSigner": "0x…",
    "receiptKeyId": "bnd-api-…",
    "issuer": { "id": "bnd-verify", "address": "0x…", "keyEpoch": 3 },
    "issuerSetVersion": 7
  }
}
POST/v1/agents

Start registration

Reserves an agent reference and returns an EIP-712 BindAgent challenge. The owner and the agent's execution key must be different addresses. The challenge expires in 10 minutes.

Request body
{
  "name": "Invoice Agent",
  "description": "Pays approved supplier invoices",
  "endpoint": "https://agents.example.com/invoice",
  "owner": "0xb9159EFfD7aD1c885424aa5bAc9986Dc8cfD28d7",
  "executionKey": "0xC27386A9b9683AE0A2400E4212882AEEdeEA584C"
}
Response
{
  "object": "registration",
  "agentRef": "bnd:4663:1042",
  "status": "pending",
  "bindingVersion": 1,
  "challenge": "0x6c1f…9a02",
  "expiresAt": 1789790400,
  "sign": { "standard": "EIP-712", "signers": ["owner", "executionKey"], "typedData": { "primaryType": "BindAgent" } },
  "next": "POST /api/v1/agents/bnd:4663:1042/bind"
}
POST/v1/agents/{agentRef}/bind

Complete registration

Submit both signatures over the challenge. BoundID Verify checks they recover to the owner and the execution key, activates the agent and issues a signed control credential.

Request body
{
  "ownerSignature": "0x…",
  "agentSignature": "0x…"
}
Response
{
  "object": "binding",
  "agentRef": "bnd:4663:1042",
  "status": "active",
  "bindingVersion": 1,
  "credential": { "id": "crd_…", "claim": "control", "status": "active", "issuer": "bnd-verify", "signature": "0x…" }
}
GET/v1/passports/{agentRef}

Read a passport

Public claims and current status. Each claim names its issuer, method, validity window and what it does not establish. Identity evidence never appears here.

Response
{
  "object": "passport",
  "agent": { "ref": "bnd:4663:1", "name": "Acme Invoice Agent", "bindingVersion": 3 },
  "claims": [
    { "profile": "registered", "state": "verified" },
    { "profile": "control", "state": "verified", "issuer": "BoundID Verify" },
    { "profile": "operator", "state": "verified", "issuer": "BoundID Verify", "expiresAt": 1791331200000 },
    { "profile": "policy", "state": "verified", "method": "Delegation v3 · policy 0x9b1e…" }
  ],
  "delegation": { "version": 3, "state": "active", "policyHash": "0x9b1e…c44a" },
  "issuerSetVersion": 7
}
POST/v1/verifications

Request an operator attestation

Starts a BoundID Verify review. Reviews complete asynchronously; poll GET /v1/verifications/{id}. The claim binds to the current binding version and lasts 30 days.

Request body
{
  "agentRef": "bnd:4663:1042",
  "claim": "operator",
  "operator": "Acme Operations",
  "jurisdiction": "US-DE"
}
Response
{
  "object": "verification",
  "id": "crd_…",
  "status": "pending",
  "issuer": "bnd-verify",
  "estimatedCompletion": 1789790406000
}
POST/v1/delegations

Record a delegation

Submit an owner-signed EIP-712 Delegation. Versions increase by one; the newest version is the one in force. The sandbox caps windows at 7 days.

Request body
{
  "agentRef": "bnd:4663:1042",
  "version": 1,
  "nonce": "0x…32 bytes",
  "policy": {
    "asset": { "symbol": "USDG", "address": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", "decimals": 6 },
    "selectors": ["0xa9059cbb"],
    "recipients": [{ "address": "0xCcb26681DcF04FDF9Ab1F828781d77D85ECdB7e3", "label": "Northwind Supply" }],
    "perTxLimit": "100000000",
    "dailyLimit": "500000000",
    "validFrom": 1789790400,
    "validUntil": 1789876800
  },
  "signature": "0x…"
}
Response
{
  "object": "delegation",
  "id": "dlg_…",
  "version": 1,
  "policyHash": "0x9b1e…c44a",
  "state": "active"
}
POST/v1/evaluations

Evaluate a request

Evaluates one call against current evidence and the active delegation. Without a signature it is a preflight. With the agent's EIP-712 ExecutionRequest signature and execute: true, the guard consumes the nonce and updates the UTC-day spend counter atomically.

Request body
{
  "agentRef": "bnd:4663:1042",
  "request": {
    "to": "0xCcb26681DcF04FDF9Ab1F828781d77D85ECdB7e3",
    "amount": "84000000",
    "nonce": "0x…32 bytes",
    "deadline": 1789790520
  },
  "signature": "0x…",
  "execute": true
}
Response
{
  "object": "evaluation",
  "mode": "signed",
  "decision": "ALLOW",
  "reasonCodes": ["POLICY_MATCH"],
  "execution": { "status": "EXECUTED", "spentToday": "84000000" },
  "receipt": {
    "id": "rcpt_…",
    "requestHash": "0x…",
    "policyVersion": 1,
    "agentBindingVersion": 1,
    "issuerSetVersion": 7,
    "evaluatedBlockNumber": 66712345,
    "evaluatedBlockHash": "0x…",
    "validUntil": 1789790460000,
    "signerKeyId": "bnd-api-…",
    "signature": "0x…"
  },
  "latencyMs": 38
}
POST/v1/revocations

Revoke a delegation

Owner-signed and permanent. Every later evaluation of that delegation returns DENY with DELEGATION_REVOKED, including requests that hold an unexpired receipt.

Request body
{
  "delegationId": "dlg_…",
  "reason": "Rotating vendors",
  "issuedAt": 1789790600,
  "signature": "0x…"
}
Response
{
  "object": "revocation",
  "delegationId": "dlg_…",
  "version": 1,
  "revokedAt": 1789790600412
}
POST/v1/receipts/verify

Verify a receipt

Checks a receipt's EIP-712 signature against the API signer. You can do the same offline with any EIP-712 library and the signer from /v1/status.

Request body
{ "receipt": { "id": "rcpt_…", "decision": "ALLOW", "": "…" } }
Response
{
  "object": "receipt_verification",
  "validSignature": true,
  "signer": "0x…",
  "expired": false
}
GET/v1/registry · /v1/issuers · /v1/activity

Registry, issuers and activity

Public listings: registry agents with claim states, the issuer set with key epochs, and the recent decision stream.

Response
{ "object": "list", "data": [] }

EIP-712 types#

Every signature uses the BoundID domain on chain 4663. Times inside signed messages are unix seconds.

// Domain
{ name: "BoundID", version: "1", chainId: 4663 }

BindAgent(string agentRef, address owner, address executionKey,
          uint64 bindingVersion, bytes32 challenge, uint64 expiresAt)

Delegation(string agentRef, address agentKey, uint64 bindingVersion,
           address asset, bytes4[] selectors, address[] recipients,
           uint256 perTxLimit, uint256 dailyLimit,
           uint64 validFrom, uint64 validUntil, uint32 version, bytes32 nonce)

ExecutionRequest(string agentRef, address to, address asset, uint256 amount,
                 bytes4 selector, bytes32 nonce, uint64 deadline)

Revocation(string delegationId, bytes32 policyHash, string reason, uint64 issuedAt)

Credential(string credentialId, string agentRef, string kind, uint64 bindingVersion,
           bytes32 claimHash, uint64 issuedAt, uint64 expiresAt, uint32 keyEpoch)

EvaluationReceipt(string receiptId, string decision, string[] reasonCodes,
                  bytes32 requestHash, uint32 policyVersion, uint64 agentBindingVersion,
                  uint32 issuerSetVersion, uint64 evaluatedBlockNumber,
                  bytes32 evaluatedBlockHash, uint64 evaluatedAt, uint64 validUntil,
                  string audience, bytes32 nonce)

Reason codes#

CodeDecisionMeaning
POLICY_MATCHALLOWEvery check passed against the current policy and evidence.
AGENT_NOT_FOUNDDENYNo agent is registered under this reference.
AGENT_SUSPENDEDDENYThe agent is suspended in the registry.
BINDING_STALEDENYOwner or execution key changed since this evidence was signed. Old permissions do not carry over.
CONTROL_UNVERIFIEDDENYOwner and execution keys have not both proven control.
OPERATOR_UNATTESTEDDENYNo accepted issuer has attested the operator.
CREDENTIAL_SUSPENDEDDENYThe issuer suspended this credential while it is reviewed.
CREDENTIAL_REVOKEDDENYThe credential was revoked. Remediation requires a new issuance.
CREDENTIAL_EXPIREDDENYThe credential's signed validity window has ended.
ISSUER_NOT_ACCEPTEDDENYThe issuer is not in this relying party's accepted set.
ISSUER_SUSPENDEDDENYThe issuer's key is suspended in IssuerRegistry.
NO_ACTIVE_DELEGATIONDENYThe owner has not signed a delegation for this agent.
DELEGATION_REVOKEDDENYThe owner revoked the delegation. Revocation overrides every window.
DELEGATION_EXPIREDDENYThe delegation's validity window has ended.
DELEGATION_NOT_YET_VALIDDENYThe delegation's validity window has not started.
ACTION_NOT_PERMITTEDDENYThe function selector is not in the policy allowlist.
ASSET_NOT_PERMITTEDDENYThe policy does not cover this asset.
RECIPIENT_NOT_ALLOWEDDENYThe recipient is not on the owner's approved list.
AMOUNT_EXCEEDS_TX_LIMITDENYThe amount is above the per-transaction limit.
DAILY_BUDGET_EXCEEDEDDENYThe amount would exceed the remaining UTC-day budget.
REQUEST_EXPIREDDENYThe request deadline has passed.
NONCE_REUSEDDENYThis nonce was already consumed. Replays are rejected.
SIGNATURE_INVALIDDENYThe execution key's signature does not match the request.
EVIDENCE_PENDINGINDETERMINATEAn issuer check is still in progress.
EVIDENCE_STALEINDETERMINATEThe claim is older than this relying party's freshness ceiling.
STATUS_UNAVAILABLEINDETERMINATECurrent chain or registry status could not be established. The request fails closed.

Errors#

Errors return a JSON body with a stable code. Every response carries an x-request-id header.

{ "error": { "code": "invalid_signature", "message": "Delegation must be signed by the agent's owner (EIP-712).", "docs": "https://useboundid.com/docs#errors" } }
StatusCodeWhen
400invalid_requestMalformed JSON, missing fields or out-of-range values
401invalid_signatureA signature does not recover to the required key
401unauthorizedInvalid or revoked API key
403read_onlyMutating a sample agent
404not_foundUnknown agent, verification or delegation
409conflictWrong delegation version, already bound, already revoked
410expiredRegistration challenge expired
429rate_limitedToo many requests; honor retry-after

Contracts#

Core contracts are immutable and versioned; administrative actions use separate keys, and an emergency pause can stop new issuance or execution but never blocks revocation. Addresses and bytecode hashes are published here after deployment and independent review.

ComponentResponsibilityRelease
IdentityAdapterResolve a pinned ERC-8004 registry, current owner and wallet bindingMVP
IssuerRegistryIssuer keys, permitted claim types, key epochs, suspensionMVP
CredentialStatusRegistryOpaque credential IDs and public status, no identity evidenceMVP
DelegationRegistryOwner-authorized policy hashes, revocations, binding versionsMVP
ExecutionGuardPermission checks, atomic spend and replay protectionMVP
ProviderBondVaultProvider collateral reserved against open obligationsLater
DisputeManagerAdjudicated outcomes and bounded penaltiesLater
interface IExecutionGuard {
    struct Call {
        address to;
        address asset;
        uint256 amount;
        bytes4 selector;
        bytes32 nonce;
        uint64 deadline;
    }

    /// Reverts with a reason code unless the delegation allows this exact call.
    function execute(bytes32 agentRef, Call calldata call, bytes calldata agentSig) external;

    /// Owner-signed and permanent.
    function revoke(bytes32 delegationId, bytes calldata ownerSig) external;

    function spentToday(bytes32 delegationId) external view returns (uint256);
}

Trust model#

BoundID starts with named, approved issuers and says so. It is not permissionless verification yet. Credentials are scoped evidence for a relying party's decision: they do not guarantee an agent's conduct, prove a model is honest, or replace human and business identity checks where those are required. Revocation does not erase public-chain history, and on-chain policy constraints are public.

Read the whitepaper for the full specification, threat model and acceptance tests.