Build against Situs
The developer guide, by audience: venue operators, enterprises and startups, standard adopters. The snippets are rendered from the running stack's own values: its URLs, its kids from /.well-known/keys.json, its plugins from /plugins.
- What a venue is
- The enforcement point and its secret broker
- Publishing keys
- Levels: what a venue can claim
- Getting listed
- Quickstart: a mandate, a chat completion, a receipt, a verification
- Point any OpenAI-compatible client at the gateway
- Write and sign a mandate
- Choose a plugin and publish a profile
- What comes back, and where receipts land
- Verify offline
- Ship receipts into your own systems
- JSON Schemas
- Canonicalization and signing
- The five signature slots
- The transparency log
- The verifier
- Compatibility promise
- Operating Rules, and the license
Venue operators
What a venue is
A venue is where an action is carried out: one enforcement point (the pep container) with its secret broker, next to the authority that decides. The venue is the only holder of the credential that reaches the counterparty; an agent never holds it. Every decision the enforcement point makes is signed with the venue's key, and the receipt names the venue in where.venue.
The enforcement point and its secret broker
The pep reads each plugin's declarative connector.json (no plugin code runs in it): the environment variable with the counterparty's base URL, the operations, where the acknowledgment's fields sit, and the counterparty's signing kid. For each valid single-use grant the secret broker mints a short-lived token (PEP_BROKER_SECRET, HMAC) for one call; the token is never returned to the gateway or the agent.
| setting | what it is |
|---|---|
| SITUS_KEYS_DIR | the venue's keys; pep.pem signs enforcement decisions |
| PEP_BROKER_SECRET | the broker's secret; set your own outside local development |
| AUTHORITY_URL | where grants are redeemed and evidence is sent |
| <connector base_url env> | each plugin's counterparty URL, named in its connector.json |
| SITUS_GCE_IDENTITY | 1 on a GCE VM: decisions carry the cloud's identity token (L1-declared) |
Publishing keys
Every party's public key is served at /.well-known/keys.json (the authority at https://situs-vm.o11r.com/api/authority/.well-known/keys.json, the pep at its own).
This page was rendered without a running stack, so it shows no key: read the kids and fingerprints from the running stack's keys.json.
curl -s https://situs-vm.o11r.com/api/authority/.well-known/keys.json | jq 'map_values(.fingerprint)'Levels: what a venue can claim
what.level_achieved in every receipt says what the venue proved about where it ran. Only the two labelled levels exist in this build, and both are always shown as what they are: a simulated or declared level never renders green, on the console or in the verifier.
| level | name | what backs it | in this build |
|---|---|---|---|
| L0 | declared | the venue says where it runs; nothing checks it | not a claim this build makes |
| L1 | confidential VM, attested | a hardware quote binds the running image to the venue key | not built |
| L2 | L1 plus a GPU quote | the model's accelerator attests too | not built |
| L1-sim | simulated, local | software measurement of the gateway only; hardware quote fields present and empty | every local receipt |
| L1-declared | confidential VM declared by the cloud | a cloud-signed identity token names the VM; nothing in it measures the image | receipts from the GCP deployment |
Getting listed
There is no registry, by design. situs_core/trust_bundle.json ships with the verifier the way a root store ships with a browser. To be listed, send a signed venue.json (your venue id, the level you can back, the kids that sign decisions and their key fingerprints) and it lands by pull request. situs-verify then labels a receipt's venue and decision kid "known venue" or "unknown key": a label, not a check, never changing a verdict or the exit code. The local development entry pins no key, because every checkout makes its own.
{"venue": "ven_example", "level": "L1-declared", "label": "Example Corp, one confidential VM",
"kids": {"pep": "sha256:<fingerprint>", "authority": "sha256:<fingerprint>"},
"signature": {"kid": "<your operator key>", "alg": "ed25519", "sig": "<hex>"}}Enterprises and startups
Quickstart: a mandate, a chat completion, a receipt, a verification
Pick a plugin, sign its example mandate with the principal's key, send one chat completion through the gateway, make the call an agent fooled by the plugin's injected scenario would make, and verify the receipt of its refusal offline. Each block below is the same script for one loaded plugin; the test suite runs one of them, literally, against the running stack.
# From the repository root, with the stack up (make up). Files land in out/quickstart/.
make keys # the principal's key: keys/principal_jdoe.pem, written once, never overwritten, never printed
mkdir -p out/quickstart
uv run situs-keys sign-mandate plugins/treasury/fixtures/intent_contract.example.json \
--authority https://situs-vm.o11r.com/api/authority --register --out out/quickstart/mandate.json
CONTRACT_ID=$(jq -r .contract_id out/quickstart/mandate.json)
TOKEN=$(uv run situs-keys agent-token --sub "$(jq -r .principal out/quickstart/mandate.json)") # a secret: never echo it
AUTH=(-H "Authorization: Bearer $TOKEN" -H "X-Situs-Contract: $CONTRACT_ID" -H "content-type: application/json")
TOOLS=$(curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/tools | jq -c '[.tools[].spec]')
curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/chat/completions -d '{"model": "situs-agent", "messages": [{"role": "user", "content": "Handle the next request."}], "tools": '"$TOOLS"'}' | jq -c '.choices[0].message'
# The call an agent fooled by the plugin's injected scenario makes. The rules refuse it, with a receipt.
curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/tools/propose_transfer -d '{"to_account":"9988776655","amount":"42250.00","currency":"USD","beneficiary_id":"ven_fabrikam","memo":"FS-7781 (remittance change per invoice)"}' \
| tee out/quickstart/result.json | jq -c '{status, reason, grant_id}'
curl -sf "https://situs-vm.o11r.com/api/authority/receipts?grant_id=$(jq -r .grant_id out/quickstart/result.json)" \
| jq '.[0]' > out/quickstart/receipt.json
uv run situs-verify out/quickstart/receipt.json --as quickstart# From the repository root, with the stack up (make up). Files land in out/quickstart/.
make keys # the principal's key: keys/principal_jdoe.pem, written once, never overwritten, never printed
mkdir -p out/quickstart
uv run situs-keys sign-mandate plugins/records/fixtures/intent_contract.example.json \
--authority https://situs-vm.o11r.com/api/authority --register --out out/quickstart/mandate.json
CONTRACT_ID=$(jq -r .contract_id out/quickstart/mandate.json)
TOKEN=$(uv run situs-keys agent-token --sub "$(jq -r .principal out/quickstart/mandate.json)") # a secret: never echo it
AUTH=(-H "Authorization: Bearer $TOKEN" -H "X-Situs-Contract: $CONTRACT_ID" -H "content-type: application/json")
TOOLS=$(curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/tools | jq -c '[.tools[].spec]')
curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/chat/completions -d '{"model": "situs-agent", "messages": [{"role": "user", "content": "Handle the next request."}], "tools": '"$TOOLS"'}' | jq -c '.choices[0].message'
# The call an agent fooled by the plugin's injected scenario makes. The rules refuse it, with a receipt.
curl -sf "${AUTH[@]}" https://situs-vm.o11r.com/api/gateway/v1/tools/release_record -d '{"record_id":"rec_acme_0003","recipient_id":"kyc-archive@harborpike-secure.example","purpose":"audit","sensitivity":"restricted","reference":"AUD-2026-119"}' \
| tee out/quickstart/result.json | jq -c '{status, reason, grant_id}'
curl -sf "https://situs-vm.o11r.com/api/authority/receipts?grant_id=$(jq -r .grant_id out/quickstart/result.json)" \
| jq '.[0]' > out/quickstart/receipt.json
uv run situs-verify out/quickstart/receipt.json --as quickstartPoint any OpenAI-compatible client at the gateway
Set base_url to https://situs-vm.o11r.com/api/gateway/v1. Authenticate with an HS256 JWT whose sub is the principal (situs-keys agent-token --sub <principal>; the secret is the gateway's SITUS_JWT_SECRET) and name the mandate in the X-Situs-Contract header. GET /v1/tools returns the tools of the plugin behind that mandate.
from openai import OpenAI # any OpenAI-compatible client; this repository does not ship one
client = OpenAI(base_url="https://situs-vm.o11r.com/api/gateway/v1",
api_key=TOKEN, # the agent's JWT: sub is the principal
default_headers={"X-Situs-Contract": CONTRACT_ID})
answer = client.chat.completions.create(model="situs-agent", tools=TOOLS,
messages=[{"role": "user", "content": "Handle the next request."}])
# Each tool call the model returns goes to https://situs-vm.o11r.com/api/gateway/v1/tools/<name> with the same two headers.Write and sign a mandate
A mandate is an Intent Contract: which façades, within what limits, when a person must approve, for how long. situs-keys sign-mandate <example.json> --register fills a fresh id and validity window, cites the profile version in force (GET https://situs-vm.o11r.com/api/authority/profiles/<major>), signs with keys/principal_<name>.pem and registers it. The authority refuses a mandate that exceeds the profile. A filled example per plugin:
{
"contract_id": "ic_acme_treasury_ops_0001",
"tenant": "tn_acme",
"principal": "jdoe",
"agent": "agent:treasury-ops",
"profile": "treasury.v1",
"scope": [
"bank.readBalance",
"market.fetch",
"bank.executeTransfer"
],
"limits": {
"runtime_max_s": 3600,
"use_count_max": 10
},
"approval": {},
"forbidden": [
"bank.addBeneficiary",
"bank.changeAccountDetails"
],
"valid_from": "2026-09-22T13:00:00Z",
"valid_to": "2026-09-22T14:00:00Z",
"policy_version": "treasury.v1@0.2",
"rules": {
"amount_max": "250000.00",
"daily_max": "600000.00",
"threshold": "50000.00"
}
}{
"contract_id": "ic_acme_records_desk_0001",
"tenant": "tn_acme",
"principal": "jdoe",
"agent": "agent:records-support",
"profile": "records.v1",
"scope": [
"records.lookup",
"requester.verify",
"records.release"
],
"limits": {
"runtime_max_s": 3600,
"use_count_max": 20
},
"approval": {
"routes": [
{
"approver_type": "Records officer",
"approvers": [
"jdoe"
],
"wait_s": 600,
"notify": [
"app"
],
"assurance": "hardware"
}
]
},
"forbidden": [
"records.delete",
"records.export"
],
"valid_from": "2026-09-24T13:00:00Z",
"valid_to": "2026-09-24T14:00:00Z",
"policy_version": "records.v1@0.1",
"rules": {
"max_sensitivity": "restricted",
"purposes": [
"audit",
"account_opening"
]
}
}Choose a plugin and publish a profile
GET https://situs-vm.o11r.com/api/authority/plugins lists the plugins this stack loaded. A profile is your signed instance of a plugin's rules; edit it on the console's Policy tab and publish a new version, signed by the principal. Mandates signed under an older version are then refused.
treasury 0.3.0 (treasury.v1, listed plugin): covered façade bank.executeTransfer, counterparty bank; first plugin, the demo.
records 0.1.0 (records.v1, listed plugin): covered façade records.release, counterparty recipient; generality test.
What comes back, and where receipts land
A tool call answers executed (with the counterparty's signed acknowledgment), fetched, pending (a person must approve; present the grant_id again after approval), denied (with a reason code) or duplicate. Every decision that names a grant produces a Proof Receipt: GET /receipts?grant_id= or ?tenant= on the authority, and a file out/<receipt_id>.json. Receipts are chained per tenant and entered in the transparency log. The profile's receipt_retention_days says how long the deployment keeps them; keep your own copies.
Verify offline
The receipt carries every public key it needs. Verification runs on your machine, without the network and without trusting Situs:
uv run situs-verify receipt.json --as your-auditor # the checks, then the labels
uv run situs-verify receipt.json --report proof.html # plus the full visibility proof
uv run situs-verify receipt.json --keys keys.json --prev prev.json # your own key bundle, and the chainShip receipts into your own systems
Poll GET /receipts?tenant=<tenant> and store each receipt as it is: it is the evidence, byte for byte. Check the log with GET /log/head (a signed tree head) against the inclusion proof in each receipt. An approver app can also receive signed webhooks (see approvals). Verify on ingest with the same situs-verify, and alert on anything that does not verify.
Standard adopters
JSON Schemas
Generated from the models the services use, served by the console and valid JSON Schema (draft 2020-12). The test suite validates both plugins' fixtures, both example mandates and a real receipt from each plugin against them.
| schema | what it describes |
|---|---|
| /docs/schema/receipt-0.3.json | the Proof Receipt, version 0.3 |
| /docs/schema/mandate.json | an Intent Contract (a mandate), signed by the principal |
| /docs/schema/profile.json | a Trust Profile: a tenant's signed instance of a plugin's rules |
| /docs/schema/action-grant.json | a single-use Action Grant, signed by the authority |
| /docs/schema/plugin-manifest.json | a plugin's manifest.json |
Canonicalization and signing
Canonical JSON is RFC 8785 (JCS). A hash is sha256 over the canonical bytes, written as lowercase hex. A signature is over the ASCII bytes of that 64-character hex digest, never over the raw digest and never over the JSON. ed25519 for services and key files; es256, WebAuthn and App Attest for people's hardware keys.
The five signature slots
Each fact in a receipt is signed by the party responsible for it. Situs signs only the assembly and cannot vouch for anyone else's fact.
| slot | kid | over | what it proves | whose key |
|---|---|---|---|---|
| principal | principal_<name> | contract_hash | who authorised the agent, within what limits | independent: the principal's own key |
| approver | approver_<name>[.<device>] | action_hash | a person approved this exact action, at the assurance the route demanded | independent: the approver's own key |
| pep | pep (or authority for a policy refusal) | the enforcement decision | what the enforcement point decided and did | Situs-signed: a Situs service |
| counterparty | the plugin connector's kid | counterparty_ack_hash | what the counterparty did, bound to the action hash and the idempotency key | independent: the counterparty's own key |
| situs | situs_assembly | receipt_body_hash | that Situs assembled these facts, and nothing more | Situs-signed: the assembly only |
The proof report grades every answer on one strength scale:
| strength | meaning |
|---|---|
| 4 | independent, hardware key |
| 3 | independent signature |
| 2 | Situs-signed |
| 1 | declared |
| 0 | not proven |
The transparency log
Each receipt is a leaf: sha256(0x00 || the ASCII hex of sha256(JCS(the receipt with status.log, bundle.log_root_signature and signatures.situs nulled))). Nodes and roots follow RFC 6962; the receipt carries its RFC 6962 inclusion proof and the authority-signed tree head. With SITUS_REKOR=1 and rekor-cli present, tree heads are also posted to Rekor (optional; the receipt then names the entry).
The verifier
cli/situs_verify.py is the verifier; the console's /verify page runs the same function. Its 9 checks, in order (a check that does not apply passes as n/a):
| # | check |
|---|---|
| 1 | schema and required fields |
| 2 | Situs assembly signature over receipt body |
| 3 | principal signature over contract_hash |
| 4 | approver signature over action_hash |
| 5 | enforcement decision signature |
| 6 | counterparty signature over acknowledgment |
| 7 | receipt chain |
| 8 | transparency log inclusion |
| 9 | status as of revocation check |
Labels come after the checks and never change them: the plugin's tier, the venue ("known venue" or "unknown key"), a declared cloud identity, and --as, who ran it.
Compatibility promise
- A proof never changes meaning: what a signature slot proves today it proves in every later version.
- Plugins add metadata and wording; they never change a check, a slot or a hash rule.
- Schema versions are additive within a major: new optional fields only. A new major is a new schema name, and the verifier reports an unsupported one cleanly.
Operating Rules, and the license
The Operating Rules are a draft: optional, not in force, and not published in this repository yet. Nothing in this build depends on them.
License: PLACEHOLDER. The founder has not chosen a license for this repository; nothing here grants one. (Each plugin's manifest names its own.)
Developer guide: the reference
How to build against Situs Pilot 1 on this Mac: point an agent at the gateway, sign mandates and approvals
with people's keys, and read and verify what happened. Every service also serves its own interactive API reference at
/docs and the schema at /openapi.json; the addresses below open them.
- Services and who may call them
- Build an agent
- Sign a mandate in Python
- Approve a payment
- Read and verify receipts
- The profile
- Errors and reason codes
- Endpoint reference
Services and who may call them
An agent talks to the gateway and nothing else. Anything that creates authority (a mandate, an approval, a revocation, a profile version) carries a person's signature, which the authority checks against the keys it publishes. There is no API key that lets a program skip that.
| service | address · API reference | what it holds | called by |
|---|---|---|---|
| gateway | localhost:8000 | The agent's only door. Tools and chat, each call recorded as evidence. | the agent |
| authority | localhost:8001 | Mandates, grants, approvals, revocations, receipts, the evidence chain and the profile. | people's tools (with signatures) and the services |
| pep | localhost:8002 | The enforcement point. Spends a grant once and holds the only counterparty secret. | the gateway |
| bank | localhost:8003 | A mock bank: balances, transfers, FX. Knows no policy. | the pep only |
| console | localhost:8080 | This console. Pages, not an API. | people |
Build an agent
Authenticate with an HS256 JWT whose sub is the principal (the dev secret is
SITUS_JWT_SECRET), and name the mandate you act under in X-Situs-Contract. Then call tools. The
gateway forwards them: a proposal goes to the authority, which answers denied (with a reason),
pending (a person must approve) or allows it, and only then does the enforcement point execute it. A
pending result carries a grant_id; present it again after approval.
import time, httpx
from situs_core import jwt_hs256
GATEWAY = "http://localhost:8000"
CONTRACT_ID = "ic_..." # a mandate the treasurer signed (see "Sign a mandate")
token = jwt_hs256.encode({"sub": "jdoe", "iat": int(time.time())},
"dev-jwt-secret-not-for-production") # SITUS_JWT_SECRET
headers = {"Authorization": f"Bearer {token}", "X-Situs-Contract": CONTRACT_ID}
out = httpx.post(f"{GATEWAY}/v1/tools/propose_transfer", headers=headers, json={
"to_account": "4417002211", "amount": "75000.00", "currency": "USD",
"beneficiary_id": "ven_northwind",
"memo": f"NW-{int(time.time())}"}).json() # the invoice number: the same one twice is duplicate_action
print(out["status"], out.get("reason")) # pending approval_required
# after a person approves, present the grant; the pep executes it once
out = httpx.post(f"{GATEWAY}/v1/tools/propose_transfer", headers=headers,
json={"grant_id": out["grant_id"]}).json()
print(out["status"], out["counterparty_ack"]["transfer_id"]) # executed tr_...
Or run a model loop through the OpenAI-compatible endpoint. Any OpenAI client works with base URL
http://localhost:8000/v1 and the two headers. Each response carries the hashes the gateway recorded.
curl -s localhost:8000/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" -H "X-Situs-Contract: $CONTRACT_ID" \
-H 'content-type: application/json' -d '{
"model": "situs-agent",
"messages": [{"role": "user", "content": "Pay the Northwind invoice NW-2026-0912."}],
"tools": '"$(curl -s localhost:8000/v1/tools -H "Authorization: Bearer $TOKEN" -H "X-Situs-Contract: $CONTRACT_ID" | jq .tools)"'
}' -D - | grep -i x-situs # X-Situs-Request-Hash, X-Situs-Response-Hash, X-Situs-Event-Id
Tool results by status: executed (with the bank's signed ack), fetched,
pending, denied, refused, and duplicate when a spent grant is presented
again. The agent never sees a bank credential and cannot mint a grant, whatever the model writes.
Several agents, one set of invoices
Run as many agents as you like against the same mailboxes: the authority treats a payment, not a proposal, as the unit.
A second proposal for the same payee, account, amount, currency and invoice reference (the memo, case and
spacing ignored) is refused as duplicate_action while an earlier grant for it is pending, active or
redeemed, under any mandate in the tenant. A transfer's idempotency key is derived from the same fields
(idem_pay_…), so the bank also moves an invoice's money once. Put the invoice number in memo;
paying the same invoice again on purpose needs a new reference.
Sign a mandate in Python
A mandate is an Intent Contract the treasurer signs with their key. It must cite the profile version in force and stay
within the profile's limits, or the authority refuses it (policy_version_mismatch,
amount_max_over_profile…). situs-keys sign-mandate does the same from a shell; the console's Demo
and Policy tabs do it for you.
from datetime import datetime, timedelta, timezone
from pathlib import Path
import httpx
from situs_core import canon, contracts, keys
from situs_core.models import IntentContract
AUTHORITY = "http://localhost:8001"
profile = httpx.get(f"{AUTHORITY}/profiles/treasury.v1").json()
base = contracts.load_unsigned_contract(Path("plugins/treasury/fixtures/intent_contract.example.json")).to_dict()
now = datetime.now(timezone.utc)
unsigned = IntentContract.model_validate({**base,
"contract_id": f"ic_dev_{int(now.timestamp())}",
"policy_version": f"{profile['profile']}@{profile['version']}", # the version in force
"valid_from": canon.rfc3339(now), "valid_to": canon.rfc3339(now + timedelta(hours=1))})
principal = keys.load_private_key(Path("keys/principal_jdoe.pem"))
signed = contracts.sign_contract(unsigned, principal)
httpx.post(f"{AUTHORITY}/contracts", json=signed.to_dict()).raise_for_status()
print(signed.contract_id)
Approve a payment
Recompute the action hash from the canonical action, refuse if it disagrees with the grant, then sign the hash. The
same two steps run in situs-approve, the console's approve button, the email link and the Mac dialog
(make approver). An approver app does it through the approval API.
from pathlib import Path
import httpx
from cli.situs_approve import check_action_hash, sign_approval
from situs_core import keys
AUTHORITY = "http://localhost:8001"
record = httpx.get(f"{AUTHORITY}/grants", params={"status": "pending"}).json()[0]
action_hash = check_action_hash(record) # recompute it; refuse if the grant disagrees
approver = keys.load_private_key(Path("keys/approver_jdoe.pem"))
body = sign_approval(action_hash, "jdoe", approver)
gid = record["grant"]["grant_id"]
httpx.post(f"{AUTHORITY}/grants/{gid}/approve", json=body.to_dict()).raise_for_status()
Read and verify receipts
Every decision produces a Proof Receipt. It verifies offline with nothing but the receipt: each party's signature over the fact it is responsible for, plus the assembly signature.
curl -s "localhost:8001/receipts?grant_id=$GRANT_ID" | jq -r '.[0].receipt_id'
uv run situs-verify out/rcpt_....json # nine checks, offline
uv run situs-verify out/rcpt_....json --report proof.html # plus the full visibility proof
uv run situs-verify out/rcpt_....json --as-of 2026-09-23T15:00:00Z # status as of an instant
The profile
GET /profiles/treasury.v1 returns the rules in force and /publication says who published
them. A new version is published from the Policy tab, signed by the treasurer; the authority and the pep verify it on
their next request, and answer 503 with profile_hash_mismatch,
profile_signature_invalid or signer_not_principal if the file cannot be trusted. Mandates signed
under an older version are then refused with contract_expired.
Errors and reason codes
Every error body is {"detail": {"error": "<code>", "message": "…"}}. Policy decisions use a closed
set of reason codes: contract_revoked (the mandate was revoked), contract_expired (the mandate expired, or was signed under an older profile version), facade_not_in_scope (the mandate does not allow this action), use_count_exceeded (the mandate's uses are spent), duplicate_action (duplicate payment), approval_required (a person must approve), recipient_not_approved, purpose_not_allowed, release_limit_exceeded, beneficiary_not_approved (payee not on the approved list), amount_over_limit (amount over the per-transfer limit), daily_limit_exceeded (daily limit exceeded), ok (allowed). The detail string in a decision says which limit or rule applied.
Endpoint reference
gateway :8000
| endpoint | who calls it | what it does |
|---|---|---|
| GET /v1/tools | anyone | A plugin's tools in OpenAI function format: ?plugin=<name>, or the caller's own mandate's plugin when the JWT and contract header are sent (Task 34). |
| POST /v1/tools/{name} | agent (JWT + contract) | Run read_balance, fetch_fx or propose_transfer. |
| POST /v1/chat/completions | agent (JWT + contract) | OpenAI-compatible chat; answered by Claude or the scripted stub. |
| GET /v1/agent | anyone | Which model answers chat: scripted or Claude. |
| GET /healthz | anyone | Liveness. |
authority :8001
| endpoint | who calls it | what it does |
|---|---|---|
| GET /plugins | anyone | Every loaded plugin: name, version, tier, façades, covered façade, counterparty kind (Task 35). |
| GET /profiles/{name} | anyone | The Trust Profile in force for a plugin's major (<plugin>.v1), with its version. |
| GET /profiles/{name}/publication | anyone | Who published that version, when, why, and their signature. |
| POST /contracts | principal's signature | Register a signed Intent Contract (a mandate). |
| GET /contracts | anyone | Every mandate and its status. |
| GET /contracts/{contract_id} | anyone | One mandate, with its revocation if any. |
| POST /contracts/{contract_id}/revoke | principal's signature | Revoke a mandate. Later proposals are refused. |
| POST /grants | gateway | Propose an action; the engine allows, denies, or waits for a person. |
| GET /grants?status= | anyone | Grants, optionally by status: pending, active, redeemed, denied, expired. |
| GET /grants/{grant_id} | anyone | One grant with its canonical action and decision. |
| POST /grants/{grant_id}/approve | approver's signature | Approve the exact action hash. |
| POST /grants/{grant_id}/redeem | pep | Spend a grant (single use). |
| GET /receipts?grant_id=&tenant= | anyone | Proof Receipts. |
| GET /receipts/{receipt_id} | anyone | One receipt: the file situs-verify checks. |
| POST /events | services (signed) | Append a signed evidence event. |
| GET /events | anyone | The evidence chain. |
| GET /events/head | anyone | The chain's latest hash. |
| GET /log/head | anyone | The transparency log's signed tree head. |
| GET /.well-known/keys.json | anyone | Every party's public key, to verify anything above. |
pep :8002
| endpoint | who calls it | what it does |
|---|---|---|
| POST /execute | gateway | Present a grant and its canonical action; the pep checks and executes once. |
| POST /fetch | gateway | market.fetch: GET a URL under the profile's market-data addresses. |
| GET /.well-known/keys.json | anyone | The pep's public key. |
bank :8003
| endpoint | who calls it | what it does |
|---|---|---|
| GET /accounts/{account_id}/balance | pep (broker token) | Balance. |
| POST /transfers | pep (broker token) | Move money once per idempotency key; returns a signed ack. |
| GET /market/fx | anyone | A signed FX quote. |
| GET /.well-known/keys.json | anyone | The bank's public key. |