One chain. Every proof.

Two families of endpoints, one tamper-evident SHA-256 chain underneath. Govern scores and seals AI decisions (needs a key). Notary seals a fingerprint of anything — a post, an identity, payment details — with no key and no content ever leaving your system. Base URL: https://sebbi.pro

QuickstartNotary APILink your stack/api/governAuthority continuityExported proofReceiptsChallengesVerificationMonitoringSovereignErrors

Quickstart — seal something in one call

No account needed for the Notary. Fingerprint your content locally, send the hash, get a sealed receipt back:

curl -X POST https://sebbi.pro/api/post/seal \
  -H "Content-Type: application/json" \
  -d '{"fingerprint":"<64-char sha-256 of your content>"}'
{
  "sealed":      true,
  "seal":        "43ac8582…",   // the chain block hash
  "block_index": 1042,
  "sealed_at":   1789420000.12,
  "code":        "43ac85820f19"   // 12-char public verify code
}
Your content never leaves your machine. You hash it locally; only the 64-character fingerprint is sent. The chain proves a document with that exact fingerprint existed at that moment — it never sees the document itself.

Notary API — seal a fingerprint of anything no key

Three notaries, one pattern: POST a fingerprint to seal, GET to verify. All public, all free. The seal is permanent and tamper-evident; re-sealing the same fingerprint returns the original receipt with already_registered: true.

Post notary — prove exact text existed

POST /api/post/seal — body {"fingerprint":"<sha256>"}. Seals the fingerprint of a post, article, or document.

GET /api/verify-post?content=<sha256> — returns {"verified":true,"block_index":…,"sealed_at":…,"seal":…} if that exact fingerprint is sealed, else {"verified":false}.

Identity notary — prove a profile is the original

POST /api/identity/seal — body {"fingerprint":"<sha256>", "public":true, "profile":{…}}. If public is set, a limited set of display fields (name, title, bio, linkedin, facebook, org) is stored so a checker can show them; otherwise only the fingerprint is sealed. Returns {sealed, seal, block_index, sealed_at, code}.

GET /api/identity/check?code=<12+ chars> — accepts the short code or full fingerprint. Returns {found, fingerprint, registered_at, seal, block_index}, plus profile if it was sealed public.

Payment notary — stop invoice fraud

POST /api/payment/seal — body {"fingerprint":"<sha256 of the real bank details>", "display":{"business":…, "sort_masked":…, "account_masked":…}}. Only masked display fields are stored; the true details never are. Returns {sealed, seal, block_index, sealed_at, code}.

GET /api/payment/check?code=<12+ chars>&fp=<optional full sha256> — looks up the seal. If you also pass fp, it returns match: true/falseMATCH, MISMATCH, or NO_SEAL. Either way the verification itself is sealed and returned as a receipt, giving provable evidence of the check under PSR reimbursement rules.

Integration is a few lines wherever your system creates the thing worth proving. The shape is always the same:

# 1. fingerprint locally — content stays with you
import hashlib, requests
fp = hashlib.sha256(content.encode()).hexdigest()

# 2. seal the fingerprint
r = requests.post("https://sebbi.pro/api/post/seal",
                  json={"fingerprint": fp}).json()
code = r["code"]        # store this next to your record

# 3. anyone verifies later — no account
v = requests.get("https://sebbi.pro/api/verify-post",
                 params={"content": fp}).json()
# v["verified"] == True

Wire that into the moment a post is published, an invoice is issued, or a profile is created, and every record from then on carries provable, tamper-evident proof — automatically. That is the whole integration.

POST /api/govern POSTBearer key

The decision engine: score an event, get a verdict in ~28ms, sealed before the response returns. All seven fields required. Deterministic — identical inputs always produce identical outputs.

curl -X POST https://sebbi.pro/api/govern \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id":    "user_123",
    "action":     "payment",
    "amount":     49.99,
    "country":    "UK",
    "device_id":  "dev_abc",
    "anomaly":    0.1,
    "device_risk":0.05
  }'
