Skip to content

Proof of Authorisation

The Xybern Authorisation Layer does not just authorise every agent action before it executes, it can prove it did, to anyone, without asking them to trust Xybern.

Authorise every action. Prove every authorisation.

Three artifacts make up the proof surface:

Artifact What it is Who it's for
Authorisation Receipt A per-action record proving one specific action was authorised, refused, escalated, or terminated before execution, with the rules and Risk Verdict that decided it and the Vault certificate that makes it independently verifiable Engineers, counterparties, anyone disputing a single action
Proof of Authorisation Pack A signed, self-contained bundle covering a period: every outcome, per-agent breakdown, receipts, the covered Vault entries with their signatures, and an ECDSA signature over the whole pack Whoever asks your customer to prove their agents are controlled: an auditor, a regulator, an insurer, a procurement team
Authorisation Coverage A live view of which agents operate under a declared authority (Agent Boundary, Intent Contract, agent-scoped Mandate) and which still hold standing, unscoped authority You, to close the gaps

Everything is built on the Provenance Vault: every decision is already hash-chained and asymmetrically signed the moment it happens, so proofs are generated from records that were sealed before anyone asked for them, never reconstructed after the fact.

Authorisation Receipts

Every enforcement decision can be exported as a standalone receipt.

from xybern import Xybern

client = Xybern(api_key="xb_live_...")

receipt = client.enforce.get_receipt("enf_ab12cd34ef56")
curl -H "X-API-Key: xb_live_..." \
  https://www.xybern.com/api/v1/enforce/decisions/enf_ab12cd34ef56/receipt

Response shape:

{
  "ok": true,
  "receipt": {
    "format": "xybern-authorisation-receipt/v1",
    "receipt_id": "rcpt_enf_ab12cd34ef56",
    "workspace_id": "ws_...",
    "issued_at": "2026-07-08T10:00:00Z",
    "action": {
      "action_type": "send_payment",
      "summary": "Refund of $180 to customer #4411",
      "content_sha256": "9f2c...",
      "occurred_at": "2026-07-08T09:58:41Z",
      "agent": {"agent_id": "agt_...", "name": "Refund Agent"}
    },
    "authorisation": {
      "outcome": "authorised after review",
      "decision": "escalate",
      "rules_triggered": [{"name": "Refunds above $100 need review"}],
      "verdict": {"dimensions": {"blast_radius": {"score": 41}}},
      "escalation": {
        "status": "approved",
        "resolved_by": "usr_...",
        "resolved_at": "2026-07-08T09:59:30Z"
      }
    },
    "proof": {
      "format": "xybern-decision-certificate/v1",
      "signing_keys": ["..."],
      "entries": ["..."]
    },
    "proof_available": true
  }
}

The proof block is a complete Vault certificate: the sealed entry, its chain position, the ECDSA P-256 signature, the public keys, and the exact hashing recipe. A recipient can verify it offline; altering any field breaks the verification.

Outcomes are reported in plain authorisation vocabulary: authorised, refused, escalated, authorised after review, refused after review, terminated.

The agent's own reason for each step

Receipts distinguish two kinds of reasoning: the authorisation reasoning (why the Authorisation Layer decided what it decided) and the agent's stated reason (why the agent says it took the step). Pass the latter at intercept time:

client.enforce.intercept(
    action_type="send_payment",
    action_content="Refund $180 to customer #4411",
    metadata={"connector": "stripe", "amount_usd": 180},
    reasoning="Customer reported a duplicate charge on order #4411; refund approved per returns policy.",
)

The raw API accepts the same as a top-level agent_reasoning field (or metadata.agent_reasoning). MCP tool calls that carry a reasoning, justification, or reason argument are captured automatically by the gateway. The stated reason is stored with the decision, sealed into the Vault entry, and rendered on every receipt and in the PDF appendix of every pack, so a reviewer sees, per step, what the agent tried to do, with which tools and parameters, why the agent says it did it, and why it was authorised or refused.

In the dashboard, every decision panel has a Download Authorisation Receipt button next to its Vault proof.

On whose authority: Charter version and lineage

Every receipt now answers two further questions, computed at the moment of decision and never reconstructed later.

Which Charter was in force. authorisation.charter carries the Charter version hash, who signed that version, when, and how many mandates it contained, plus the compiled rule set hash. The version hash is a plain sha256 over the canonical mandate list, so anyone holding the receipt can fetch the version (GET /v1/enforce/charter/versions/<hash>) and recompute the hash without any secret. A later change to the Charter creates a new version and never rewrites what earlier decisions were taken under.

On whose authority the action rested. authorisation.lineage is the chain from the accountable human to the action: the human who signed the Charter version, the mandates that spoke on this decision, the agent's access profile, the intent contract, the temporal window or break-glass event if any, the full delegation chain from the root grant to the leaf grant with the principal it acted for, the agent's credential, the session, and the action. authorisation.lineage_text is the same in one sentence, in the language of the dashboard.

