Documentation
Record what your AI agent is allowed to do. One line of code.
Quickstart
Get up and running in under 2 minutes.
1. Install the SDK
pip install agentcheck-sdk
2. Get your API key
from agentcheck import Client result = Client.signup( email="[email protected]", company_name="Your Company", base_url="https://agentcheck.spaceplanning.work" ) print(result["api_key"]) # Save this! Shown only once.
ak_live_. Store it securely. It cannot be retrieved after signup.3. Create your first agreement
import agentcheck agentcheck.init( api_key="ak_live_your_key_here", base_url="https://agentcheck.spaceplanning.work" ) proof = agentcheck.record( agent="my-bot", scope="read database, send reports", authorized_by="[email protected]" ) print(proof.id) # Agreement ID print(proof.status) # "pending" until approved print(proof.verify_url) # Public verification link
Approval Flow
When you create an agreement, the authorized_by email receives a message with an "Approve" button. No account creation needed - one click approves.
After approval:
record = agentcheck.get(proof.id) print(record.status) # "approved" print(record.approved_at) # timestamp
Public Verification
Every agreement has a public verification page that anyone can access:
https://agentcheck.spaceplanning.work/api/v1/verify/{agreement_id}
The page shows the agreement details, approval status, HMAC signature validity, and a QR code for physical display.
Amendments
Need to change the scope? Amend the agreement:
amended = agentcheck.amend( agreement_id=proof.id, new_scope="read database, send reports, deploy to staging", reason="Added staging deployment permission" ) # Sends re-approval email to the authorizer
Webhooks
Get notified in real-time when agreements are approved or amended:
client = agentcheck.Client(
api_key="ak_live_...",
base_url="https://agentcheck.spaceplanning.work"
)
webhook = client.register_webhook(
url="https://your-server.com/webhook",
events=["record.approved", "record.amended"]
)
print(webhook["secret"]) # whsec_... for HMAC verification
Verifying webhook signatures
from agentcheck.webhook import WebhookHandler handler = WebhookHandler(secret="whsec_...") # In your webhook endpoint: event = handler.verify_and_parse( body=request.body, signature=request.headers["X-AgentCheck-Signature"] ) print(event.type) # "record.approved" print(event.data) # agreement details
Agreement Lifecycle
Every agreement follows this state machine:
email click
pending ──────────────────────────→ approved
│ │
│ expires_at passed │ POST /revoke
↓ ↓
expired revoked
│
↑ │
│ expires_at passed │
│ │
approved ────────────────────────────┘
POST /amend (on approved) → back to pending (re-approval needed)
approved or revoked, its core fields (agent, scope, authorized_by, signature) cannot be modified. Only status transitions are allowed. This is enforced at the database level with triggers.
Status descriptions
| Status | Meaning | Transitions to |
|---|---|---|
pending | Created, waiting for email approval | approved, expired |
approved | Authorizer clicked "Approve" in email | revoked, expired, pending (via amend) |
revoked | Explicitly revoked by API key owner | None (terminal) |
expired | Past expires_at date (auto-detected every 5 min) | None (terminal) |
Revoking Agreements
Immediately revoke an agreement when an agent should no longer have authorization:
# Python agentcheck.revoke( agreement_id="abc123", reason="Security incident - agent compromised" ) // TypeScript / Node.js await fetch(`${BASE_URL}/api/v1/record/${id}/revoke`, { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: 'Security incident' }) })
revoked and a record.revoked webhook is fired.Integration Guide
How to integrate AgentCheck into your system. This section covers what you need to build on your side.
What AgentCheck does vs. what you do
| Responsibility | AgentCheck | Your System |
|---|---|---|
| Create delegation record | API provided | Call our API |
| Tamper-proof signature | Automatic (HMAC-SHA256) | Nothing to do |
| Send approval email | Automatic (SendGrid) | Nothing to do |
| Store records immutably | PostgreSQL + DB triggers | Nothing to do |
| Public verification page | /verify/:id with QR | Nothing to do |
| Expiry detection | Auto-scan every 5 min | Nothing to do |
| Adapter (API field mapping) | - | You build this |
| Action verification (is this in scope?) | - | You build this |
| Approve flow UX | Email link | Handle async approval |
| Webhook receiver | Sends events | You receive and process |
Building an Adapter
Your system likely has its own delegation/authorization model. You need an adapter - a thin layer that translates between your model and the AgentCheck API.
// TypeScript example: Adapter pattern interface YourDelegationProvider { create(agentId: string, scope: object, delegator: string): Promise<Delegation> revoke(id: string, reason: string): Promise<void> verify(id: string): Promise<boolean> } // Your adapter that wraps AgentCheck class AgentCheckAdapter implements YourDelegationProvider { private baseUrl: string; private apiKey: string; async create(agentId: string, scope: object, delegator: string) { // Map YOUR fields to AgentCheck fields const resp = await fetch(`${this.baseUrl}/api/v1/record`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ agent: agentId, // your agentId -> our agent scope: JSON.stringify(scope), // your object -> our string authorized_by: delegator, // your delegator -> our authorized_by }) }); return resp.json(); } async revoke(id: string, reason: string) { await fetch(`${this.baseUrl}/api/v1/record/${id}/revoke`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ reason }) }); } async verify(id: string) { const resp = await fetch(`${this.baseUrl}/api/v1/record/${id}`, { headers: { 'X-API-Key': this.apiKey } }); const data = await resp.json(); return data.data.status === 'approved'; } }
Understanding the Approval Flow
AgentCheck uses email-based approval, which is fundamentally different from synchronous API approval:
| Synchronous (your local system) | AgentCheck (email approval) | |
|---|---|---|
| Flow | POST /approve -> instant | Email sent -> human clicks -> webhook fires |
| Speed | Milliseconds | Minutes to hours |
| Evidence | Internal log only | Email receipt + IP + timestamp + signature |
| Legal weight | Weak (self-signed) | Strong (third-party record + human action) |
How to handle async approval in your system:
// 1. Create agreement (status = pending) const proof = await adapter.create(agent, scope, email); // Agent should NOT act yet // 2. Register webhook to get notified // POST /api/v1/webhooks { url: "https://your-server/webhook", events: ["record.approved"] } // 3. In your webhook handler: app.post('/webhook', (req, res) => { if (req.body.type === 'record.approved') { // NOW the agent can act enableAgent(req.body.data.agreement_id); } }); // 4. Or poll periodically: const record = await adapter.verify(proof.id); if (record) { enableAgent(proof.id); }
Action Verification (Your Responsibility)
AgentCheck verifies that a delegation exists and is authentic. It does NOT verify whether a specific action falls within the scope. That logic depends on your business rules.
// This is YOUR code, not AgentCheck's function isActionInScope(agreement, action) { // Parse the scope (you define the format) const scope = JSON.parse(agreement.scope); // Your business logic if (action.type === 'purchase' && action.amount > scope.maxAmount) { return false; // Over limit } return true; } // Full verification flow in your system: async function canAgentAct(agentId, action) { // 1. Get delegation from AgentCheck (is it valid + authentic?) const records = await fetch(`${BASE_URL}/api/v1/records?agent=${agentId}&status=approved`); const delegation = records.data.records[0]; if (!delegation) return false; // No valid delegation // 2. Check if action is in scope (YOUR logic) return isActionInScope(delegation, action); }
Integration Scenarios
Scenario 1: AI Agent Framework (LangChain, CrewAI, AutoGen)
# Before your agent executes any tool, check delegation import agentcheck def execute_tool(agent_id, tool_name, params): # Check if agent has valid delegation records = agentcheck.list(agent=agent_id, status="approved") if not records.records: raise PermissionError(f"No valid delegation for {agent_id}") # Your scope check delegation = records.records[0] if not is_in_scope(delegation.scope, tool_name, params): raise PermissionError(f"Action {tool_name} not in scope") # Proceed with tool execution return run_tool(tool_name, params)
Scenario 2: ERP / Business System
// AI agent in your ERP wants to create a purchase order async function createPurchaseOrder(agentId, order) { // 1. Check delegation exists and is approved const resp = await fetch( `${BASE_URL}/api/v1/records?agent=${agentId}&status=approved`, { headers: { 'X-API-Key': API_KEY } } ); const delegations = (await resp.json()).data.records; if (!delegations.length) throw new Error('No delegation'); // 2. Your scope check: is this order within limits? const scope = delegations[0].scope; if (order.amount > parseMaxAmount(scope)) { throw new Error(`Order $${order.amount} exceeds delegation limit`); } // 3. Create the order (agent is authorized) return db.purchaseOrders.create(order); }
Scenario 3: DevOps / CI-CD Pipeline
# In your deployment script import agentcheck agentcheck.init(api_key=os.environ["AGENTCHECK_API_KEY"]) def deploy(bot_name, environment): records = agentcheck.list(agent=bot_name, status="approved") if not records.records: print(f"BLOCKED: {bot_name} has no delegation") sys.exit(1) scope = records.records[0].scope if environment == "production" and "production" not in scope: print(f"BLOCKED: {bot_name} not authorized for production") sys.exit(1) # Delegation verified - proceed with deployment run_deployment(environment)
Scenario 4: Customer Support Bot
// Support bot handling refunds async function handleRefund(botId, customerId, amount) { // Check if bot has approved delegation const records = await fetch( `${BASE_URL}/api/v1/records?agent=${botId}&status=approved`, { headers: { 'X-API-Key': API_KEY } } ); const delegation = (await records.json()).data.records[0]; if (!delegation) { return { error: 'Bot not authorized. Contact admin.' }; } // Your limit check if (amount > 500) { return { error: 'Amount exceeds bot refund limit. Escalating to human.' }; } // Process refund with audit trail return processRefund(customerId, amount, delegation.id); // Anyone can verify: /verify/{delegation.id} }
Scenario 5: Multi-Agent System
# Multiple agents, each with different scopes import agentcheck agentcheck.init(api_key="ak_live_...") # Register delegations for each agent monitor = agentcheck.record( agent="monitor-bot", scope="read server metrics, send alerts", authorized_by="[email protected]" ) deployer = agentcheck.record( agent="deploy-bot", scope="deploy to staging only, rollback on failure", authorized_by="[email protected]" ) orderer = agentcheck.record( agent="procurement-bot", scope="order supplies under $5K, approved vendors only", authorized_by="[email protected]" ) # Each agent's delegation is independently approved via email # Each has a public verification page with QR code # Each can be independently revoked if compromised
Audit Trail (Hash Chain)
Every agreement event is recorded in a tamper-proof hash chain. Each entry links to the previous one cryptographically.
모든 위임장 이벤트가 변조 방지 해시 체인에 기록됩니다. 각 항목이 이전 항목에 암호학적으로 연결됩니다.
How it works / 작동 원리
Entry #1: hash = SHA256("genesis" + data1), sig = HMAC(hash1) Entry #2: hash = SHA256(hash1 + data2), sig = HMAC(hash2) Entry #3: hash = SHA256(hash2 + data3), sig = HMAC(hash3) If any entry is tampered with, all subsequent hashes break. 하나라도 변조하면 이후 모든 해시가 깨집니다.
Three layers of protection / 3중 보호
| Layer | What it does | What it proves |
|---|---|---|
| Hash Chain | Each entry contains SHA-256 of previous entry | No entries inserted, deleted, or reordered |
| DB Triggers | UPDATE/DELETE physically blocked | Cannot modify after storage |
| HMAC Signature | Server signs each entry | Entry was created by AgentCheck server |
Recorded events / 기록되는 이벤트
| Event | When |
|---|---|
record.created | Agreement created |
record.approved | Email approval clicked |
record.revoked | Agreement revoked |
record.amended | Scope changed |
record.expired | Auto-expired by server |
Get audit trail / 감사 추적 조회
# Python trail = client.get_audit_trail("agreement-id") for entry in trail["entries"]: print(f"{entry['event_type']} | hash: {entry['entry_hash'][:16]}...") // TypeScript const trail = await client.getAuditTrail("agreement-id"); trail.entries.forEach(e => console.log(e.event_type, e.entry_hash));
Verify chain integrity / 체인 무결성 검증
# Python - customer can verify independently result = client.verify_audit_chain("agreement-id") print(result["valid"]) # True = chain intact print(result["total_entries"]) # number of entries // TypeScript const result = await client.verifyAuditChain("agreement-id"); console.log(result.valid); // true
왜 중요한가: 규제기관이 "3월 15일에 이 에이전트가 승인됐다는 것을 증명하라"고 하면, 감사 추적을 제공합니다. 해시 체인은 사후에 항목이 추가/삭제되지 않았음을 증명합니다.
Safety Stack (Multi-Layer Verification)
Combine multiple verification layers for defense-in-depth. Each layer catches what the previous one misses.
다중 레이어 검증을 조합하여 심층 방어합니다. 각 레이어가 이전 레이어가 놓친 것을 잡습니다.
| Layer | What it catches | Example |
|---|---|---|
| ScopeEngine | Unauthorized actions, amount limits | $15K order when limit is $10K |
| SemanticVerifier | Intent mismatch (LLM) | Ordering luxury items as "maintenance parts" |
| BudgetTracker | Cumulative overspend | $9K x 6 times = $54K exceeds $50K daily |
| PatternMonitor | Anomalous behavior | 50 actions today vs 3/day average |
| HumanEscalation | High-risk decisions | $8K+ orders require manager approval |
# Python from agentcheck import SafetyStack, BudgetTracker, PatternMonitor, HumanEscalation, build_scope scope = build_scope( allowed=["order_parts", "monitor"], denied=["shutdown"], limits={"order_parts": {"max_amount": 10000}}, ) stack = SafetyStack( budget=BudgetTracker(daily_limit=50000), pattern=PatternMonitor(), escalation=HumanEscalation(threshold_amount=8000), ) result = stack.check(scope, "order_parts", amount=3000) # result.allowed = True, result.passed_layers = ["scope_engine", "budget_tracker", ...]
VerificationPipeline (Complete Flow)
Connects AgentCheck server + safety layers + LLM into one verify-and-execute call.
AgentCheck 서버 + 안전 레이어 + LLM을 하나의 검증-실행 호출로 연결합니다.
# Python from agentcheck import Client, VerificationPipeline, ClaudeProvider, BudgetTracker, HumanEscalation client = Client(api_key="ak_live_...", base_url="https://agentcheck.spaceplanning.work") pipeline = VerificationPipeline( client=client, llm=ClaudeProvider(), # LLM semantic check (optional) budget=BudgetTracker(daily_limit=50000), escalation=HumanEscalation(threshold_amount=5000), ) result = pipeline.verify_and_execute( agent="factory-bot", action="order_parts", amount=3000, execute_fn=lambda: place_order(item="bearings"), ) pipeline.print_report(result) # Shows: delegation_check -> scope_engine -> semantic_verifier -> budget -> pattern -> escalation -> execution
// TypeScript import { AgentCheckClient, VerificationPipeline, ClaudeProvider, BudgetTracker } from "agentcheck-sdk"; const pipeline = new VerificationPipeline(client, { llm: new ClaudeProvider(), budget: new BudgetTracker({ dailyLimit: 50000 }), }); const result = await pipeline.verifyAndExecute( "factory-bot", "order_parts", () => placeOrder({ item: "bearings" }), { amount: 3000 }, );
Known Limitations (Honest Disclosure)
AgentCheck is transparent about what it can and cannot do.
AgentCheck는 할 수 있는 것과 없는 것을 솔직하게 공개합니다.
| Can detect | Cannot detect |
|---|---|
| Unauthorized action (not in allowed list) | Malicious intent behind allowed action |
| Amount over limit ($15K > $10K) | Unnecessary purchase within limit |
| Cumulative budget exceeded | Data exfiltration through allowed reads |
| Unusual frequency (anomaly) | Prompt injection in agent system |
| Denied action attempted | Subtle scope abuse matching exact wording |
Mitigation for what we cannot detect:
| Limitation | Mitigation |
|---|---|
| Malicious intent | SemanticVerifier (LLM) flags "suspicious" for human review |
| Cumulative small abuse | BudgetTracker catches total exceeding daily/monthly limits |
| Unusual patterns | PatternMonitor detects frequency/amount anomalies |
| High-risk actions | HumanEscalation requires human approval above threshold |
| All of the above combined | VerificationPipeline chains all layers sequentially |
우리의 철학: 완벽한 검증 시스템은 없습니다. 여러 레이어로 위험을 줄입니다. 자동 검증이 불확실하면 사람에게 넘깁니다. 한계를 숨기면 신뢰가 무너지기 때문에 솔직하게 공개합니다.
Rate Limits
| Endpoint | Limit |
|---|---|
| POST /api/v1/signup | 3 per IP per hour |
| POST /api/v1/record | 100 per minute per API key |
| GET /api/v1/record/:id | 300 per minute per API key |
| GET /api/v1/records | 60 per minute per API key |
| GET /api/v1/verify/:id | No limit (public) |
API Endpoints
X-API-Key.X-API-Key.X-API-Key.X-API-Key.X-API-Key. Body: {"reason": "..."}X-API-Key.SDK Reference
Individual Commands (Basic Menu / 개별 명령어)
Low-level API calls. Use these when you need full control.
개별 API 호출. 세밀한 제어가 필요할 때 사용합니다.
| Python | TypeScript | Description |
|---|---|---|
agentcheck.record() | client.record() | Create agreement + send email / 위임장 생성 |
agentcheck.get(id) | client.get(id) | Get agreement by ID / 위임장 조회 |
agentcheck.list() | client.list() | List with filters / 목록 조회 (필터) |
agentcheck.amend() | client.amend() | Amend scope / 위임장 수정 |
agentcheck.revoke() | client.revoke() | Revoke immediately / 즉시 폐기 |
Client.signup() | Client.signup() | Self-service signup / 회원가입 |
client.register_webhook() | client.registerWebhook() | Register webhook / 웹훅 등록 |
Set Menus (High-Level Wrappers / 세트 메뉴)
Pre-built combinations of individual commands. Use these for common patterns.
개별 명령어를 조합한 고수준 래퍼. 일반적인 패턴에 바로 사용합니다.
1. DelegationProvider - Universal (모든 프로젝트)
Combines permission check + scope verification + execution logging into simple calls.
권한 확인 + 범위 검증 + 실행 로깅을 간단한 호출로 조합합니다.
# Python from agentcheck import Client, DelegationProvider client = Client(api_key="ak_live_...") provider = DelegationProvider(client, verify_scope=lambda scope, action: action in scope ) # Can this agent do this action? result = provider.can_act("my-bot", "send-email") # {"allowed": True, "agreement": ...} # Does this agent have any valid delegation? provider.has_active_delegation("my-bot") # True/False # Log what agent did provider.log_execution("my-bot", "send-email", result="success") # Check + execute + log in one call provider.execute_with_guard("my-bot", "send-email", lambda: send_email())
// TypeScript import { AgentCheckClient, DelegationProvider } from "agentcheck-sdk"; const client = new AgentCheckClient("ak_live_..."); const provider = new DelegationProvider(client, { verifyScope: (scope, action) => scope.includes(action), }); await provider.canAct("my-bot", "send-email"); await provider.hasActiveDelegation("my-bot"); await provider.logExecution("my-bot", "send-email"); await provider.executeWithGuard("my-bot", "send-email", () => sendEmail());
2. DelegationGuard - Express / FastAPI Middleware
Automatically checks delegation before every API request. Zero code in your route handlers.
모든 API 요청 전에 자동으로 위임 확인. 라우트 핸들러에 코드 추가 불필요.
// TypeScript (Express) import { delegationGuard } from "agentcheck-sdk"; app.use('/api/agent/*', delegationGuard(provider, { getAgent: (req) => req.headers['x-agent-id'], getAction: (req) => `${req.method} ${req.path}`, })); // All agent routes are now automatically protected
# Python (FastAPI) from agentcheck.guard import fastapi_guard @app.middleware("http") async def check(request, call_next): return await fastapi_guard( provider, get_agent=lambda req: req.headers.get("x-agent-id"), get_action=lambda req: f"{req.method} {req.url.path}", )(request, call_next)
3. AgentToolChecker - LangChain / CrewAI
Wrap any LangChain tool with automatic delegation checks. The agent can only use tools it's authorized for.
LangChain 도구에 자동 위임 체크를 래핑. 에이전트는 승인된 도구만 사용할 수 있습니다.
# Python from agentcheck import AgentToolChecker checker = AgentToolChecker(provider, "my-bot") # Wrap existing tools - they now require delegation safe_send = checker.wrap("send-email", original_send_email) safe_query = checker.wrap("query-db", original_query_db) # These will check delegation, execute, and log automatically safe_send(to="[email protected]", body="Hello") safe_query(sql="SELECT * FROM orders")
// TypeScript import { AgentToolChecker } from "agentcheck-sdk"; const checker = new AgentToolChecker(provider, "my-bot"); const safeSend = checker.wrap("send-email", originalSendEmail); await safeSend({ to: "[email protected]" }); // Delegation checked + executed + logged
4. DelegationDashboard - Embeddable Widget
Drop a live delegation status widget into your admin page. Auto-refreshing, self-contained HTML.
관리자 페이지에 위임 현황 위젯을 삽입합니다. 자동 갱신, 독립적 HTML.
# Python (FastAPI) from agentcheck import Client, DelegationDashboard from fastapi.responses import HTMLResponse client = Client(api_key="ak_live_...") dashboard = DelegationDashboard(client, title="Our Agents", refresh_interval=30) @app.get("/admin/delegations") def delegations(): return HTMLResponse(dashboard.render()) # Or embed in existing page: widget_html = dashboard.render_widget() # HTML snippet only
// TypeScript (Express) import { AgentCheckClient, DelegationDashboard } from "agentcheck-sdk"; const dashboard = new DelegationDashboard(client, { title: "Our Agents", refreshInterval: 30, }); app.get("/admin/delegations", async (req, res) => { res.send(await dashboard.render()); }); // Or embed widget in your template: const widget = await dashboard.renderWidget();
Summary: Individual vs Set Menu
| When to use | Individual (Basic) | Set Menu (Wrapper) |
|---|---|---|
| You want full control | Use this | |
| Quick integration | Use this | |
| Custom approval flow | Use this | |
| Standard check+execute+log | Use this | |
| Middleware (Express/FastAPI) | DelegationGuard | |
| LangChain/CrewAI tools | AgentToolChecker |
Error Codes
| Code | HTTP | Description |
|---|---|---|
unauthorized | 401 | Missing or invalid API key |
not_found | 404 | Agreement not found |
bad_request | 400 | Invalid request parameters |
rate_limited | 429 | Too many requests |
internal_error | 500 | Server error |