FieldTypeMeaning
user_idstringYour stable identifier for the acting user. Trust is learned per user_id.
actionstringWhat they're doing — payment, login, message, anything.
amountnumberMonetary value if relevant, else 0. Log-scaled internally.
countrystringISO-style code. Country changes and off-allowlist jurisdictions raise score.
device_idstringDevice identifier for velocity correlation.
anomaly0–1Your behavioural-anomaly signal, if you have one. 0 if not.
device_risk0–1Your device-risk signal, if you have one. 0 if not.

Verdicts: score < 0.35 → ALLOW · < 0.70 → CHALLENGE · else BLOCK. Every verdict carries plain-language reasons. Trust moves per decision: earned slowly on ALLOW, lost 8× faster on BLOCK — burst attacks self-amplify.

Authority continuity — derive it, don't look it up Bearer key

Everything else here proves what your system did. This proves it was entitled to. An agent acts; it got its authority from another agent, which got it from a system, which got it from a person. A permission check answers one hop. An audit log describes the aftermath. Neither derives anything, so neither can see authority widening three delegations back.

Every grant points at a parent and terminates at a named human. Scope, limits, purpose and validity must narrow at every hop, and the whole chain is re-derived at the instant of execution rather than trusted from the instant of issue.

Issue a root grant

A root must be issued by a human, must state a purpose, and must expire. Authority with no stated purpose cannot be checked for intent drift later, so it is refused.

curl -X POST https://sebbi.pro/x/continuity/issue \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"issuer":"you@company.com","issuer_kind":"human",
       "subject":"orchestrator",
       "scope":["payments.refund","payments.read"],
       "constraints":{"max_amount":5000,"allowed_currency":["GBP"]},
       "purpose":"resolve customer refund complaints",
       "purpose_tags":["refunds","support"],
       "not_after":1786400000,
       "delegations_left":2}'
{ "grant": "g_9ec009c0a02345169788", "depth": 0,
  "risk_accepted_by": "you@company.com",
  "digest": "…", "block_index": 842,
  "note": "Sealed at issue. Any later edit to the stored grant changes
           its digest and fails integrity." }

Delegate it onward

Same endpoint, with a parent. A child may narrow and never widen. Try raising max_amount above the parent's and it is refused with the axis named.

curl -X POST https://sebbi.pro/x/continuity/issue \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"parent":"g_9ec…","issuer":"orchestrator","issuer_kind":"agent",
       "subject":"refund-agent","scope":["payments.refund"],
       "constraints":{"max_amount":200,"allowed_currency":["GBP"]},
       "purpose":"issue small refunds","purpose_tags":["refunds"],
       "not_after":1786390000,"delegations_left":0}'
Risk acceptance. A grant that only narrows inherits the acceptor above it. A grant that can delegate onward must name its own risk_accepted_by and cannot inherit one — handing an agent the power to hand authority on again is a new risk that did not exist when the person above signed up to it. A lineage where nobody has accepted the risk refuses to act at all.

Exercise it

The whole path is walked and re-checked now, against this action, with these parameters.

curl -X POST https://sebbi.pro/x/continuity/exercise \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"grant":"g_9ec…","action":"payments.refund",
       "params":{"amount":150,"currency":"GBP"},
       "purpose_tag":"refunds"}'
{ "verdict": "ALLOW",
  "authority_verdict": "ALLOW", "risk_verdict": "ALLOW",
  "authorised_by": "you@company.com",
  "executed_by": "refund-agent",
  "risk_accepted_by": "you@company.com",
  "delegation_depth": 1,
  "lineage": [ … every hop, root first … ],
  "lineage_digest": "…", "params_digest": "…",
  "block_index": 843 }

A refusal names where it broke rather than simply denying:

{ "verdict": "BLOCK",
  "broken_at": "g_mid…", "broken_invariant": "boundary_integrity",
  "reasons": ["amount=900 exceeds max_amount=200"] }
VerdictMeaning
ALLOWEvery invariant held and the risk engine agreed. Derivable from a valid human grant.
CHALLENGENothing provably broken, nothing provably fine. Triggered by a purpose the grant does not carry, a wildcard too broad to review, or a parameter no ancestor constrains. Escalated rather than guessed.
BLOCKAn invariant failed. The response names the grant and the invariant.
Composition. The verdict is the worse of the authority verdict and your existing risk engine's. Either can stop an action; neither waves one through alone. If the engine cannot be reached or returns something unreadable, the result degrades to CHALLENGE — a missing risk opinion is missing, not favourable.

