Powered by KATA · Know And Trust Agents

API reference

Shapes below are the fields this service returns. FastAPI generates the machine-readable description at /docs/openapi and /openapi.json.

GET /v1/revocations

Pull list of stored principal revocations. The response sets Cache-Control: public, max-age=300.

Response

{
  "revocations": []
}

503 {"error": "DATABASE_URL is not set"} when storage is not configured.

POST /v1/revocations/push

Notifier-signed webhook. The body is the object from sign_revocation_push: at, kata_revocation_push "1", revocation, and signature. signature is a flattened JWS whose protected header typ is kata-revocation-push+jws.

Request

{
  "at": "2026-10-04T18:00:00Z",
  "kata_revocation_push": "1",
  "revocation": {
    "agreement_id": "agr_demo",
    "at": "2026-10-04T18:00:00Z",
    "kata_revocation": "1",
    "mandate_hash": "sha256:…",
    "reason": "principal revoked",
    "signature": {"payload": "…", "protected": "…", "signature": "…"}
  },
  "signature": {"payload": "…", "protected": "…", "signature": "…"}
}

Response

{
  "stored": true,
  "revocation_id": "sha256:…",
  "sequence": 1
}

A repeat of the same revocation returns {"stored": false, "revocation_id": "sha256:…"} and does not append again. 401 {"error": "revocation push did not verify"}. 503 when KATA_PUSH_NOTIFIER_JWK or KATA_LOG_SIGNING_SEED is unset. 400 when the body is not a JSON object.

POST /v1/log/entries

Append an agreement, revocation, registration, or incident and sign a new tree head. Authorization: Bearer . Bureau registrations, revocations, and incidents are appended by the service itself.

Request

{
  "kind": "agreement",
  "body": {}
}

Response

{
  "entry": {
    "kind": "agreement",
    "sequence": 1,
    "body": {},
    "entry_hash": "sha256:…"
  },
  "tree_head": {
    "algorithm": "EdDSA",
    "at": "2026-10-04T14:02:11Z",
    "kata_tree_head": "1",
    "root": "sha256:…",
    "tree_size": 1,
    "signature": {"payload": "…", "protected": "…", "signature": "…"}
  }
}

kind is agreement, revocation, registration, or incident. A later entry also carries prev_entry_hash. The tree-head JWS typ is kata-tree-head+jws. 401 {"error": "append token does not match"}. 400 {"error": "log append needs kind and a body object"} and 400 when kind is rejected. 503 when KATA_APPEND_TOKEN or KATA_LOG_SIGNING_SEED is unset.

GET /v1/log/entries/{sequence}

One hash-chained entry. sequence starts at 1.

Response

{
  "entry": {
    "kind": "revocation",
    "sequence": 1,
    "body": {},
    "entry_hash": "sha256:…"
  }
}

404 {"error": "log entry was not found"} when the sequence is outside the log.

GET /v1/log/entries/{sequence}/proof

Merkle inclusion proof for that entry. index is sequence minus 1. side is left or right.

Response

{
  "sequence": 1,
  "index": 0,
  "leaf": "sha256:…",
  "proof": [{"hash": "sha256:…", "side": "right"}],
  "root": "sha256:…",
  "tree_size": 1
}

404 when the sequence is outside the tree.

GET /v1/log/head

Signed tree head. The same fields are on tree_head from a log append.

Response

{
  "algorithm": "EdDSA",
  "at": "2026-10-04T14:02:11Z",
  "kata_tree_head": "1",
  "root": "sha256:…",
  "tree_size": 1,
  "signature": {"payload": "…", "protected": "…", "signature": "…"}
}

404 {"error": "the transparency log has no signed tree head"} when nothing has been appended.

GET /.well-known/kata-keys.json

Log public key, plus any records in KATA_PUBLISHED_KEYS. An empty key list is a 200 when no seed is set.

Response

{
  "keys": [
    {"public_key": {"crv": "Ed25519", "kid": "sha256:…", "kty": "OKP", "x": "…"}}
  ]
}

500 when a configured key or seed fails to load.

GET /v1/fx?currency=CAD&reference=USD&day=YYYY-MM-DD