"charter": {
  "hash": "3f9a1c...e2",
  "signed_by": "cfo@bank.example",
  "signed_at": "2026-09-11T08:12:04Z",
  "mandate_count": 7,
  "policy_set_hash": "b71d...09"
},
"lineage": {
  "format": "xybern-lineage-v1",
  "accountable": {"kind": "human", "id": "cfo@bank.example", "via": "charter", "charter_hash": "3f9a1c...e2"},
  "mandates": [{"mandate_id": "mnd_2c1e...", "outcome_text": "No payment above SAR 500,000 without a named approver", "status": "active"}],
  "access_profile": {"profile_id": "apf_...", "name": "Treasury operations", "status": "active"},
  "delegation": {"grant_id": "dlg_9b...", "source_agent_id": "agt_treasury", "root_agent_id": "agt_treasury",
                 "chain": [{"grant_id": "dlg_9b...", "source_agent_id": "agt_treasury", "target_agent_id": "agt_payments", "depth": 1}],
                 "principal_authority": {"agent_id": "agt_treasury", "intersection": "applied", "status": "active"}},
  "agent": {"agent_id": "agt_payments", "name": "Payments Agent", "identity_verified": true},
  "session": {"session_id": "ses_..."},
  "action": {"decision_id": "enf_...", "action_type": "send_payment", "decision": "allow"}
},
"lineage_text": "This action rests on the authority of cfo@bank.example, who signed Charter version 3f9a1c... (7 mandates), the mandate that spoke: “No payment above SAR 500,000 without a named approver”, within the access profile “Treasury operations”, delegated from agt_treasury via grant dlg_9b..., evaluated against both authorities, acting agent Payments Agent with a verified credential, in session ses_...."

When no Charter is in force the lineage says so (accountable.kind is none) rather than inventing an authority. Both blocks are also sealed inside the Vault entry of the decision and shown in the decision dialog under On whose authority.

Charter versions are listed at GET /v1/enforce/charter/versions and in the dashboard's Charter view; the SDK exposes list_charter_versions() and get_charter_version(hash).

Proof of Authorisation Packs

A pack answers the question "prove your agents only did what they were authorised to do between these dates" in one signed document.

pack = client.enforce.create_proof_pack(
    from_date="2026-06-01T00:00:00",
    to_date="2026-06-30T23:59:59",
    name="June, all agents",
)

packs = client.enforce.list_proof_packs()
full = client.enforce.get_proof_pack(pack["pack"]["pack_id"], verify=True)
curl -X POST -H "X-API-Key: xb_live_..." -H "Content-Type: application/json" \
  -d '{"from_date": "2026-06-01T00:00:00", "to_date": "2026-06-30T23:59:59"}' \
  https://www.xybern.com/api/v1/enforce/proof-packs

Each pack contains:

  • a plain-language statement of what the pack attests to
  • the summary: total actions, how many were authorised, refused, escalated, terminated, and how escalations were resolved by humans
  • a per-agent breakdown
  • the coverage snapshot at generation time
  • one receipt per action (up to 2,000 per pack; narrower periods give complete coverage)
  • the covered Vault entries with their asymmetric signatures and the public keys
  • an integrity block: the SHA-256 of the canonical pack body and an ECDSA P-256 signature over it

Pack generation is itself sealed to the Vault, so the record that a proof was produced is as tamper-evident as the proofs inside it.

Every pack gets a read-only share link, https://www.xybern.com/proof/<pack_id>?t=<token>, that requires no Xybern account. The page re-verifies the pack signature server-side on every view, shows the summary, and offers the signed JSON and a PDF. Revoke or rotate the link at any time from the dashboard or the share endpoint; a revoked link stops working immediately.

Verifying a pack independently

  1. Download the signed JSON.
  2. Remove the integrity block and recompute sha256 over the canonical JSON (sort_keys=True, separators (",", ":")).
  3. Check the ECDSA P-256 signature in integrity.signature against the public keys enclosed in vault_proof.signing_keys.
  4. Optionally verify each enclosed Vault entry with the same keys and the enclosed hashing recipe.

No Xybern access, account, or goodwill required at any step.

Proof of Constraint Observance and Authority Time Travel

A proof pack can also be generated over a constraint (a rule, a mandate, a mission, an invariant or an action family) and a window: the channels the layer controlled, the actions the constraint spoke to, the violations observed and a coverage statement, signed and verifiable like any pack. Every decision also answers Authority at decision time, the authority as sealed when it was made and what changed since. Both are described in Passports, Time Travel, Constraint Observance, retention.

Authorisation Coverage

coverage = client.enforce.get_authorisation_coverage(window_days=30)
{
  "coverage": {
    "headline": {
      "agents_total": 12,
      "agents_covered": 9,
      "coverage_pct": 75,
      "standing_authority_agents": 2,
      "workspace_charter_active": true,
      "actions_recent": 4180
    },
    "agents": [
      {
        "agent_id": "agt_...",
        "name": "Refund Agent",
        "access_profile": "active",
        "intent_contract": true,
        "covered": true,
        "standing_authority": false,
        "actions_recent": 812
      }
    ]
  }
}

An agent is covered when it operates under at least one declared authority: an active or shadow Agent Boundary, an active Intent Contract, or an agent-scoped Mandate. An agent with recent activity and none of these holds standing authority, the thing the Authorisation Layer exists to eliminate. The dashboard's Proof of Authorisation tab shows the same view with one-click paths to close each gap.

Endpoint reference

Method Endpoint Purpose
GET /v1/enforce/decisions/<id>/receipt Authorisation Receipt for one decision
POST /v1/enforce/proof-packs Generate and sign a pack (from_date, to_date, agent_id, name, all optional)
GET /v1/enforce/proof-packs List packs
GET /v1/enforce/proof-packs/<pack_id> Full signed pack document, ?verify=1 re-verifies server-side
GET /v1/enforce/coverage Authorisation coverage, ?window_days=30
GET /proof/<pack_id>?t=<token> Public read-only verification page (no auth)

SDK: get_receipt, create_proof_pack, list_proof_packs, get_proof_pack, get_authorisation_coverage, available from xybern 1.20.0; intercept(reasoning=...) from 1.21.0.