Bind the execution to the decision

An ALLOW is a decision about a request that may not be the request that ran. confirm re-derives the parameter digest from what actually executed and compares it, enforces the validity window, and can be spent exactly once — enforced by a unique index rather than a read followed by a write, so concurrent attempts cannot both win.

curl -X POST https://sebbi.pro/x/continuity/confirm \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"evaluation":"e_05da…","action":"payments.refund",
       "params":{"amount":150,"currency":"GBP"},"outcome":"executed"}'

Revoke

POST /x/continuity/revoke — body {"grant":"g_…","reason":"…"}. Transitive by derivation: everything beneath stops evaluating immediately, with no descendant needing to be found. Actions already evaluated remain exactly as they were decided — revocation does not rewrite history.

EndpointBody / queryNotes
POST /x/continuity/issueissuer, issuer_kind, subject, scope, constraints, purpose, purpose_tags, not_after, delegations_left, parent, risk_accepted_byRoot needs a human issuer. No expiry and no purpose are both refused.
POST /x/continuity/exercisegrant, action, params, purpose_tagRe-derives the whole lineage now. Sealed whichever way it goes.
POST /x/continuity/confirmevaluation, action, params, outcomeOne evaluation binds to one execution. A rejected binding is sealed too.
POST /x/continuity/revokegrant, reasonTransitive. Sealed.
GET /x/continuity/specno key The derivation rules in full, sufficient to reimplement the evaluator.
GET /x/continuity/decisionslimitno key Real sealed evaluations. Blocks listed beside allows. Each carries its own links.
GET /x/continuity/decision?evaluation=no key One sealed decision in full.
GET /x/continuity/trace?grant=no key The whole authority path, root first, with the effective constraints across it.

The exported proof — and the verifier that doesn't need us no key

A proof you can only check with the prover's own online tool is a reassurance, not a proof. So any authority decision exports as a self-contained signed bundle, and the checker runs on your machine with the network off.

curl -sO https://sebbi.pro/verify-authority.py
curl -s "https://sebbi.pro/x/continuity/proof" | python3 verify-authority.py -

With no evaluation the proof route returns the most recent decision, so you can start knowing nothing. Pass one from /x/continuity/decisions for a specific decision.

{ "bundle_version": "1.0",
  "issued_by": { "algorithm": "Ed25519", "public_key": "…" },
  "decision": { "verdict": "ALLOW", "authority_verdict": "ALLOW",
                 "risk_verdict": "ALLOW", "lineage_digest": "…",
                 "params_digest": "…", "broken_at": null },
  "request":  { "action": "payments.refund", "params": {…} },
  "lineage":  [ … every grant as it stood at that instant … ],
  "chain":    { "audit_hash": "…", "block_index": 843 },
  "rules":    { … how every digest and the signature are computed … },
  "signature": "…" }

The verifier does four separate things, each able to fail on its own: signature (Ed25519 over the canonical bundle), integrity (every digest recomputed from the fields in front of it), derivation (the whole authority path re-run from the published rules), and agreement (its own verdict compared with ours — a disagreement is reported as our failure, not its).

No dependencies, no network, no telemetry. Python standard library only, including the Ed25519 implementation, so nothing has to be installed to check a proof. It never contacts sebbi.pro and reports nothing back — a verification tool that phones home to the party being verified is not a verification tool. Check script_sha256 at /x/verifier/status against what you downloaded, and read it before you run it.

The refusal proof

When authority cannot be derived you do not get a bare BLOCK. The bundle carries the grant and the invariant that failed, and the verifier independently reproduces that failure at the same hop. An agent that can prove it was not authorised is a different object to one that was merely denied — and it is what a counterparty needs when an action does not happen.