Reference-currency units for one unit of currency on that UTC day. reference_currency is an alias of reference. The default reference is USD. day defaults to the current UTC date. The rate is a decimal string from the configured source (default https://api.frankfurter.dev/v1). The same currency returns rate "1".

Response

{
  "currency": "CAD",
  "reference_currency": "USD",
  "day": "2026-10-04",
  "rate": "…"
}

400 when a currency is not three letters or day is not YYYY-MM-DD. 404 {"error": "no USD rate for CAD on YYYY-MM-DD"} when the source has no rate. 502 {"error": "FX source did not answer"}. 503 when DATABASE_URL is not set.

POST /demo/check

Signs one sample agreement with the public demo keys and evaluates one payment. principal is K. Uluer and the agent is shopper-01. demo is true. verdict is ALLOW, DENY, or STEP_UP. kata_decision is ALLOW, BLOCK, or STEP_UP. DENY maps to BLOCK.

Request

{
  "intent": {
    "maximum_amount": 1500,
    "currency": "CAD",
    "purpose": "purchase",
    "category": "electronics",
    "natural_language": "Find and buy a laptop for me, maximum CAD 1,500.",
    "include_expiry": true,
    "time_limit": "2026-12-31"
  },
  "payment": {
    "amount": 1300,
    "currency": "CAD",
    "merchant": "techstore.example",
    "item": "Laptop"
  }
}

Response

{
  "verdict": "ALLOW",
  "kata_decision": "ALLOW",
  "reasons": [],
  "messages": [],
  "required_action": "NONE",
  "confirmation_text": "…",
  "mandate_hash": "sha256:…",
  "principal": "K. Uluer",
  "principal_fingerprint": "sha256:…",
  "agent_id": "shopper-01",
  "agreement_id": "agr_demo",
  "status": "active",
  "demo": true
}

A payment of 4200 CAD against that ceiling returns verdict DENY, kata_decision BLOCK, and reasons including AMOUNT_ABOVE_INTENT_CEILING. include_expiry false returns verdict STEP_UP and reasons including OVER_BROAD_INTENT. 400 when an amount or currency is unusable.

POST /v1/orgs

Admin bootstrap. Creates a participant organization. org_type is agent_operator, wallet, bank_issuer, processor_psp, or merchant.

Request

{
  "name": "Harbor Agents",
  "org_type": "agent_operator"
}

Response

{
  "org_id": "org_…",
  "name": "Harbor Agents",
  "org_type": "agent_operator",
  "status": "active"
}

401 without the admin token. 403 for any other credential. 400 for an unknown org_type.

POST /v1/orgs/{org_id}/keys

Issues an API key. The raw token is returned once. Stored value is the SHA-256 hash. List responses omit the token.

Request

{
  "name": "primary"
}

Response

{
  "key_id": "key_…",
  "prefix": "…",
  "scopes": ["registry:read"],
  "token": "gab_…"
}

404 when another organization asks. 401 when the key is revoked.

POST /v1/agents

An agent operator registers an agent. public_key is an Ed25519 or P-256 JWK with no private d. principal_id is a pseudonymous id.

Request

{
  "agent_id": "shopper-01",
  "principal_id": "prn_demo",
  "verification_level": "operator_verified",
  "public_key": {"kty": "OKP", "crv": "Ed25519", "x": "…"}
}

Response

{
  "agent_id": "shopper-01",
  "status": "active",
  "verification_level": "operator_verified",
  "fingerprint": "sha256:…",
  "score": 700
}

Other tenants do not receive principal_id. 409 when the agent id or key is already registered.

POST /v1/verify

Checks a payment action against the agent's active stored agreement. ALLOW records the spend. DENY maps to kata_decision BLOCK.

Request

{
  "agent_id": "shopper-01",
  "action": {"action_id": "act_1", "action_type": "purchase", "amount": 1300, "currency": "CAD", "proposed_at": "2026-10-04T13:55:00Z"}
}

Response

{
  "verdict": "ALLOW",
  "kata_decision": "ALLOW",
  "reasons": [],
  "agent_id": "shopper-01",
  "agreement_id": "agr_demo"
}

AGENT_NOT_ACTIVE, NO_ACTIVE_AGREEMENT, and AGREEMENT_NOT_SELECTED are bureau reasons. Library reasons such as AMOUNT_ABOVE_INTENT_CEILING are passed through.

POST /v1/inquiries

Purpose-coded inquiry. Returns identity, verification, active authorizations, revocations, incident counts, and the trust score. Contributors also receive recent evidence hashes.

Request

{
  "agent_id": "shopper-01",
  "purpose_code": "payment_authorization"
}

Response

{
  "inquiry_id": "inq_…",
  "agent": {"agent_id": "shopper-01", "status": "active", "verification_level": "institution_attested"},
  "trust_score": {"score": 724, "cold_start": false, "reasons": [{"code": "CHARGEBACK", "points": -56}]}
}

400 when purpose_code is missing. The body does not include an email address or principal_id.

POST /v1/reports

Files a transaction outcome, fraud incident, chargeback, or policy violation. evidence_hash is sha256 and 64 hex characters. The same reporter, agent, kind, and hash is a duplicate.

Request

{
  "agent_id": "shopper-01",
  "kind": "chargeback",
  "evidence_hash": "sha256:…",
  "summary": "Disputed purchase."
}

Response

{
  "report_id": "rpt_…",
  "kind": "chargeback"
}

409 duplicate report. 429 when the organization exceeds the daily cap.

POST /v1/subscriptions

Subscribes the caller to signed webhooks for revocation, a new incident, and a score drop of 50 points or more. The signature typ is kata-bureau-alert+jws and uses the log signing key.

Request

{
  "agent_id": "shopper-01",
  "webhook_url": "https://merchant.example/hooks/gab"
}

Response

{
  "subscription_id": "sub_…",
  "agent_id": "shopper-01"
}

400 unless the URL is https, or http on localhost.

POST /v1/portal/magic-link

Sends a portal sign-in link. The response is always sent true. In development, GAB_DEV_MAILBOX=1 keeps the latest link at GET /v1/dev/mailbox.

Request

{
  "email": "principal@example.com"
}

Response

{
  "sent": true
}

POST /v1/sessions exchanges the token. A new email creates a new principal id.

POST /v1/admin/seed-demo

Inserts the public demo organizations, shopper-01, and agr_demo into the live repository. Authorization is the GAB_ADMIN_TOKEN bearer. The route stays closed unless GAB_ALLOW_DEMO_SEED=1. It does not require GAB_DEMO. A second call returns already_present true and does not insert another copy. The response omits the admin token.

Response

{
  "operator_key": "gab_demo_operator_public",
  "wallet_key": "gab_demo_wallet_public",
  "merchant_key": "gab_demo_merchant_public",
  "agent_id": "shopper-01",
  "agreement_id": "agr_demo",
  "already_present": "false"
}

401 without a bearer. 403 for a non-admin key. 403 {"error": "demo seed is disabled"} when the flag is unset.