Xybern API Documentation
The Authorisation API for AI agents. Intercept, evaluate, and enforce every agent action before it runs.
Base URL: https://www.xybern.com/api/v1
The Xybern Authorisation API sits between your AI agents and the world. Every tool call, every delegation, every action — evaluated against your policies and either allowed or denied in real time. Full audit trail included.
Authentication
All API requests require your API key in the X-API-Key header.
X-API-Key: xb_your_api_key_here
Get your API key from the Sentinel Dashboard under API Keys.
Keep your API key secure. Never expose it in client-side code or public repositories.
SDK Installation New
Download our official SDKs for Python and JavaScript - no package manager required.
How it works: Download the SDK file to your project folder, then import it. No pip/npm install needed.
Python SDK
Step 1: Install the required dependency
pip install requests
Step 2: Download the SDK to your project folder
# Navigate to your project directory first
cd /path/to/your/project
# Download the SDK file
curl -O https://www.xybern.com/static/sdk/xybern.py
# Now you have xybern.py in your project folder
Step 3: Import and use in your Python script (must be in same folder)
# your_script.py (in the same folder as xybern.py)
from xybern import Xybern
client = Xybern(api_key="xb_your_api_key")
result = client.verify(
content="AI generated text to verify...",
source={"type": "llm", "model": "gpt-4", "provider": "openai"}
)
print(f"Trust Score: {result['trust_score']}%")
print(f"Fairness Score: {result['bias']['fairness_score']}%")
Project structure:
my_project/
├── xybern.py # Downloaded SDK
├── your_script.py # Your code that imports xybern
└── ...
JavaScript SDK
Option A: Browser (easiest)
Just add a script tag - no download needed:
<!DOCTYPE html>
<html>
<head>
<script src="https://www.xybern.com/static/sdk/xybern.js"></script>
</head>
<body>
<script>
const client = new Xybern({ apiKey: 'xb_your_api_key' });
async function verify() {
const result = await client.verify({
content: 'AI generated text...',
source: { type: 'llm', model: 'gpt-4' }
});
console.log('Trust Score:', result.trust_score);
}
verify();
</script>
</body>
</html>
Option B: Node.js
Step 1: Download the SDK to your project folder
# Navigate to your project directory
cd /path/to/your/project
# Download the SDK file
curl -O https://www.xybern.com/static/sdk/xybern.js
Step 2: Require and use in your script
// app.js (in the same folder as xybern.js)
const { Xybern } = require('./xybern.js');
const client = new Xybern({ apiKey: 'xb_your_api_key' });
async function main() {
const result = await client.verify({
content: 'AI generated text...',
source: { type: 'llm', model: 'gpt-4' }
});
console.log(`Trust Score: ${result.trust_score}%`);
}
main();
Project structure:
my_project/
├── xybern.js # Downloaded SDK
├── app.js # Your code that requires xybern
└── package.json
That's it! The SDK file must be in the same directory as your script. No global installation needed.
Important: Keep your API key secure. For production, use environment variables instead of hardcoding.
Quick Start (Direct API)
Or use the API directly without the SDK:
import requests
API_KEY = "xb_your_api_key_here"
response = requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": API_KEY},
json={
"agent_id": "agent_crewai_001",
"action": {
"type": "tool_call",
"tool": "send_email",
"parameters": {"to": "cfo@company.com", "subject": "Q4 Report"}
},
"context": {"task": "financial_reporting", "session_id": "sess_abc123"}
}
)
result = response.json()
print(f"Decision: {result['decision']}") # ALLOW or DENY
print(f"Reason: {result['reason']}")
const API_KEY = "xb_your_api_key_here";
const response = await fetch("https://www.xybern.com/api/v1/enforce/intercept", {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
agent_id: "agent_autogen_001",
action: {
type: "tool_call",
tool: "write_file",
parameters: { path: "/reports/q4.csv", content: "..." }
},
context: { task: "report_generation", session_id: "sess_xyz789" }
})
});
const result = await response.json();
console.log(`Decision: ${result.decision}`); // ALLOW or DENY
curl -X POST https://www.xybern.com/api/v1/enforce/intercept \
-H "X-API-Key: xb_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_langgraph_001",
"action": {
"type": "tool_call",
"tool": "query_database",
"parameters": {"table": "customers", "query": "SELECT * FROM customers"}
},
"context": {"task": "data_analysis", "session_id": "sess_lan001"}
}'
Authorisation New
Pre-execution AI governance layer that intercepts every AI action before it reaches any real system. Enforces policies, manages agent trust, and records immutable audit trails.
/v1/enforce/intercept before executing any action. The control plane evaluates policies, checks agent trust, and returns allow, block, or escalate. Every decision is recorded in the Sentinel Vault hash chain.
Architecture
Agent wants to execute_trade
↓
POST /v1/enforce/intercept (+ optional signed_assertion for identity)
↓
┌──────────────────────────────────────┐
│ Identity Verification (< 1ms) │ ← Ed25519 sig + Redis nonce dedup
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ Policy Engine │ ← All active policies evaluated
│ • action_type — name matching │
│ • threshold — trust level │
│ • content — regex patterns │
│ • temporal — hours / days │
│ • chain — delegation depth │
│ • identity — DID / scope rules │
│ • metadata — field comparisons │ ← notional_usd > 100k, ticker == X, …
│ • composite — AND / OR combiner │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ Decision │
│ allow → fast path (~5ms) │
│ block → fast path (~5ms) │
│ escalate → human review queue │
└──────────────────────────────────────┘
↓
Vault entry + decision returned to SDK / caller
Decision Paths
| Path | Latency | Description |
|---|---|---|
fast | ~5ms | Policy directly blocks or allows — no LLM call |
standard | 3-5s | Full LLM verification runs against the action content |
escalation | — | Action held for human review in the escalation queue |
POST /v1/enforce/intercept Core
Intercept an AI action before execution. This is the primary endpoint — every action must pass through here.
Pre-execution interception with policy evaluation, trust scoring, and vault recording.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
action_type | string | Yes | Action name: execute_trade, send_email, query_database, etc. |
action_content | string | No | Content being actioned (e.g. email body, SQL query) |
metadata | object | No | Arbitrary metadata (amount, recipient, etc.) |
agent_id | string | No | Registered agent performing the action |
chain_id | string | No | Multi-step chain identifier |
chain_step | integer | No | Step number within the chain |
parent_decision_id | string | No | Previous decision in this chain |
Example
import requests
API_KEY = "xb_your_api_key"
# Before executing a trade, intercept it
result = requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": API_KEY},
json={
"action_type": "execute_trade",
"action_content": "Buy 1000 shares of AAPL at market price",
"metadata": {"symbol": "AAPL", "quantity": 1000, "side": "buy"},
"agent_id": "agent_abc123"
}
).json()
if result["decision"] == "allow":
execute_trade() # proceed
elif result["decision"] == "block":
log_blocked(result["reasoning"])
elif result["decision"] == "escalate":
notify_human(result["decision_id"])
Response
{
"ok": true,
"decision": "allow",
"decision_id": "enf_7c8f8c26e62c",
"decision_path": "fast",
"trust_score": 85,
"reasoning": "No policies triggered — default allow",
"policies_evaluated": ["policy_123", "policy_456"],
"policies_triggered": [],
"vault_entry_id": "ve_abc123",
"latency_ms": 5,
"created_at": "2026-03-13T21:48:54Z"
}
POST /v1/enforce/agent-comm
Intercept agent-to-agent instructions. When Agent A tells Agent B to do something, the instruction flows through the control plane first.
Agent-to-agent communication interception with full audit trail.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
source_agent_id | string | Yes | Agent sending the instruction |
target_agent_id | string | Yes | Agent receiving the instruction |
instruction | string | Yes | The instruction content |
action_type | string | No | Defaults to agent_instruction |
chain_id | string | No | Chain identifier for multi-step flows |
Example
result = requests.post(
"https://www.xybern.com/api/v1/enforce/agent-comm",
headers={"X-API-Key": API_KEY},
json={
"source_agent_id": "agent_research_01",
"target_agent_id": "agent_trading_01",
"instruction": "Execute buy order for AAPL based on analysis",
"chain_id": "chain_research_trade_001"
}
).json()
if result["decision"] == "allow":
forward_instruction_to_target_agent()
Enforcement Policies
Create, read, update, and delete enforcement policies programmatically. Policies are evaluated in priority order (highest first) and the most restrictive decision wins.
List all policies for the workspace, ordered by priority.
Create a new enforcement policy.
Update an existing policy.
Delete a policy.
Policy Types
| Type | What It Does | Conditions |
|---|---|---|
action_type | Matches actions by name | Uses action_types field (supports wildcards: delete_*) |
threshold | Blocks agents below a trust level | Uses trust_threshold field |
content_pattern | Regex match on action content | conditions.patterns: array of regex strings |
temporal | Time/day restrictions | conditions.blocked_hours: [0-23], conditions.blocked_days: [1-7] |
chain_of_custody | Agent chain depth/agent rules | conditions.max_chain_depth, conditions.forbidden_agents |
Create Policy Example
# Block trades containing sensitive keywords
policy = requests.post(
"https://www.xybern.com/api/v1/enforce/policies",
headers={"X-API-Key": API_KEY},
json={
"name": "Block Insider Trading Keywords",
"description": "Block any trade with insider-related language",
"policy_type": "content_pattern",
"decision": "block",
"priority": 200,
"action_types": ["execute_trade", "modify_order"],
"conditions": {
"patterns": [
"insider.*info",
"material.*non-public",
"tip.*from.*executive"
]
}
}
).json()
# Block all activity on weekends
weekend_policy = requests.post(
"https://www.xybern.com/api/v1/enforce/policies",
headers={"X-API-Key": API_KEY},
json={
"name": "Weekend Trading Lockout",
"policy_type": "temporal",
"decision": "block",
"priority": 300,
"action_types": ["execute_trade"],
"conditions": {"blocked_days": [6, 7]}
}
).json()
Agent Registry
Register AI agents with the control plane. Each agent accumulates an adaptive trust level — clean records earn fast-path clearance, blocks reduce trust.
Register a new agent.
List all registered agents.
Get agent details including trust level and action history.
Get trust history and decision timeline for an agent.
Adaptive Trust
| Event | Trust Change | Description |
|---|---|---|
| Action allowed | +0.2 | Clean record builds trust over time |
| Action blocked | -2.0 | Policy violations significantly reduce trust |
| Action escalated | -0.5 | Uncertain decisions slightly reduce trust |
Register Agent Example
# Register a trading agent
agent = requests.post(
"https://www.xybern.com/api/v1/enforce/agents",
headers={"X-API-Key": API_KEY},
json={
"name": "Trading Agent Alpha",
"framework": "langchain",
"description": "Automated equity trading agent",
"capabilities": ["execute_trade", "read_data", "send_email"],
"permissions": {
"allowed_action_types": ["execute_trade", "read_data"],
"denied_action_types": ["delete_data", "admin_action"]
}
}
).json()
agent_id = agent["agent"]["agent_id"]
print(f"Registered: {agent_id}, trust: {agent['agent']['trust_level']}")
Decisions & Escalations
Query the immutable decision log and manage the human review queue.
List decisions with pagination and filtering by action_type or decision.
Get a single decision with full details.
List pending escalations (actions awaiting human review).
Approve or reject an escalated action. Body: {"resolution": "approved"|"rejected"}. Immediately unblocks any SDK call polling this escalation.
Poll the resolution status of a pending escalation. Returns {"ok": true, "status": "pending"|"approved"|"rejected"}. Called automatically by the SDK's wait_for_escalation() method every escalation_poll_interval seconds.
Query Decisions
# Get all blocked decisions
blocked = requests.get(
"https://www.xybern.com/api/v1/enforce/decisions",
headers={"X-API-Key": API_KEY},
params={"decision": "block", "per_page": 20}
).json()
for d in blocked["decisions"]:
print(f"{d['decision_id']}: {d['action_type']} → {d['decision']} "
f"(trust: {d['trust_score']}, {d['latency_ms']}ms)")
# Resolve an escalation
requests.post(
f"https://www.xybern.com/api/v1/enforce/escalations/{esc_id}/resolve",
headers={"X-API-Key": API_KEY},
json={"resolution": "approved", "reason": "Reviewed and safe to proceed"}
)
Stats Endpoint
Aggregate enforcement statistics: decisions, block rate, avg latency, agent counts.
Custom Policy Builder
Build enforcement policies tailored to your organization's needs. The policy engine supports six built-in types and a composite type for complex AND/OR logic. All types are available in the Sentinel Dashboard UI under Control Plane → Add Policy.
Content Pattern Policy
Block or escalate actions whose content matches regex patterns. Useful for PII detection, insider trading keywords, or prohibited content.
{
"name": "PII Detection",
"policy_type": "content_pattern",
"decision": "escalate",
"action_types": ["send_email", "export_data"],
"conditions": {
"patterns": [
"\\b\\d{3}-\\d{2}-\\d{4}\\b",
"\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b",
"password|secret|credential|api[_-]?key"
]
}
}
Temporal Policy
Restrict actions to specific hours or days. Blocked hours are in UTC (0-23), blocked days use ISO format (1=Monday, 7=Sunday).
{
"name": "After-Hours Lockout",
"policy_type": "temporal",
"decision": "block",
"action_types": ["execute_trade", "wire_transfer"],
"conditions": {
"blocked_hours": [0, 1, 2, 3, 4, 5, 22, 23],
"blocked_days": [6, 7]
}
}
Chain of Custody Policy
Limit agent delegation depth and block specific agents from chains.
{
"name": "Max Delegation Depth",
"policy_type": "chain_of_custody",
"decision": "escalate",
"conditions": {
"max_chain_depth": 3,
"forbidden_agents": ["agent_untrusted_001"],
"required_agents": ["agent_compliance_reviewer"]
}
}
Dashboard support: All policy types can be created from the Sentinel Dashboard UI under Control Plane → Add Policy, with dynamic condition fields that appear based on the selected type.
Metadata Field Policy New
Evaluate field-level conditions against the metadata object passed on each intercept call. Combine rules with AND/OR logic to build precise, data-driven policies — such as blocking high-notional trades or flagging specific tickers.
/enforce/intercept with a metadata object, the engine evaluates every active metadata policy against those fields. Rules can reference any key in the metadata object with numeric or string comparisons.Supported Operators
| Operator | Type | Example |
|---|---|---|
> < >= <= | Numeric | notional_usd > 100000 |
== != | String / Numeric | ticker == "TSLA" |
contains not_contains | String | strategy contains "pre-earnings" |
exists not_exists | Presence | insider_flag exists |
Example: Block High-Risk Financial Transactions
requests.post(f"{BASE}/enforce/policies", headers=HEADERS, json={
"name": "High-Risk Financial Transactions",
"policy_type": "metadata",
"decision": "block",
"action_types": ["execute_trade", "wire_transfer", "fund_transfer"],
"conditions": {
"operator": "AND", # ALL rules must match to trigger
"rules": [
{"field": "notional_usd", "operator": ">", "value": 100000},
{"field": "strategy", "operator": "contains", "value": "pre-earnings"}
]
}
})
Example: OR Logic — Flag Any Large or Sensitive Action
{
"name": "Sensitive Trade Escalation",
"policy_type": "metadata",
"decision": "escalate",
"conditions": {
"operator": "OR", # ANY rule matching triggers the policy
"rules": [
{"field": "notional_usd", "operator": ">=", "value": 500000},
{"field": "ticker", "operator": "==", "value": "GME"},
{"field": "insider_flag", "operator": "exists"}
]
}
}
Intercept Call with Metadata
result = requests.post(f"{BASE}/enforce/intercept", headers=HEADERS, json={
"action_type": "execute_trade",
"action_content": "Buy $4.2M block of TSLA ahead of earnings",
"agent_id": "agent_trading_01",
"metadata": {
"ticker": "TSLA",
"notional_usd": 4200000,
"strategy": "pre-earnings",
"order_type": "market"
}
}).json()
# → decision: "block"
# → reasoning: "All metadata conditions met [metadata.notional_usd > 100000; ...]"
SDK auto-extract: When using the @guard decorator or XybernToolkit, numeric and string keyword arguments are automatically extracted as metadata and evaluated against metadata policies — no manual metadata construction needed.
Okta for AI Agents — Cryptographic Identity v1.22
Every AI agent is assigned an Ed25519 cryptographic identity — a verifiable credential that defines what the agent is, what it's authorised to do, and under what conditions. The control plane verifies identity on every action before any policy evaluation. Replay protection is enforced via Redis-backed nonce deduplication across all workers.
Key Concepts
| Concept | Description |
|---|---|
| Ed25519 Keypair | Every agent gets a public/private key. Private key returned once at registration, never stored server-side. |
| DID | W3C-aligned did:xybern:{workspace}:{agent} identifier. |
| Signed Assertion | JSON payload signed by agent's private key, verified by control plane in < 1 ms. |
| Permission Boundary | Scopes, resource rules, temporal windows, delegation controls, rate limits. |
| Nonce + Timestamp | Replay protection: each assertion must have a unique nonce and timestamp within 5 minutes. Nonces are deduplicated in Redis across all API workers. |
Register Agent (via SDK — recommended)
from xybern import Xybern, AgentCredential
client = Xybern(api_key="xb_your_key")
# Generates Ed25519 keypair, registers agent, issues credential — one call
cred = client.agents.register(
name="Trading Agent Alpha",
framework="langchain",
capabilities=["execute_trade", "query_portfolio"],
)
cred.save("./trading_agent.cred") # chmod 600, private key never leaves your server
print(cred.did) # did:xybern:{workspace}:agent_abc123
print(cred.key_fingerprint) # SHA-256 hex fingerprint
Register Agent (raw API)
resp = requests.post(f"{BASE}/enforce/agents", headers=HEADERS, json={
"name": "Trading Agent Alpha",
"framework": "langchain",
"scopes": ["trade:write", "db:read"],
"permission_boundary": {
"resource_rules": [{"action_types": ["execute_trade"], "max_amount": 50000}],
"temporal": {"allowed_hours": {"start": 9, "end": 17}},
"rate_limit": {"max_actions": 100, "window_minutes": 60}
}
})
cred = resp.json()["credential"]
private_key = cred["private_key"] # SAVE THIS — shown only once
did = cred["did"] # did:xybern:ws1:agent_abc123
Intercept with Identity (SDK — auto-signing)
# SDK signs every assertion automatically — no crypto code needed
result = client.agents.intercept(
action_type="execute_trade",
action_content="Buy 100 AAPL at market",
credential="./trading_agent.cred", # or AgentCredential object
metadata={"ticker": "AAPL", "notional_usd": 19000},
)
# response includes identity_verified=True when signature checks pass
print(result.identity_verified) # True
Intercept with Identity (raw API)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from base64 import b64encode, b64decode
import json, uuid
nonce = uuid.uuid4().hex
payload = {"agent_id": "agent_abc", "action": "execute_trade",
"nonce": nonce, "timestamp": "2026-01-25T12:00:00Z"}
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
pk = Ed25519PrivateKey.from_private_bytes(b64decode(PRIVATE_KEY))
sig = b64encode(pk.sign(canonical)).decode()
resp = requests.post(f"{BASE}/enforce/intercept", headers=HEADERS, json={
"action_type": "execute_trade",
"agent_id": "agent_abc",
"signed_assertion": payload,
"assertion_signature": sig
})
# → response: {"identity_verified": true, "identity": {"did": "...", "fingerprint": "..."}}
SDK Auto-Capture v1.19
Integrate once — every AI agent action is automatically captured on the Sentinel dashboard. No manual intercept() call per action. Three patterns available depending on your stack.
Option 1: @guard Decorator
Wrap any Python function. Every call is intercepted, signed, and recorded. Blocked actions raise AgentBlockedError and the wrapped function never executes. When an action is escalated and wait_on_escalate=True (default), the agent blocks until a human approves or rejects on the Sentinel dashboard.
from xybern import Xybern, AgentCredential, AgentBlockedError
client = Xybern(api_key="xb_your_key")
cred = AgentCredential.load("./trading_agent.cred")
@client.agents.guard(credential=cred)
def execute_trade(ticker, quantity, notional_usd=0):
place_order(ticker, quantity) # only runs if Sentinel allows it
@client.agents.guard(credential=cred)
def query_portfolio(account):
return get_holdings(account)
# Every call → captured on Sentinel dashboard, identity verified
execute_trade("AAPL", 100, notional_usd=19000) # → ALLOW
execute_trade("TSLA", 1000, notional_usd=5000000) # → BLOCK (raises AgentBlockedError)
The @guard decorator accepts the following optional parameters for human-in-the-loop escalation gating:
| Parameter | Default | Description |
|---|---|---|
credential | required | Path to .cred file or AgentCredential object |
wait_on_escalate | True | Block execution until a human approves or rejects on the dashboard |
escalation_timeout | 300 | Maximum seconds to wait for a human decision before raising AgentBlockedError |
escalation_poll_interval | 5 | Seconds between each poll of the escalation status endpoint |
metadata_fn | None | Callable (args, kwargs) → dict for fine-grained policy metadata |
Provide a metadata_fn for fine-grained policy evaluation:
@client.agents.guard(
credential=cred,
metadata_fn=lambda args, kw: {
"ticker": kw.get("ticker"),
"notional_usd": kw.get("notional_usd", 0),
"strategy": kw.get("strategy", ""),
}
)
def execute_trade(ticker, quantity, notional_usd=0, strategy=""):
place_order(ticker, quantity)
Option 2: LangChain Callback Handler
Zero changes to tool code. Attach XybernLangChainCallback to any LangChain agent — every tool call is intercepted before execution.
from xybern import Xybern, XybernLangChainCallback, AgentCredential
from langchain.agents import initialize_agent
client = Xybern(api_key="xb_your_key")
cred = AgentCredential.load("./trading_agent.cred")
handler = XybernLangChainCallback(client=client, credential=cred)
agent = initialize_agent(tools, llm, callbacks=[handler])
# Done — every tool call appears on the Sentinel dashboard
Option 3: XybernToolkit
Wrap a list of plain callables for non-LangChain agents. Exposes tools as attributes with full auto-interception.
from xybern import Xybern, XybernToolkit, AgentCredential
client = Xybern(api_key="xb_your_key")
cred = AgentCredential.load("./trading_agent.cred")
def execute_trade(ticker, quantity, notional_usd=0): ...
def query_portfolio(account): ...
def risk_analysis(sector): ...
tools = XybernToolkit(client=client, credential=cred,
tools=[execute_trade, query_portfolio, risk_analysis])
# All calls auto-captured — same API as the original functions
tools.execute_trade("AAPL", 100, notional_usd=19000)
tools.query_portfolio("ACC-001")
print(tools.list_tools()) # ["execute_trade", "query_portfolio", "risk_analysis"]
AgentCredential — Key Management
| Method | Description |
|---|---|
cred.save(path) | Write JSON credential file, chmod 600 |
AgentCredential.load(path) | Load credential from file |
AgentCredential.from_env("PREFIX") | Load from env vars: PREFIX_AGENT_ID, PREFIX_PRIVATE_KEY, etc. |
cred.to_env_snippet("PREFIX") | Generate export lines for secrets managers / CI |
client.agents.rotate_credential(agent_id) | Rotate keypair — old key immediately invalid |
Every decision is live on the dashboard. Whether via @guard, XybernLangChainCallback, or direct intercept(), every enforcement decision is recorded in real time under Sentinel → Control Plane → Enforcement Decisions with full metadata, trust score, and policy reasoning.
Credential Lifecycle
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/enforce/agents/{id}/credentials | List all credentials for an agent |
GET | /v1/enforce/agents/{id}/credentials/active | Get active credential |
POST | /v1/enforce/agents/{id}/credentials/rotate | Rotate: revoke old, issue new (returns private key) |
POST | /v1/enforce/credentials/{id}/revoke | Permanently revoke a credential |
GET | /v1/enforce/credentials | List all workspace credentials |
POST | /v1/enforce/credentials/{id}/verify | Verify a signed assertion |
Identity Policy Type
requests.post(f"{BASE}/enforce/policies", headers=HEADERS, json={
"name": "Require Agent Identity",
"policy_type": "identity",
"decision": "block",
"conditions": {
"require_identity": True,
"required_scopes": ["trade:write"],
"blocked_dids": ["did:xybern:ws1:agent_compromised"]
}
})
Agent-to-Agent Delegation (A2A Auth) NEW
When Agent A tells Agent B to perform an action, both sides must be authorised. Xybern intermediates every agent-to-agent interaction with bidirectional authorisation, scope attenuation, and cryptographic delegation grants recorded in the Provenance Vault.
Core Concepts
| Concept | Description |
|---|---|
delegation_policy | Per-agent config: can_delegate, can_accept_delegation, delegable_scopes, acceptable_scopes, max_delegation_depth |
| Scope Attenuation | Granted scopes = source's delegable ∩ target's acceptable ∩ requested. Wildcards supported (trade:*). |
| Delegation Grant | First-class verifiable token (dlg_...) that the target agent presents when acting on behalf of the source. |
| Delegation Chains | Agent B can re-delegate to Agent C if depth allows. Max depth enforced, cascading revocation propagates down the chain. |
Register Agents with Delegation Policy
import requests
BASE = "https://xybern.com/api/v1"
HEADERS = {"X-API-Key": "xb_...", "Content-Type": "application/json"}
# Source agent — can delegate trade:read and db:read
requests.post(f"{BASE}/enforce/agents", headers=HEADERS, json={
"name": "finance-agent",
"framework": "crewai",
"permissions": {"allowed_action_types": ["execute_trade", "query_database"]},
"scopes": ["trade:write", "trade:read", "db:read", "agent:delegate"],
"delegation_policy": {
"can_delegate": True,
"can_accept_delegation": False,
"delegable_scopes": ["trade:read", "db:read"],
"max_delegation_depth": 3
}
})
# Target agent — accepts trade:read and db:read
requests.post(f"{BASE}/enforce/agents", headers=HEADERS, json={
"name": "analyst-agent",
"framework": "crewai",
"permissions": {"allowed_action_types": ["query_database", "read_data"]},
"scopes": ["trade:read", "db:read"],
"delegation_policy": {
"can_delegate": False,
"can_accept_delegation": True,
"acceptable_scopes": ["trade:read", "db:read"]
}
})
POST /v1/enforce/delegate
Request a delegation grant from one agent to another.
resp = requests.post(f"{BASE}/enforce/delegate", headers=HEADERS, json={
"source_agent_id": "agent_abc123",
"target_agent_id": "agent_def456",
"scopes": ["trade:read", "db:read"],
"action_types": ["query_database"],
"constraints": {"max_amount": 100000},
"instruction": "Analyse Q1 trading performance",
"ttl_hours": 12,
"max_uses": 5
})
grant = resp.json()["grant"]
# grant["grant_id"] → "dlg_42eb33adab69"
# grant["attenuated_scopes"] → ["db:read", "trade:read"]
# grant["delegation_depth"] → 1
# grant["vault_entry_id"] → "ve_590dba34..."
POST /v1/enforce/delegate/verify
Verify a delegation grant before executing an action.
resp = requests.post(f"{BASE}/enforce/delegate/verify", headers=HEADERS, json={
"grant_id": "dlg_42eb33adab69",
"agent_id": "agent_def456",
"action_type": "query_database"
})
# resp.json()["valid"] → True
# resp.json()["reason"] → "Grant verified"
Using a Grant with Intercept
Pass grant_id to /enforce/intercept so the control plane verifies the delegation before policy evaluation.
resp = requests.post(f"{BASE}/enforce/intercept", headers=HEADERS, json={
"action_type": "query_database",
"action_content": "SELECT * FROM trades WHERE quarter='Q1'",
"agent_id": "agent_def456",
"grant_id": "dlg_42eb33adab69",
"metadata": {"table": "trades", "operation": "read"}
})
# If grant is invalid or revoked → decision: "block"
# If grant is valid → normal policy evaluation proceeds
POST /v1/enforce/delegate/{grant_id}/revoke
Revoke a grant with cascading — all child grants in the delegation chain are revoked automatically.
resp = requests.post(f"{BASE}/enforce/delegate/dlg_42eb33adab69/revoke",
headers=HEADERS, json={"reason": "task_complete"})
# resp.json()["revoked_count"] → 1
# resp.json()["revoked_grants"] → ["dlg_42eb33adab69"]
GET /v1/enforce/delegations
List all delegation grants for the workspace. Filter by status (active, revoked, expired) or agent_id.
curl -H "X-API-Key: xb_..." \
"https://xybern.com/api/v1/enforce/delegations?status=active"
Delegation Policies
Create enforcement policies of type delegation to control A2A behaviour at the workspace level.
requests.post(f"{BASE}/enforce/policies", headers=HEADERS, json={
"name": "Block Finance→Marketing delegation",
"policy_type": "delegation",
"decision": "block",
"conditions": {
"blocked_pairs": [["agent_finance", "agent_marketing"]],
"max_depth": 2,
"blocked_scopes": ["payments:execute"]
}
})
Policy Shadow Mode NEW
Test new policies against live production traffic without affecting any decisions. Shadow policies are evaluated on every incoming action but their verdicts are logged separately — the actual authorisation decision is never changed.
How It Works
| Step | What Happens |
|---|---|
| 1. Create | Create a policy with "mode": "shadow". It's active but observe-only. |
| 2. Evaluate | Every /enforce/intercept call evaluates shadow policies in a separate pass after the real decision. |
| 3. Record | Shadow results are stored in shadow_results on the decision record: what the shadow policy would have decided, which policies triggered, whether the outcome would differ. |
| 4. Report | The /enforce/shadow/report endpoint and the Policy Simulation dashboard aggregate the impact. |
| 5. Promote | When satisfied, promote to live with a single API call or one click in the dashboard. |
Create a Shadow Policy
resp = requests.post(f"{BASE}/enforce/policies", headers=HEADERS, json={
"name": "Block Trades Over 500K",
"policy_type": "threshold",
"action_types": ["execute_trade"],
"decision": "block",
"priority": 200,
"trust_threshold": 95,
"mode": "shadow" # ← observe-only
})
# resp.json()["policy"]["mode"] → "shadow"
Shadow Results on Decisions
After sending traffic, each decision record includes a shadow_results field:
{
"decision": "allow",
"shadow_results": {
"shadow_decision": "block",
"would_have_changed": true,
"live_decision": "allow",
"triggered": [
{
"policy_id": "abc123",
"name": "Block Trades Over 500K",
"decision": "block",
"reason": "Agent trust 50.2 < threshold 95"
}
],
"reasoning": "Agent trust 50.2 < threshold 95"
}
}
GET /v1/enforce/shadow/report
Aggregate impact report for all active shadow policies over a time window.
curl -H "X-API-Key: xb_..." \
"https://xybern.com/api/v1/enforce/shadow/report?hours=24"
{
"shadow_policies": 2,
"hours": 24,
"report": {
"total_evaluated": 847,
"would_block": 23,
"would_escalate": 8,
"would_change_outcome": 15,
"affected_agents": ["agent_abc", "agent_def"],
"per_policy": [
{
"policy_id": "abc123",
"name": "Block Trades Over 500K",
"would_block": 23,
"would_escalate": 0,
"total_triggered": 23
}
]
}
}
POST /v1/enforce/shadow/{policy_id}/promote
Promote a shadow policy to live mode — it will start enforcing immediately.
resp = requests.post(
f"{BASE}/enforce/shadow/{policy_id}/promote",
headers=HEADERS
)
# resp.json()["policy"]["mode"] → "live"
Toggle Mode via Update
You can also switch any policy between live and shadow using the standard update endpoint:
requests.put(f"{BASE}/enforce/policies/{policy_id}",
headers=HEADERS,
json={"mode": "shadow"} # or "live"
)
Dashboard
The Sentinel dashboard includes:
- Policies tab — every policy shows a LIVE or SHADOW badge with a one-click mode toggle.
- Policy Simulation tab — impact stats (would-block, would-escalate, outcome changes), per-policy breakdown, live vs shadow comparison table, and a "Promote to Live" button.
Temporal Permission Windows NEW
Grant time-bounded permissions to AI agents that auto-expire. Modeled after Just-In-Time (JIT) access patterns in human IAM — AWS STS temporary credentials, CyberArk JIT provisioning, HashiCorp Vault dynamic secrets — but purpose-built for AI agent authorization.
| Concept | Description |
TemporalPermissionWindow | A time-bounded authorization grant with scopes, action types, constraints, and auto-expiry. |
| Duration | 1 minute to 24 hours. Configurable per window. |
| Scopes | Same scope system as credentials — trade:write, db:read, etc. |
| Max Uses | Optional limit on how many times the window can be used within its time boundary. |
| Extensions | Windows can be extended up to max_extensions times (default: 3). Each extension is vault-recorded. |
| Workflow Binding | Optional workflow_id ties the window to a specific workflow execution. |
| Lazy Expiry | No background sweeper — expiry is checked at intercept time for zero overhead. |
Create a Temporal Permission Window
import requests
BASE = "https://xybern.com/api/v1"
HEADERS = {"Authorization": "Bearer xb_your_key"}
resp = requests.post(f"{BASE}/enforce/temporal-windows", headers=HEADERS, json={
"agent_id": "agent_finance_01",
"scopes": ["payments.read", "payments.execute", "db:read"],
"duration_minutes": 30,
"action_types": ["execute_trade", "query_database"],
"constraints": {"max_amount": 50000},
"reason": "Processing customer order #4821",
"workflow_id": "wf_order_4821",
"max_uses": 10,
"max_extensions": 2
})
window = resp.json()["window"]
# window["window_id"] → "tw_a1b2c3d4e5f6"
# window["expires_at"] → "2026-04-01T14:30:00Z"
# window["remaining_seconds"] → 1800
# window["is_active"] → True
How It Works at Intercept Time
When an agent makes a request through POST /v1/enforce/intercept, the control plane automatically checks for active temporal windows. If the agent has an active window that covers the requested action, the response includes a temporal_window field:
{
"decision": "allow",
"decision_id": "enf_abc123",
"temporal_window": {
"window_id": "tw_a1b2c3d4e5f6",
"remaining_seconds": 1247,
"reason": "Temporal window 'tw_a1b2c3d4e5f6' grants access (expires in 1247s)"
}
}
Extend a Window
If a workflow is still running and needs more time, extend the window (up to max_extensions):
resp = requests.post(
f"{BASE}/enforce/temporal-windows/{window_id}/extend",
headers=HEADERS,
json={
"additional_minutes": 15,
"reason": "Workflow still processing batch"
}
)
# resp.json()["window"]["extensions"] → 1
# resp.json()["window"]["remaining_seconds"] → updated
Revoke a Window
Immediately terminate a window before it expires:
resp = requests.post(
f"{BASE}/enforce/temporal-windows/{window_id}/revoke",
headers=HEADERS,
json={"reason": "Workflow completed early"}
)
# resp.json()["window"]["status"] → "revoked"
Pre-flight Check
Check if an agent has an active window without consuming a use:
resp = requests.post(f"{BASE}/enforce/temporal-windows/check", headers=HEADERS, json={
"agent_id": "agent_finance_01",
"action_type": "execute_trade",
"metadata": {"amount": 25000}
})
# resp.json()["has_active_window"] → True
# resp.json()["remaining_seconds"] → 847
List & Stats
# List all windows (filter by agent, status, workflow)
requests.get(f"{BASE}/enforce/temporal-windows?active_only=true", headers=HEADERS)
# Agent-specific windows
requests.get(f"{BASE}/enforce/agents/{agent_id}/temporal-windows", headers=HEADERS)
# Aggregate stats
requests.get(f"{BASE}/enforce/temporal-windows/stats", headers=HEADERS)
# → { active_windows, expired_windows, revoked_windows, total_uses, avg_duration_minutes, ... }
Dashboard
The Sentinel dashboard includes a dedicated Temporal Windows tab showing:
- Active windows with real-time countdown timers, scope badges, and one-click extend/revoke
- Window history — expired and revoked windows with usage stats
- Aggregate stats — active count, total uses, average duration, agents with active windows
Breakglass Protocol NEW
Emergency override mechanism for blocked enforcement decisions. When a critical action is blocked by policy but must proceed, operators trigger a breakglass override — a time-limited bypass with mandatory justification, immutable audit trail, and post-incident review requirements.
Use sparingly. Breakglass overrides bypass normal authorization. Every event is recorded in the Provenance Vault and requires post-incident review. A per-agent cooldown (max 3 per 30 minutes) prevents abuse.
| Concept | Description |
BreakglassEvent | A time-limited authorization override with justification, severity, and audit trail |
Justification | Mandatory written rationale (min 10 chars) explaining why the override is needed |
Severity | critical, high, or medium — controls visibility and review priority |
Cooldown | Max 3 breakglass events per agent per 30-minute window |
Auto-expire | Override auto-expires after configured duration (default 15 min, max 2 hours) |
Post-incident review | Events require review annotation after the fact for compliance |
Trigger a Breakglass Override
import requests
BASE = "https://www.xybern.com/api/v1"
HEADERS = {"X-API-Key": "xb_your_key"}
override = requests.post(f"{BASE}/enforce/breakglass", headers=HEADERS, json={
"agent_id": "agent_deploy_01",
"action_type": "deploy:production",
"justification": "Critical hotfix for payment processing outage — approved by VP Eng",
"triggered_by": "oncall_engineer_42",
"severity": "critical",
"duration_minutes": 15,
"max_actions": 5,
})
bg = override.json()["event"]
print(f"Override active: {bg['breakglass_id']}")
print(f"Expires in: {bg['remaining_seconds']}s")
How It Works at Intercept Time
When an action is blocked and the agent has an active breakglass override, the decision is automatically flipped to allow with a breakglass proof attached:
{
"decision": "allow",
"decision_id": "enf_abc123",
"decision_path": "breakglass",
"reasoning": "Policy violation detected | Breakglass override active (bg_xyz789)",
"breakglass": {
"breakglass_id": "bg_xyz789",
"remaining_seconds": 842,
"reason": "Breakglass override 'bg_xyz789' active (expires in 842s)"
}
}
When a decision is blocked and no active override exists, the response includes a hint:
{
"decision": "block",
"breakglass_available": true
}
Close an Override Early
requests.post(f"{BASE}/enforce/breakglass/{bg_id}/close", headers=HEADERS, json={
"reason": "Hotfix deployed successfully, no longer needed"
})
Add Post-Incident Review
requests.post(f"{BASE}/enforce/breakglass/{bg_id}/review", headers=HEADERS, json={
"reviewed_by": "security_lead_01",
"review_notes": "Override was justified — payment outage affected 12k users. "
"Root cause: stale policy blocking deploy:production for this agent. "
"Action: Updated policy to allow during incident windows."
})
List & Stats
# List all breakglass events
requests.get(f"{BASE}/enforce/breakglass", headers=HEADERS)
# Active overrides only
requests.get(f"{BASE}/enforce/breakglass?active_only=true", headers=HEADERS)
# Aggregate stats
requests.get(f"{BASE}/enforce/breakglass/stats", headers=HEADERS)
# → { total_events, active_overrides, pending_review, reviewed, by_severity, ... }
Dashboard
The Sentinel dashboard includes a dedicated Breakglass Protocol tab showing:
- Active overrides with real-time countdown timers, severity badges, and one-click close
- Event history — closed and expired events with review status indicators
- Severity breakdown — critical/high/medium counts at a glance
- Post-incident review — review button on unreviewed events, notes visible inline
Policy-as-Code SDK NEW
Define authorization policies as Python code, version them in Git, and deploy atomically via the SDK. Xybern diffs your policy definitions against the current state and creates, updates, or removes policies automatically — with full Provenance Vault tracking.
| Concept | Description |
Policy | A fluent builder for a single enforcement policy — .on(), .when_*(), .block() |
PolicyPack | A versioned collection of policies deployed as one atomic unit |
PolicyClient | SDK sub-client at client.policies for deploy, rollback, validate operations |
| Deploy | Create/update/delete policies from a pack — POST /v1/enforce/policy-packs |
| Rollback | Revert to previous pack version — POST /v1/enforce/policy-packs/:name/rollback |
| Validate | Dry-run a pack without deploying — POST /v1/enforce/policy-packs/validate |
| Source Hash | SHA-256 fingerprint of policy definitions — idempotent redeployments |
Define Policies with the Python DSL
from xybern import Xybern, PolicyPack, Policy
client = Xybern(api_key="xb_your_key")
pack = PolicyPack("finance-controls", version="2.0.0",
description="Production trading controls")
# Block low-trust agents from executing trades
pack.add(Policy("Block Untrusted Traders")
.on("execute_trade")
.when_threshold(trust_below=50)
.block("Agents with trust < 50 cannot trade"))
# No deployments on weekends
pack.add(Policy("Weekend Deploy Freeze")
.on("deploy:*")
.when_time(blocked_days=[6, 7])
.escalate("Weekend deploys require human approval"))
# Detect PII in prompts
pack.add(Policy("PII Scanner")
.on("send_prompt")
.when_content_matches(r"\b\d{3}-\d{2}-\d{4}\b", r"\bSSN\b")
.block("PII detected in prompt content"))
# Limit delegation chain depth
pack.add(Policy("Chain Depth Guard")
.when_chain(max_depth=3)
.escalate("Delegation chain too deep"))
Deploy a Policy Pack
# Deploy to live enforcement
result = client.policies.deploy(pack)
print(result)
# → {"ok": true, "pack": {...}, "summary": {"created": 4, "updated": 0, "deleted": 0}}
# Deploy in shadow mode (observe only, never enforced)
result = client.policies.deploy(pack, deploy_mode="shadow")
# Redeploy with changes — Xybern auto-diffs
pack_v2 = PolicyPack("finance-controls", version="2.1.0")
pack_v2.add(Policy("Block Untrusted Traders")
.on("execute_trade")
.when_threshold(trust_below=60) # raised threshold
.block("Agents with trust < 60 cannot trade"))
result = client.policies.deploy(pack_v2)
# → {"summary": {"created": 0, "updated": 1, "deleted": 3, "unchanged": 0}}
Validate Without Deploying
# Dry-run validation
validation = client.policies.validate(pack)
print(validation)
# → {"ok": true, "valid": true, "policy_count": 4, "policies": [...]}
Rollback to Previous Version
# Something went wrong — rollback instantly
result = client.policies.rollback("finance-controls")
# → redeploys previous version's policies
Delete a Pack
# Remove a pack and all its managed policies
result = client.policies.delete("finance-controls")
# → {"ok": true, "policies_removed": 4}
REST API Reference
POST /v1/enforce/policy-packs— Deploy a policy packGET /v1/enforce/policy-packs— List all packsGET /v1/enforce/policy-packs/:name— Get a specific packPOST /v1/enforce/policy-packs/:name/rollback— Rollback to previous versionDELETE /v1/enforce/policy-packs/:name— Delete a packPOST /v1/enforce/policy-packs/validate— Dry-run validationGET /v1/enforce/policy-packs/stats— Pack statistics
Dashboard
The Sentinel Control Plane includes a dedicated Policy-as-Code view showing all active packs, their version, policy count, deploy mode, source hash, and deployment timestamp. From the dashboard you can rollback or delete packs with one click.
Federation NEW
Enable secure, policy-controlled interactions between AI agents across different Xybern workspaces and organizations. Federation links establish bilateral trust, and short-lived tokens allow scoped cross-org actions.
| Concept | Description |
Federation Link | A trust relationship between two workspaces (source → target) with configurable guardrails |
Federation Token | A short-lived, scope-limited, use-counted token for cross-org agent calls |
Trust Cap | Maximum trust level for external agents (prevents foreign agents from exceeding local thresholds) |
Direction | outbound (you → partner) or inbound (partner → you) |
Propose a Federation Link
from xybern import Xybern
client = Xybern(api_key="xb_your_key")
# Propose federation to a partner organization
link = client.federation.propose(
target_workspace_id="partner_workspace_id",
source_org_name="Acme Corp",
target_org_name="Partner Inc",
allowed_action_types=["data:read", "data:query"],
allowed_scopes=["read:market_data"],
max_trust_level=40.0, # External agents capped at 40
expires_in_days=90, # Link expires after 90 days
)
print(link["link_id"]) # fed_a1b2c3d4e5f6
print(link["shared_secret"]) # Exchange this securely
Accept an Inbound Link (Partner Side)
# Partner accepts the pending link
partner_client = Xybern(api_key="xb_partner_key")
result = partner_client.federation.accept(
link_id="fed_a1b2c3d4e5f6",
approved_by="security-team",
)
# Both sides now show status: "active"
Issue a Cross-Org Token
# Source org issues a token for one of its agents
token = client.federation.issue_token(
link_id="fed_a1b2c3d4e5f6",
agent_id="research-agent-01",
scopes=["read:market_data"],
action_types=["data:read"],
ttl_seconds=300, # 5-minute lifetime
max_uses=10, # Up to 10 uses
)
print(token["token"]) # Short-lived credential
print(token["expires_in"]) # 300
Use Token in Cross-Org Intercept
# Agent includes the federation token in its intercept call
result = partner_client.agents.intercept(
action_type="data:read",
action_content="Query market data for AAPL",
credential="./my_agent.cred",
federation_token=token["token"],
)
# Response includes federation proof:
# result.raw["federation"]["source_org"] → "Acme Corp"
# result.raw["federation"]["trust_cap"] → 40.0
REST API Reference
| Method | Endpoint | Description |
POST | /v1/enforce/federation/links | Propose a new federation link |
GET | /v1/enforce/federation/links | List all federation links |
POST | /v1/enforce/federation/links/:id/accept | Accept a pending link |
POST | /v1/enforce/federation/links/:id/suspend | Suspend an active link |
POST | /v1/enforce/federation/links/:id/revoke | Permanently revoke a link |
POST | /v1/enforce/federation/tokens | Issue a cross-org token |
GET | /v1/enforce/federation/stats | Get federation statistics |
Sentinel Dashboard
The Sentinel Control Plane includes a dedicated Federation view showing all active links, pending invites, token counts, and federated action totals. You can accept, suspend, or revoke links directly from the dashboard.
Human-in-the-Loop Escalation Gating NEW
When a policy decision is escalate, the agent can now block and wait until a human reviews the action on the Sentinel Control Plane dashboard. This is controlled by the wait_on_escalate parameter on the @guard decorator (default: True).
Full Flow
- Agent calls the guarded function → Sentinel intercepts and returns
decision: "escalate"with anescalation_id. - SDK polls
GET /v1/enforce/escalations/{id}/statuseveryescalation_poll_intervalseconds. - Human opens Sentinel → Control Plane, sees the pending escalation card with Approve and Reject buttons.
- If approved: agent unblocks and the wrapped function executes normally.
- If rejected (or timeout exceeded):
AgentBlockedErroris raised and the function never runs.
Code Example
from xybern import Xybern, AgentCredential, AgentBlockedError
client = Xybern(api_key="xb_your_key")
cred = AgentCredential.load("./bot.cred")
@client.agents.guard(
credential=cred,
wait_on_escalate=True, # Block until human decides (default)
escalation_timeout=300, # Wait up to 5 minutes
escalation_poll_interval=5, # Check every 5 seconds
)
def wire_transfer(amount_usd: float, recipient: str):
# Only executes if a human approves on the Sentinel dashboard
execute_wire(amount_usd, recipient)
try:
wire_transfer(50000, "vendor@example.com")
except AgentBlockedError as e:
print(f"Transfer rejected or timed out: {e}")
InterceptResult Attributes
| Attribute | Type | Description |
|---|---|---|
.decision | str | "allow", "block", or "escalate" |
.trust_score | float | Agent trust score at time of decision (0-100) |
.decision_id | str | Immutable decision record ID |
.escalation_id | str | None | Set when decision == "escalate"; used to poll for human resolution |
.policy_name | str | None | Name of the policy that triggered this decision |
.latency_ms | int | Decision latency in milliseconds |
wait_for_escalation() Method
The @guard decorator calls this automatically when wait_on_escalate=True. You can also call it directly when handling escalations manually.
# Manual escalation polling (when wait_on_escalate=False)
result = client.agents.intercept(
action_type="wire_transfer",
action_content="Transfer $50,000 to vendor",
agent_id=cred.agent_id,
)
if result.decision == "escalate":
resolution = client.agents.wait_for_escalation(
escalation_id=result.escalation_id,
timeout=300, # seconds
poll_interval=5, # seconds
)
# resolution == "approved" or "rejected"
if resolution == "approved":
execute_wire(50000, "vendor@example.com")
Escalation Status Endpoint
Poll the resolution status of a pending escalation. Returns {"ok": true, "status": "pending"|"approved"|"rejected"}. Used internally by the SDK's wait_for_escalation() method.
Dashboard: Approve / Reject
Pending escalations appear as cards in Sentinel → Control Plane. Each card shows the action details, agent identity, and two buttons: Approve and Reject. Resolving a card immediately unblocks any waiting SDK call.
The underlying endpoint is: POST /api/sentinel/enforcement/escalations/{escalation_id}/resolve with body {"resolution": "approved"|"rejected"}.
Set wait_on_escalate=False if you want fire-and-forget escalation behaviour — the SDK returns immediately with decision="escalate" and you handle the escalation_id asynchronously.
More Authorisation Examples
Common patterns for working with the Authorisation API beyond basic intercept calls.
Registering an Agent Identity
import requests
response = requests.post(
"https://www.xybern.com/api/v1/enforce/agents",
headers={"X-API-Key": "xb_your_api_key"},
json={
"agent_id": "finance_agent_001",
"name": "Finance Reporting Agent",
"role": "financial_analyst",
"allowed_tools": ["read_database", "generate_report", "send_email"],
"denied_tools": ["delete_records", "modify_schema"],
"max_delegation_depth": 2
}
)
print(response.json()["status"]) # "registered"
Creating a Policy
response = requests.post(
"https://www.xybern.com/api/v1/enforce/policies",
headers={"X-API-Key": "xb_your_api_key"},
json={
"policy_id": "no_external_email_without_approval",
"name": "Block external email without human approval",
"rules": [
{
"condition": {
"action.tool": "send_email",
"action.parameters.to": {"not_contains": "@company.com"}
},
"effect": "DENY",
"reason": "External emails require human-in-the-loop approval"
}
],
"shadow_mode": False
}
)
A2A Delegation
response = requests.post(
"https://www.xybern.com/api/v1/enforce/delegate",
headers={"X-API-Key": "xb_your_api_key"},
json={
"delegator_agent_id": "orchestrator_agent",
"delegate_agent_id": "sub_agent_001",
"scoped_tools": ["read_database", "generate_report"],
"expires_in_seconds": 1800,
"context": {"task": "q4_report", "initiated_by": "orchestrator_agent"}
}
)
print(response.json()["delegation_token"])
Breakglass — Emergency Override
response = requests.post(
"https://www.xybern.com/api/v1/enforce/breakglass",
headers={"X-API-Key": "xb_your_api_key"},
json={
"agent_id": "incident_response_agent",
"reason": "Production incident — database migration requires elevated access",
"requested_tools": ["modify_schema", "delete_records"],
"duration_seconds": 900,
"approver_id": "admin_user_007"
}
)
# Breakglass events are always logged regardless of outcome
print(response.json()["breakglass_token"])
Framework Integrations
Drop Xybern authorisation into your existing agent stack. Each integration wraps your tools or callbacks so every action is intercepted before execution — no changes to your agent logic required.
CrewAI
Wrap any CrewAI tool with a Xybern authorisation check. The guard function calls /enforce/intercept before the tool executes and raises PermissionError on a DENY decision.
# crewai_xybern.py
import requests
from crewai import Agent, Task, Crew, Tool
XYBERN_API_KEY = "xb_your_api_key"
def xybern_intercept(agent_id: str, tool_name: str, tool_input: dict) -> dict:
"""Call Xybern before every tool execution."""
return requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": XYBERN_API_KEY},
json={
"agent_id": agent_id,
"action": {"type": "tool_call", "tool": tool_name, "parameters": tool_input},
"context": {"framework": "crewai"}
}
).json()
def guarded_tool(agent_id: str, tool_fn, tool_name: str):
"""Wrap any CrewAI tool with Xybern authorisation."""
def wrapper(input_data: dict):
check = xybern_intercept(agent_id, tool_name, input_data)
if check["decision"] == "DENY":
raise PermissionError(f"Xybern denied: {check['reason']}")
return tool_fn(input_data)
return wrapper
def raw_send_email(data: dict):
print(f"Sending email to {data['to']}")
guarded_email = guarded_tool("finance_agent_001", raw_send_email, "send_email")
finance_agent = Agent(
role="Financial Analyst",
goal="Generate quarterly reports",
backstory="Expert in financial analysis",
tools=[Tool(name="send_email", func=guarded_email, description="Send email with Xybern authorisation")]
)
task = Task(description="Generate and send the Q4 report", agent=finance_agent)
crew = Crew(agents=[finance_agent], tasks=[task])
result = crew.kickoff()
AutoGen
Wrap AutoGen's function_map so every function call is authorised before execution. DENY decisions return a safe error string rather than raising, keeping the conversation intact.
# autogen_xybern.py
import requests
import autogen
XYBERN_API_KEY = "xb_your_api_key"
def authorise_action(agent_name: str, tool: str, params: dict) -> bool:
resp = requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": XYBERN_API_KEY},
json={
"agent_id": agent_name,
"action": {"type": "tool_call", "tool": tool, "parameters": params},
"context": {"framework": "autogen"}
}
).json()
return resp["decision"] == "ALLOW"
config_list = [{"model": "gpt-4", "api_key": "your_openai_key"}]
def guarded_function_map(agent_name: str, function_map: dict) -> dict:
"""Wrap an AutoGen function_map with Xybern checks."""
guarded = {}
for fn_name, fn in function_map.items():
def make_guarded(name, original_fn):
def guarded_fn(**kwargs):
if not authorise_action(agent_name, name, kwargs):
return f"Action '{name}' was denied by Xybern authorisation."
return original_fn(**kwargs)
return guarded_fn
guarded[fn_name] = make_guarded(fn_name, fn)
return guarded
def query_database(table: str, query: str):
return f"Results from {table}: [...]"
analyst = autogen.AssistantAgent(
name="analyst_agent",
llm_config={"config_list": config_list}
)
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
function_map=guarded_function_map("analyst_agent", {"query_database": query_database})
)
user_proxy.initiate_chat(analyst, message="Query the customers table and summarise the data.")
LangGraph
Add a Xybern gate as a dedicated node in your LangGraph state graph. The gate runs before any execution node and sets an xybern_approved flag in state — the execution node checks this flag before proceeding.
# langgraph_xybern.py
import requests
from typing import TypedDict
from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage, AIMessage
XYBERN_API_KEY = "xb_your_api_key"
class AgentState(TypedDict):
messages: list
next_action: str
xybern_approved: bool
def xybern_gate(state: AgentState) -> AgentState:
"""LangGraph node: authorise the next action before execution."""
next_action = state.get("next_action", "")
if not next_action:
return {**state, "xybern_approved": True}
resp = requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": XYBERN_API_KEY},
json={
"agent_id": "langgraph_agent_001",
"action": {"type": "tool_call", "tool": next_action, "parameters": {}},
"context": {"framework": "langgraph", "state_step": len(state["messages"])}
}
).json()
return {**state, "xybern_approved": resp["decision"] == "ALLOW"}
def execute_action(state: AgentState) -> AgentState:
if not state["xybern_approved"]:
msg = AIMessage(content="Action denied by Xybern authorisation policy.")
return {**state, "messages": state["messages"] + [msg]}
msg = AIMessage(content=f"Executing {state['next_action']}...")
return {**state, "messages": state["messages"] + [msg], "next_action": ""}
graph = StateGraph(AgentState)
graph.add_node("xybern_gate", xybern_gate)
graph.add_node("execute", execute_action)
graph.set_entry_point("xybern_gate")
graph.add_edge("xybern_gate", "execute")
graph.add_edge("execute", END)
app = graph.compile()
result = app.invoke({
"messages": [HumanMessage(content="Run report")],
"next_action": "generate_report",
"xybern_approved": False
})
LlamaIndex
Use make_guarded_tool to wrap any LlamaIndex FunctionTool with a Xybern check. Denied actions return a safe string that the agent can interpret without crashing.
# llamaindex_xybern.py
import requests
from llama_index.core.tools import FunctionTool
from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
XYBERN_API_KEY = "xb_your_api_key"
AGENT_ID = "llamaindex_agent_001"
def xybern_check(tool_name: str, params: dict) -> bool:
resp = requests.post(
"https://www.xybern.com/api/v1/enforce/intercept",
headers={"X-API-Key": XYBERN_API_KEY},
json={
"agent_id": AGENT_ID,
"action": {"type": "tool_call", "tool": tool_name, "parameters": params},
"context": {"framework": "llamaindex"}
}
).json()
return resp["decision"] == "ALLOW"
def make_guarded_tool(name: str, description: str, fn):
"""Wrap a LlamaIndex tool function with Xybern authorisation."""
def guarded(**kwargs):
if not xybern_check(name, kwargs):
return f"Xybern denied action: {name}. Check your authorisation policies."
return fn(**kwargs)
return FunctionTool.from_defaults(fn=guarded, name=name, description=description)
def search_documents(query: str) -> str:
return f"Documents matching '{query}': [doc1, doc2]"
def write_report(content: str, filename: str) -> str:
return f"Report written to {filename}"
tools = [
make_guarded_tool("search_documents", "Search internal documents", search_documents),
make_guarded_tool("write_report", "Write a report to disk", write_report),
]
llm = OpenAI(model="gpt-4")
agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)
response = agent.chat("Find all documents about Q4 and write a summary report.")
print(response)
Custom Pipelines
Use the XybernClient class and its @guard decorator to wrap any Python function in your own agent pipeline. Works with any orchestration system — no framework dependency required.
# custom_pipeline_xybern.py
import requests
from functools import wraps
XYBERN_API_KEY = "xb_your_api_key"
class XybernClient:
"""Minimal Xybern client for custom agent pipelines."""
def __init__(self, api_key: str, agent_id: str):
self.api_key = api_key
self.agent_id = agent_id
self.base_url = "https://www.xybern.com/api/v1"
def _headers(self):
return {"X-API-Key": self.api_key, "Content-Type": "application/json"}
def intercept(self, tool: str, params: dict, context: dict = None) -> dict:
payload = {
"agent_id": self.agent_id,
"action": {"type": "tool_call", "tool": tool, "parameters": params},
"context": context or {}
}
return requests.post(
f"{self.base_url}/enforce/intercept",
headers=self._headers(),
json=payload
).json()
def guard(self, tool_name: str):
"""Decorator: wrap any function with Xybern authorisation."""
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = self.intercept(tool_name, kwargs)
if result["decision"] == "DENY":
raise PermissionError(f"[Xybern] Denied: {result['reason']}")
return fn(*args, **kwargs)
return wrapper
return decorator
# Usage
xybern = XybernClient(api_key=XYBERN_API_KEY, agent_id="custom_pipeline_agent")
@xybern.guard("send_payment")
def send_payment(amount: float, recipient: str, currency: str = "USD"):
print(f"Sending {currency} {amount} to {recipient}")
return {"status": "sent"}
@xybern.guard("read_customer_data")
def read_customer_data(customer_id: str, fields: list):
return {"id": customer_id, "data": "..."}
# These will be authorised by Xybern before executing
send_payment(amount=50000.00, recipient="vendor@external.com", currency="USD")
read_customer_data(customer_id="cust_001", fields=["name", "email", "balance"])
Error Handling
| Status | Meaning |
|---|---|
200 |
Success |
400 |
Bad request |
401 |
Invalid API key |
429 |
Rate limited |
500 |
Server error |
Caching & Deduplication
Identical content verified within a 5-minute window returns the cached result instantly — no LLM call, no latency, no cost.
Cached responses include "cached": true in the response body so you can distinguish them from fresh verifications.
How It Works
- Content is hashed with SHA-256 on arrival
- The hash is checked against a per-workspace Redis cache
- If a match is found within the 5-minute TTL, the cached result is returned with a fresh
verification_id - If no match, the full verification pipeline runs and the result is cached
Agent RBAC (Role-Based Access Control)
Define reusable roles with granular permissions and assign them to agents. Roles are evaluated during every intercept call — if any active role denies the action type, the request is blocked before policies run.
| Concept | Description |
|---|---|
| Role | A named permission bundle with allowed/denied action types, scopes, and a minimum trust threshold. |
| Assignment | A many-to-many link between roles and agents. One agent can hold multiple roles. |
| Inheritance | A role can inherit from a parent role, composing permissions up the hierarchy. |
| Wildcards | Action type patterns like payment:* match any action starting with payment:. |
Create a Role
from xybern import Xybern
client = Xybern(api_key="xb_your_key")
role = client.roles.create(
name="finance-agent",
description="Can read and transfer payments, but not admin actions",
allowed_action_types=["payment:*", "report:read"],
denied_action_types=["admin:*"],
min_trust_level=60.0,
)
print(role["role"]["role_id"])
Assign a Role to an Agent
client.roles.assign(
role_id="role_abc123",
agent_id="agent_xyz789",
)
Role Inheritance
# Create a base "reader" role
reader = client.roles.create(
name="reader",
allowed_action_types=["*.read"],
)
# Create "analyst" that inherits from "reader" and adds more
analyst = client.roles.create(
name="analyst",
allowed_action_types=["report:generate", "data:query"],
inherits_from=reader["role"]["role_id"],
)
How Enforcement Works
- Agent submits an action via
client.agents.intercept(...) - Control plane resolves the agent and loads all active roles
- If any role's denied list matches the action type → block
- If no role's allowed list matches → block
- If the agent's trust level is below the role's
min_trust_level→ block - Otherwise, proceed to policy evaluation
REST API Reference
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/enforce/roles | Create a role |
GET | /v1/enforce/roles | List roles |
GET | /v1/enforce/roles/:id | Get a role |
PUT | /v1/enforce/roles/:id | Update a role |
DELETE | /v1/enforce/roles/:id | Delete a role |
POST | /v1/enforce/roles/:id/assign | Assign role to agent |
POST | /v1/enforce/roles/:id/unassign | Unassign role from agent |
GET | /v1/enforce/roles/:id/agents | List agents in role |
GET | /v1/enforce/roles/stats | RBAC statistics |
Dashboard
The Roles (RBAC) tab in the Sentinel dashboard displays all roles, their agent counts, allowed/denied action types, trust thresholds, and inheritance chains. You can delete roles directly from the UI.
Webhooks & Real-Time Event Streaming
Subscribe to authorization events and have them delivered to any HTTP endpoint in real time. Every payload is HMAC-SHA256 signed with your webhook secret.
Supported Event Types
| Event | Fires when... |
|---|---|
decision.block | Any agent action is blocked |
decision.escalate | A decision requires human review |
decision.allow | An action is approved (opt-in, high volume) |
breakglass.triggered | Emergency override activated |
breakglass.deactivated | Emergency override ended |
federation.link_proposed | New cross-org trust link proposed |
federation.token_issued | Cross-org token minted |
policy.deployed | A policy pack goes live |
policy.rollback | A policy pack is rolled back |
agent.registered | New agent registered |
agent.trust_changed | Agent trust score crosses a threshold |
rbac.role_assigned | A role is assigned to an agent |
temporal.window_opened | A JIT access window opens |
temporal.window_expired | A JIT access window closes |
Create a Webhook
from xybern import Xybern
client = Xybern(api_key="xb_your_key")
hook = client.webhooks.create(
url="https://hooks.slack.com/services/T0/B0/xxx",
events=["decision.block", "breakglass.triggered"],
description="Slack alerts for security events",
)
# Save hook["webhook"]["secret"] to verify signatures later
print(hook["webhook"]["webhook_id"])
Payload Format
{
"event": "decision.block",
"timestamp": "2026-04-01T19:32:14.003Z",
"workspace_id": "f5300764...",
"data": {
"decision_id": "enf_a1b2c3d4e5f6",
"agent_id": "agent_35c86445e7e2",
"action_type": "admin:delete_workspace",
"decision": "block",
"decision_path": "rbac",
"reasoning": "Action not allowed by agent roles",
"trust_score": 22.8
}
}
Signature Verification
Every delivery includes an X-Xybern-Signature header. Verify it server-side:
import hmac, hashlib
def verify_webhook(payload_bytes, signature_header, secret):
expected = hmac.new(
secret.encode(), payload_bytes, hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
Retry Policy
Failed deliveries are retried with exponential backoff (1s, 2s, 4s, 8s, 16s). After 50 consecutive failures, the webhook is auto-disabled. Re-enable it via the API or dashboard.
REST API Reference
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/enforce/webhooks | Create a subscription |
GET | /v1/enforce/webhooks | List subscriptions |
GET | /v1/enforce/webhooks/:id | Get a subscription |
PUT | /v1/enforce/webhooks/:id | Update a subscription |
DELETE | /v1/enforce/webhooks/:id | Delete a subscription |
POST | /v1/enforce/webhooks/:id/test | Send a test event |
GET | /v1/enforce/webhooks/:id/deliveries | List delivery history |
POST | /v1/enforce/webhooks/:id/rotate-secret | Rotate signing secret |
GET | /v1/enforce/webhooks/stats | Webhook statistics |
Xybern API Documentation v1.21 · info@xybern.com