RESULT: VERIFIED - BLOCK
This is a proof that the action was NOT authorised, and where it failed.
Checked with no network access, no dependencies, and nothing taken on
the issuer's word except the meaning of their public key.
EndpointReturns
GET /x/continuity/proofThe signed bundle. No evaluation returns the most recent; ?evaluation= returns a specific one.
GET /x/continuity/pubkeyThe Ed25519 public key, RFC 8032, verifiable with any standard library.
GET /verify-authority.pyThe verifier itself, as a plain file.
GET /x/verifier/statusThe script's size and SHA-256, to check what you downloaded.
Honest limits. This proves authority was derivable from a human grant — not that the human should have granted it, and not that the parameters describe something that really happened. Grants are authenticated by sealing rather than per-issuer signatures, so an outside party verifies them through the chain rather than entirely offline. The risk half of a composed verdict cannot be re-derived without the scoring engine, which the bundle states rather than glosses over. And decisions made before signed proofs shipped carry no lineage snapshot — reconstructing one now would describe today's authority rather than the authority the action was judged against, so the route refuses instead.
Environment: set CONTINUITY_SIGNING_SEED to 64 hex characters. Without it a key is generated into the database on first use and a warning is printed — workable, but a signing key living in the same database as the records it signs is the first thing a reviewer will ask about. Changing it rotates the public key, so every proof already handed out verifies against the old one.

Receipts — omission is countable

Every govern response includes receipt_seq: a per-key sequence issued in the same transaction as the chain write. Sequences are gapless by construction. Store them: if you ever hold receipts 46 and 48 with no 47, a record has been omitted — provable by arithmetic. Edited records break the chain. Missing records break the sequence.

Challenges — the workflow's built in

When the verdict is CHALLENGE, the response includes a hosted resolution flow — you don't build exception UX:

{
  "decision": "CHALLENGE",
  "challenge_url":        "https://sebbi.pro/verify-challenge?token=…",
  "challenge_status_url": "https://sebbi.pro/api/challenge/status?token=…",
  "challenge_expires_in": 900
}

Show challenge_url to your user (link, redirect, or iframe). They confirm or deny on our hosted page; the resolution is sealed into the chain as its own block; you poll challenge_status_url until resolved: true. Tokens are stateless and HMAC-signed.

Three-line integration: if decision == CHALLENGE → surface challenge_url → poll status. That's the whole exception workflow.

Verification — public, unauthenticated

EndpointReturns
GET /api/verify-chainWhole-chain integrity: {"valid":true,"blocks":N,"tip":…}. Anyone can run it — auditors, regulators, your customers.
GET /api/inclusion?hash=Whether a full 64-char receipt hash is sealed, with its block index and sequence.
GET /api/regulation-mapVersioned, hash-sealed mapping of engine features → legal obligations (EU AI Act Arts. 9/12/13/14, OSA, Children's Code).
GET /api/partner/status?badge=Partner account standing and platform status as separate fields — independently attributable.
GET /api/specThis API, describing itself — machine-readable.

Monitoring — see it while it happens

GETBearer key /api/pulse — your last hour at a glance:

{ "last_hour": { "ALLOW": 412, "CHALLENGE": 9, "BLOCK": 3 },
  "recent": [ { "ts":…, "action":"transfer", "decision":"BLOCK",
               "score":0.87, "reasons":["velocity_spike","low_trust"],
               "sealed":"8db26b04…" }, … ],
  "chain_tip": "bf9257ab…" }

GETBearer key /api/coverage — reconciliation in one call: receipts issued vs blocks sealed, complete: true/false.

Real-time alerts: the moment the engine BLOCKs on your traffic, the account email receives the sealed evidence — user, action, score, reasons, audit hash. Throttled to one per hour so a burst can't flood your inbox.

Sovereign deployment

The engine runs entirely inside your network — decisions, chain and database on your hardware; nothing leaves your building. Licensing is offline: HMAC-signed 365-day tokens validated with pure cryptography, no phone-home. Ask via /contact. Paid plans can mint air-gap tokens at POST /api/generate-airgap-token.

Errors

StatusBodyMeaning
400valid sha-256 fingerprint requiredNotary needs a 64-char hex fingerprint. Govern needs all seven fields.
401api_key_required / invalid_api_keyGovern only — send Authorization: Bearer YOUR_KEY. Notary needs no key.
403account_inactiveAccount disabled — contact us.
429quota_exceeded / rate_limit_minute / rate_limit_hourFree tier spent, or per-key limits: 60/min, 1000/hr.
500internalLogged server-side; detail is never leaked to callers.
Kick the tyres without writing code: seal a post at sebbi.pro/seal, verify it at sebbi.pro/verify, or watch Brain judge a live instruction at sebbi.pro/brain. Same maths, same catch.