API reference
KYE Protocol™ App API
132 operations · app.yaml
Clerk-gated customer app surface for app.kyeprotocol.com. All endpoints require a Clerk JWT. Responses are automatically scoped to the caller's tenant derived from the JWT org_id. All entity endpoints are read-only for tenant principals.
Tenants
GET/tenantsGet caller's own tenant
Auth: Session (Clerk JWT)
Responses
200OK404Tenant not yet provisioned
GET/tenants/{id}Get tenant by id (only own tenant allowed)
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt404Resource not found
GET/reportsList signed report envelopes for the caller's tenant (KYE™ Reporting Engine™ tenant view)
Auth: Session (Clerk JWT)
Responses
200OK401Missing or invalid bearer token
GET/settingsLoad merged tenant settings
Auth: Session (Clerk JWT)
Responses
200OK401Missing or invalid bearer token
PATCH/settingsUpdate one or more tenant settings fields
Auth: Session (Clerk JWT)
Responses
200OK400Invalid field401Missing or invalid bearer token
LegalEntities
GET/legal-entities
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer | |
offset | query | integer |
Responses
200OK
GET/legal-entities/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt404Resource not found
BillingAccounts
GET/billing-accounts
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/billing-accounts/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Domains
GET/domains
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/domains/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Policies
GET/policies
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/policies/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Workspaces
GET/workspaces
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/workspaces/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Projects
GET/projects
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/projects/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Teams
GET/teams
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/teams/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Principals
GET/principals
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/principals/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Resources
GET/resources
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/resources/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Models
GET/models
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/models/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
Tools
GET/tools
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/tools/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
ExternalApps
GET/external-apps
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/external-apps/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
AuditStreams
GET/audit-streams
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer |
Responses
200OK
GET/audit-streams/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt
StateRegistry
GET/state-eventsList state events for an entity (tenant-scoped)
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
entity_id required | query | string | |
limit | query | integer |
Responses
200OK400entity_id required403Cross-tenant access attempt
GET/state-events/{id}
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200OK403Cross-tenant access attempt404Resource not found
StateLibrary
GET/state-libraryBrowse published KYE™ State Library entries
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
category | query | string | |
include | query | string |
Responses
200OK
GET/state-machines/from-libraryList state machine derivations for caller's tenant
Auth: Session (Clerk JWT)
Responses
200OK
POST/state-machines/from-libraryAdopt a State Library entry into caller's tenant
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
library_id required | string | |
library_version required | string | |
tenant_entity_class required | string | |
overrides | object |
Responses
200OK400Validation error404Library entry not found409Seal mismatch
Analytics
GET/analytics-decisions-per-hourHourly decision rollup for the caller's tenant
Aggregates the tenant's decisions ledger into hourly buckets with per-verdict counts (allow / deny / review / quarantine). The window is capped at 90 days; longer windows are served by the warehouse path on the gateway-worker. Powers the usage sparkline and dashboard time-series drilldowns.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
since | query | string | Window start (defaults to until minus 7 days) |
until | query | string | Window end (defaults to now) |
Responses
200Hourly rows with decision_count, allow_count, deny_count, review_count, quarantine_count400Invalid since/until, since not before until, or window over 90 days503Required D1 / service binding not attached (db_binding_missing)
GET/analytics-widget-callsPer-widget call counts for the caller's tenant
Groups the tenant's widget_calls telemetry by widget_slug over the requested window (default trailing 7 days), reporting total and successful call counts per widget.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
since | query | string | Window start (defaults to until minus 7 days) |
until | query | string | Window end (defaults to now) |
Responses
200Per-widget summary rows with widget_slug, total_calls, ok_calls400Invalid since or until timestamp503Required D1 / service binding not attached (db_binding_missing)
Dashboard
GET/dashboard-statsSeven top-level dashboard KPIs for the caller's tenant
Honest D1 aggregates scoped to the caller's tenant — decisions today, pending reconfirmations (Purpose Permissions expiring within 30 days), open drift events, evidence packs awaiting sign-off, 24h decision breakdown, authority finality (in-force / attenuated / revoked delegations + p99 lookup), and the most active agent today. Cold tenants get flat zeros, never fabricated values.
Auth: Session (Clerk JWT)
Responses
200KPI snapshot for the tenant503Required D1 / service binding not attached (db_binding_missing)
GET/dashboard-snapshotCombined dashboard snapshot (SSE poll-fallback)
Poll fallback for clients whose EventSource connection to /stream/dashboard fails. Returns the last 50 decisions, the authority graph, the 24x7 density heatmap, the edge infra topology, and the §0.3 attestation envelope for the read. A client-supplied tenant_id that diverges from the session-resolved tenant is refused per §0.11.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
tenant_id | query | string | Optional echo of the caller's tenant; refused with 403 if it diverges from the session |
Responses
200Snapshot with decisions, density_heatmap, authority_graph, infra_topology, attestation401Missing or invalid bearer token403cross_tenant_smuggle_refused — client tenant_id diverges from session (refusal attestation included)503Required D1 / service binding not attached (db_binding_missing)
GET/stream/dashboardServer-Sent Events stream for the realtime dashboard
Long-lived text/event-stream emitting typed events — subscribe envelope on open, then decision, authority_change, metric_tick and 25-second ping heartbeats, with a closing kye.compliance.attestation.v1 envelope. Connections are capped at 5 minutes; EventSource auto-reconnect re-verifies the rotating Clerk JWT. Client-supplied tenant_id diverging from the session is refused before the stream opens (§0.11).
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
tenant_id | query | string | Optional echo of the caller's tenant; refused with 403 if it diverges from the session |
Responses
200SSE stream of kye.dashboard.event.v1 frames401Missing or invalid bearer token403cross_tenant_smuggle_refused — client tenant_id diverges from session503Required D1 / service binding not attached (db_binding_missing)
GET/density-heatmap24x7 decision-density grid for the caller's tenant
Buckets the tenant's decisions by UTC day-of-week and hour-of-day into a kye.density_heatmap.v1 snapshot with per-cell allow / deny / review counts. Default window is the trailing 7 days.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
days | query | integer | Trailing window in days |
tenant_id | query | string | Optional echo of the caller's tenant; refused with 403 if it diverges from the session |
Responses
200kye.density_heatmap.v1 snapshot with cells, max_cell_count, total_count, attestation401Missing or invalid bearer token403cross_tenant_smuggle_refused — client tenant_id diverges from session503Required D1 / service binding not attached (db_binding_missing)
GET/infra-topologyEdge-infrastructure topology snapshot
Returns the kye.infra_topology.v1 graph (Worker, D1, Queue, R2, KV nodes plus read/write/produce/consume edges) for the kye-infra-topology web component. Each node carries the §51 No-SPOF posture; D1 status reflects whether the KYE_DB binding is reachable. Per-tenant throughput values are honest zeros until the per-binding counter export ships.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
tenant_id | query | string | Optional echo of the caller's tenant; refused with 403 if it diverges from the session |
Responses
200kye.infra_topology.v1 snapshot with nodes, edges, attestation401Missing or invalid bearer token403cross_tenant_smuggle_refused — client tenant_id diverges from session
Authority
GET/authority-graphTenant-wide authority graph of in-force delegations
Derives a kye.authority_graph.v1 node/edge graph from the tenant's most recent 200 delegations — nodes classified by URN segment (agent / capability / scope / policy / delegate / principal), edges typed delegates_to with an in_force flag and audit_ref. Includes a §0.3 attestation envelope.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
tenant_id | query | string | Optional echo of the caller's tenant; refused with 403 if it diverges from the session |
Responses
200kye.authority_graph.v1 snapshot with nodes, edges, attestation401Missing or invalid bearer token403cross_tenant_smuggle_refused — client tenant_id diverges from session503Required D1 / service binding not attached (db_binding_missing)
GET/authority-walletList the tenant's wallet credentials with KPI roll-up
A wallet credential is a presentable, revocable, downstream-delegatable authority grant held by the tenant principal. Read-only — credentials enter the wallet when grants are accepted or Operating Model seals are published. KPI counts active credentials, active delegations issued, credentials presentable for at most 30 more days, and revocations in the last 30 days; includes distinct holders for the filter bar.
Auth: Session (Clerk JWT)
Responses
200Credential rows (credential_id, credential_class, issuer, holder, presentable_until, status, issued_at, revoked_at) plus kpi and holders503Required D1 / service binding not attached (db_binding_missing)
GET/authoritiesAuthorities held by the calling tenant, with issuer rollup
Auth: Session (Clerk JWT)
Responses
200Authorities, their issuers and a KPI rollup503Required D1 / service binding not attached (db_binding_missing)
Entities
GET/entitiesList the tenant's entity inventory with KPI roll-up
Every actor bound to the tenant — principals, agents, model endpoints, tools, datasets, connectors, partners — each identified by a stable kye URN. Read-only; entities are registered through the Operating Model authoring flow, not created here. KPI counts agents, people and tools by entity class.
Auth: Session (Clerk JWT)
Responses
200Entity list with id, entity_class, display_name, trust_domain, status, last_seen, plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/entitiesAdmit an entity into the governed graph
The admission half of the one governed register. Validates the request against kye.entity_registration.v1, checks whether the registrant may register into this trust domain, then runs the decision engine for the action entity.register. entity.register is a consequential action, so a request carrying no recorded approval is HELD (202) and nothing is written; the row and the decision that admitted it are created together or not at all. trust_domain_id is taken from the verified session and any value supplied in the body is ignored — a caller-asserted scope is not a scope.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
entity_id required | string | Canonical entity URN |
entity_type required | string | |
display_name | string,null | |
registered_by_entity_id required | string | The registering entity's URN |
basis required | string | One of self_registration, delegated_registration, bulk_import, agent_nomination_admitted, bootstrap_genesis |
delegation_id | string,null | Required for delegated_registration and bulk_import |
nomination_ref | string,null | Required for agent_nomination_admitted |
consequential_action_classes | array | |
labels | array | |
approval_recorded | boolean | Whether an approval for this admission is on record |
Responses
201Admitted — entity_id plus the decision that admitted it202Held for approval — reason_code and decision reference; nothing written400Contract violation — the offending field is named403Refused — registrant may not register into this trust domain503Required D1 / service binding not attached (db_binding_missing)
GET/hierarchyEntity-relation tree for the caller's tenant
Builds the full adjacency-list tree from the tenant's hierarchy nodes (delegation / containment / binding edges) with a KPI roll-up of node_count, depth, agent_count and principal_count. Read-only — the tree is derived from the operating model, not authored here.
Auth: Session (Clerk JWT)
Responses
200Nested tree of nodes with node_class, label, lifecycle, edge_kind, children, plus kpi503Required D1 / service binding not attached (db_binding_missing)
Search
GET/searchTenant-scoped search across entities and decisions
Prefers the private kye-search-engine worker over a service binding (signed kye.search_result.v1 envelope, lexical mode); falls back to tenant-scoped D1 LIKE matching across the entities and decisions tables when the binding is absent or the engine errors. An empty q returns an empty hit list.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
q | query | string | Search term |
index | query | string | Optional index filter (app_entities, decisions, library_entries, state_events, policies) |
Responses
200Flat hit list with id, kind, title, snippet (plus score/classification on the engine path); source field names the path taken503Required D1 / service binding not attached (db_binding_missing)
Events
GET/events/searchQuery the tenant's event index
Searches the tenant's indexed event store with full-text matching; when a free-text q is supplied. All filters combine with AND; rows are newest-first. Pure read with no mutation.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
q | query | string | Free-text FTS5 match |
family | query | string | Event family id |
action | query | string | Action kind |
phase | query | string | |
actor | query | string | Actor id |
risk | query | string | Risk level |
since | query | string | |
until | query | string | |
limit | query | integer |
Responses
200Echoed query, count, result rows (entry_id, event_family_id, emitted_at, phase, actor, verdict, audit_chain_ref, framework_refs, tags), served_at401Missing or invalid bearer token503Required D1 / service binding not attached (db_binding_missing)
Runtime
GET/live-runtimeRecent PDP decisions with a 5-minute KPI roll-up
Newest-first decision events for the caller's tenant with a KPI block covering the last 5 minutes (decisions/min rate plus allow / review / deny percentages) and distinct capability and actor lists for filter dropdowns. Updates the tenant's last-polled cursor. Read-only — decisions are emitted by the PDP, never created here.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
limit | query | integer | |
since | query | string | Only decisions decided at or after this instant |
capability | query | string | Filter by capability_id |
actor | query | string | Matches actor_entity_id or agent_entity_id |
decision | query | string | Filter by decision verdict |
Responses
200Decision rows, kpi (rate, allow_pct, review_pct, deny_pct), capabilities, actors400invalid_since — unparseable timestamp503Required D1 / service binding not attached (db_binding_missing)
POST/runtime/evaluateEvaluate an agent action and return an Authority Finality decision + receipt
Runs the deterministic runtime authority decision for one proposed agent action and returns the three-outcome verdict (allow | require_approval | deny), a replay-stable decision_id, a patent-safe evidence reference, a receipt verify_url, and an Ed25519-sealed, offline-verifiable Evidence Pack. Emits the full evidence-event family server-side. Auth is a minted API key (runtime:write or runtime:* scope) or a Clerk session.
Auth: API key (kye_live_ / kye_test_) / Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
subject | string | KYE™ URN of the acting agent/principal (e.g. kye:agent:acme:kyc-triage). Alias: agent. |
agent | string | Alias for subject (quickstart-friendly). |
action required | string | Dotted capability id (e.g. payments.transfer, kyc.screening.run). |
purpose required | string | Declared purpose class the action is bound to. |
context | object | Optional decision context (amount, jurisdiction, approval_recorded, irreversible, …). |
Responses
200Governed decision: decision (allow | require_approval | deny), reason_code (canonical vocabulary), decision_id (kye:decision:<hex>, replay-stable), evidence (patent-safe audit_reference), verify_url (receipt deep-link on evidence.html), replay_seed, latency_m…400invalid_json | missing_subject | subject_not_kye_urn | missing_action | action_malformed | missing_purpose401Missing or invalid bearer token403permission_scope_exceeded — key lacks runtime:write / runtime:*429quota_exceeded — the tenant's monthly agent_action quota is exhausted (governed refusal with a patent-safe evidence reference and quota {used, limit, remaining, resets_at}; configure via KYE_AGENT_ACTION_MONTHLY_QUOTA, 0 = unlimited).
Memory
GET/memoryList signed agent-memory records for the caller's tenant
Reads the agent_memory table, excluding forgotten records. Exposes the observable contract only — id, agent_entity_id, memory_class, purpose, created_at, signed_by_kid — plus total, distinct-agent and distinct-class counts.
Auth: Session (Clerk JWT)
Responses
200Memory rows (newest 200) with total, agents_count, classes_count503Required D1 / service binding not attached (db_binding_missing)
Onboarding
GET/onboarding/workflowsRead-only onboarding workflow projection
The workflow is a projection of the six per-step rows in the onboarding_steps table — there is no separate workflow store (§0 one source of truth). Returns a single-element array when the tenant has started onboarding, otherwise an empty array.
Auth: Session (Clerk JWT)
Responses
200Array of zero or one kye.onboarding.workflow.v1 objects401Missing or invalid bearer token503Required D1 / service binding not attached (db_binding_missing)
Purposes
GET/purposesList the tenant's declared purposes with KPI roll-up
A purpose is the bounded reason any agent, connector or partner may act — every Purpose Permission grant cites one. Grants themselves live under /purpose-permissions; this registry holds the definitions.
Auth: Session (Clerk JWT)
Responses
200Purpose rows (purpose_id, class, display_name, lawful_basis, active_grants, status) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/purposesDeclare a new purpose
Mints a kye:purpose URN for the tenant. The class must be a lowercase slug and the lawful basis one of the six GDPR Article 6 bases.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
class required | string | |
display_name required | string | |
lawful_basis required | string | One of consent, contract, legal_obligation, vital_interests, public_task, legitimate_interests |
Responses
201Created purpose with its minted purpose_id and active status400invalid_json, invalid_class, display_name_required or invalid_lawful_basis503Required D1 / service binding not attached (db_binding_missing)
GET/purpose-least-privilegeDeclared-vs-exercised purpose drift for the caller's tenant
Answers the least-privilege question no single existing surface answers: of the purposes this tenant declared and granted, how many were ever actually exercised? Joins the purpose registry and the grant ledger to the per-decision purpose attribution on the evidence-pack index, and classifies every declared purpose as exercised, unexercised, or not_observable. Purpose attribution only exists on decisions sealed after the decision writer began recording it, so the response reports attributed and unattributed decision counts separately and a purpose whose whole life predates the attributed window is reported as not_observable — never as unused, which would be a fabricated finding. Tenant scoping is applied in SQL on every statement.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
window_days | query | integer | Observation window in days; out-of-range values are clamped, non-numeric falls back to the default |
Responses
200Per-purpose findings (exercised / unexercised / not_observable), finding counts, the honest attributed and unattributed decision denominators, the observation window, unexercised live grants, and a compliance attestation401No session, or the session resolves to no tenant403A client-supplied tenant_id did not match the session-resolved tenant (§0.11 cross-tenant refusal)503Required D1 / service binding not attached (db_binding_missing)
DataGovernance
GET/data-flow-graphList signed data-flow seals for the caller's tenant
Exposes the observable contract of the Data Mapping Agent — seal_id, asset_count, flow_count, pii_assets, sealed_at, signed_by_kid — newest first (up to 200), with the latest seal's totals lifted to top-level asset_total / flow_total / pii_assets.
Auth: Session (Clerk JWT)
Responses
200Seal rows plus latest-seal totals503Required D1 / service binding not attached (db_binding_missing)
GET/classificationList the tenant's data-asset classifications with KPI roll-up
Every classification is tenant-scoped and Ed25519-signed (the signing kid is recorded). KPI counts special-category and restricted assets plus rows still pending a signature.
Auth: Session (Clerk JWT)
Responses
200Classification rows (classification_id, asset_id, classification, detection, confidence, signature_kid, effective_at) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/classificationRegister a new asset classification
Records the classification exactly as submitted (effective immediately); signature_kid is stored as supplied — empty until the record is signed.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
asset_id required | string | |
classification required | string | One of public, internal, confidential, restricted, top_secret, special_category |
detection | string | One of human_review, regex_scan, llm_inference, gdpr_art9_match, sector_template, schema_inference |
confidence | number | |
signature_kid | string |
Responses
201Created classification with its minted classification_id400invalid_json, asset_id_required, invalid_classification, invalid_detection or confidence_must_be_0_to_1503Required D1 / service binding not attached (db_binding_missing)
Evidence
GET/publisher-access-ledgerList the tenant's Publisher Access Ledger snapshots with KPI roll-up
Backs the KYE™ Publisher Access Ledger™ dashboard — the tenant's licensed-vs-breach split, the advisory GBP 500-per-Product accrual per operator, and the possible-spoof surfacing. Every snapshot is tenant-scoped (newest first, up to 200) and the KPI totals sum snapshots / breach_events / possible_spoof_events / accrued_total. The schedule is ADVISORY (§70): the publisher-claimable accrual from attribution signals, never a KYE-metered charge; unknown/unattributed access is never invoiced and possible-spoof is surfaced, not invoiced as certain.
Auth: Session (Clerk JWT)
Responses
200ok, contract_url, honesty{advisory,basis}, kpi roll-up and snapshots[] (each with summary + operators + effective_at)503Required D1 / service binding not attached (db_binding_missing)
POST/publisher-access-ledgerDerive and record a Publisher Access Ledger snapshot from a classified-access report
Accepts a kye.crawler_classification.v1 report (records[]) from the collector (Tier A edge or Tier C log ingest), derives the advisory breach schedule via the ONE canonical breach-schedule engine (never re-implemented, §0), and records the tenant-scoped snapshot. The customer-visible response carries a patent-safe evidence reference built only by the canonical publicEvidence() helper (§0.35).
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
report required | object | A kye.crawler_classification.v1 instance |
access_fee_per_product | integer | Per-website Clause 14 override of the advisory GBP 500-per-Product accrual |
currency | string |
Responses
201ok, snapshot{snapshot_id, summary, operators, honesty, effective_at} and a patent-safe evidence reference400invalid_json, report_records_required or invalid_report503Required D1 / service binding not attached (db_binding_missing)
GET/evidence-importList the tenant's recent evidence imports
Tenant-visible history of GRC-export imports — source tool, target kind, format and row / mapped / error counts for the latest 100 batches.
Auth: Session (Clerk JWT)
Responses
200Import history rows503Required D1 / service binding not attached (db_binding_missing)
POST/evidence-importMap a GRC-tool export into a canonical evidence batch
Forwards the upload to the single canonical mapping engine (kye-evidence-import-worker) over a service binding with the worker bearer, then records the import for tenant-visible history. Fails closed — with no service binding or bearer configured the request is refused with 503 rather than silently passing content through unmapped. Content is capped at 5 MiB of decoded text.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
source_tool required | string | |
format required | string | One of csv, json |
target_kind | string | One of control, obligation, evidence_item, ai_system |
content required | string | Raw export text (max 5 MiB) |
column_map | object | Optional source-column to canonical-field overrides |
defaults | object | Optional default field values applied to every mapped row |
Responses
200Import summary (rows, mapped, errors) plus the mapped kye.connector.evidence_import.v1 batch400invalid_json, source_tool_required, invalid_format, invalid_target_kind or content_required413content_too_large — over the 5 MiB cap502mapping_failed or import_service_unreachable — the mapping engine errored503Required D1 / service binding not attached (db_binding_missing)
GET/evidence-coverageDecision-to-Evidence-Pack coverage for the caller's tenant
Joins the tenant's decision ledger to its sealed-pack index on decision_id and reports coverage as covered / decisions, plus the full list of decisions that resolve to no pack. Breakdowns are returned per verdict, per capability and per day. dimensions reports how many rows actually carry each decision axis, so an axis the ledger does not record reads as unrecorded rather than as an empty finding. Tenant scoping is applied in SQL on both sides of the join.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
since | query | string | Window start (omit for the whole ledger) |
until | query | string | Window end (omit for the whole ledger) |
limit | query | integer | Maximum uncovered decisions returned |
Responses
200Coverage totals, per-verdict / per-capability / per-day breakdowns, axis-recording health, and the uncovered-decision list400Invalid since/until, or since not before until503Required D1 / service binding not attached (db_binding_missing)
GET/evidence-packsList evidence packs for the caller's tenant
Evidence Packs are signed audit-ready envelopes for each governed action window. This is the console-level registry view; sealing and signing mechanics live in the runtime.
Auth: Session (Clerk JWT)
Responses
200Pack rows (window, actions, sealed_at, status, sha256, compilation_seal, size_bytes, signers)503Required D1 / service binding not attached (db_binding_missing)
POST/evidence-packsCreate a new draft evidence pack
Opens a draft pack for the chosen window (default daily) with zero actions and no signers.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
window | string | One of daily, weekly, quarterly |
Responses
201Created draft pack400invalid_json503Required D1 / service binding not attached (db_binding_missing)
GET/evidence-packs/{id}Fetch a single evidence pack
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200The pack row with parsed signers array404Resource not found503Required D1 / service binding not attached (db_binding_missing)
POST/evidence-packs/{id}Attest a pack (append signer)
Appends the caller as a signer; the pack advances from awaiting_signoff to attested once three signers have signed. Each caller may sign only once.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Request body
| field | type | description |
|---|---|---|
action | string | One of attest |
Responses
200Updated signers list and status400unsupported_action404Resource not found409already_signed_by_caller503Required D1 / service binding not attached (db_binding_missing)
Approvals
GET/action-approvalsList the tenant's action proposals awaiting or decided review
Backs the GovernedUI approval queue (envelope kye.governedui.action_proposal.v1). Optional risk-level and approval-mode filters; KPI counts pending / approved / rejected / escalated proposals.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
risk_level | query | string | |
approval_mode | query | string | |
limit | query | integer |
Responses
200Proposal rows (proposal_id, actor_id, action_type, target_system, risk_level, approval_mode, state, proposed_at, decided_at, decided_by) plus kpi503Required D1 / service binding not attached (db_binding_missing)
Actions
GET/actionsPDP-mediated action history for the caller's tenant
Console-level slice of the shared decisions ledger — each row maps a decision to its action_id, actor, capability, verdict, reason_code and decided_at, newest first.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
decision | query | string | Filter by decision verdict; "all" or absent returns every verdict |
limit | query | integer |
Responses
200Action rows derived from the decisions ledger503Required D1 / service binding not attached (db_binding_missing)
Agents
GET/agentsList agents with activity roll-up
Merges the explicit agents registry with per-agent activity derived from the decisions ledger (decision_count, last_seen_at, allow / review / deny counts). Registry rows are authoritative for metadata; agents observed only in decisions appear with null metadata. Sorted by last activity.
Auth: Session (Clerk JWT)
Responses
200Merged agent list with registry metadata and activity counts503Required D1 / service binding not attached (db_binding_missing)
POST/agentsRegister a new agent
Mints a kye:agent URN. New agents start in the pilot lifecycle state with zero activity — no fabricated counts.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
label required | string | |
kind | string | One of agent, service, human, model |
capability_id | string |
Responses
201Registered agent in lifecycle_state pilot400invalid_json or missing_label503Required D1 / service binding not attached (db_binding_missing)
AICalls
GET/ai-callsPer-call ledger of model invocations
Cost, latency, token counts, purpose binding and evidence link for every model invocation; reconciles to provider invoices. Includes tenant-level KPI aggregates and distinct purpose / model lists for filter dropdowns.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
date | query | string | Restrict to a single UTC day (YYYY-MM-DD) |
purpose | query | string | |
model | query | string | |
min_cost | query | number | Only calls with cost at or above this value |
limit | query | integer |
Responses
200Call rows plus kpis (calls, cost_total, tokens, avg_latency) and distinct purposes/models503Required D1 / service binding not attached (db_binding_missing)
Webhooks
GET/webhooksList the caller's tenant's webhook endpoints
Returns endpoint metadata only. The signing secret is stored (KYE™ must sign each outbound delivery with it) but is NEVER returned here — only a short non-usable hint. Includes a 30-day delivery summary per endpoint.
Auth: Session (Clerk JWT)
Responses
200Endpoint list for the caller's tenant401Missing or invalid bearer token503Database binding unavailable
POST/webhooksRegister a webhook endpoint (signing secret returned once)
Registers an HTTPS endpoint for governance-event delivery. The signing secret is generated server-side and returned EXACTLY once — no endpoint will hand it back. Plain http is refused rather than downgraded. One active subscription per endpoint URL per tenant.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
endpoint_url required | string | Absolute HTTPS URL |
signal_types | array | Defaults to every canonical signal type when omitted |
Responses
201Endpoint registered; signing secret returned once400Invalid JSON, non-HTTPS or malformed URL, or unknown signal type401Missing or invalid bearer token409An active subscription already exists for this endpoint URL503Database binding unavailable
GET/webhooks/{id}Read one webhook endpoint
Tenant-scoped read. An id belonging to another tenant resolves to 404, never to another tenant's record (§0.11).
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200Endpoint detail401Missing or invalid bearer token404Unknown endpoint for this tenant
DELETE/webhooks/{id}Disable a webhook endpoint
Disables rather than row-deletes: webhook_deliveries rows reference subscriber_id, so a hard delete would orphan the delivery history the §30 audit trail depends on. Deliveries stop immediately. Idempotent — disabling an already-disabled endpoint returns 200 with idempotent true.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200Endpoint disabled (or already disabled)401Missing or invalid bearer token404Unknown endpoint for this tenant
ApiKeys
GET/api-keysList API keys for the caller's tenant
Returns key metadata only — id, label, display prefix, scope, env, created_by/at, last_used_at, revoked_at. The secret is never stored; only its SHA-256 hash is persisted.
Auth: Session (Clerk JWT)
Responses
200Key metadata rows (no secrets)503Required D1 / service binding not attached (db_binding_missing)
POST/api-keysCreate a new API key (secret returned once)
Mints a kye_<live|test>_ key with 192 bits of entropy. The plaintext secret is returned exactly once in this response; only its SHA-256 hash and 12-character display prefix are persisted. Unrecognised scopes are dropped; an empty scope defaults to runtime:read.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
label required | string | |
scope | string | Comma-separated scopes from the canonical kye:dictionary:api-key-scopes: runtime:read, runtime:write, runtime:*, evidence:read, evidence:write, attestation:write, directory:read, directory:write, admin:* |
env | string | One of live, test |
ttl_days | integer | Optional TTL in days. When set, the key carries expires_at = created_at + ttl_days and stops verifying after it. Omit for a long-lived key. |
ip_allowlist | array | Optional CIDR/IP allowlist. When set, the verifier rejects a presented key from any source IP outside the list. |
Responses
201Key metadata (incl. expires_at) plus the one-time plaintext secret, an Authority Finality receipt (patent-safe evidence), and a copy-now warning400invalid_json, missing_label or invalid_ttl503Required D1 / service binding not attached (db_binding_missing)
DELETE/api-keys/{id}Revoke an API key (governed hard-kill)
Sets status=revoked + revoked_at on the tenant's key and emits kye.admin.api_key.revoked.v1 to the WORM chain. Idempotent revocations of an already-revoked or unknown key return 404.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200Revoked — returns id, revoked_at and an Authority Finality receipt404not_found_or_already_revoked503Required D1 / service binding not attached (db_binding_missing)
POST/api-keys/{id}/rotateRotate an API key (mint successor + grace-window the predecessor)
Mints a successor key inheriting the predecessor's scope/env, marks the predecessor status=rotated with a bounded grace window (default 3600s, during which it still verifies before it expires), and emits kye.admin.api_key.rotated.v1 to the WORM chain. The plaintext successor secret is returned exactly once. The executable form of continuously re-earned authority (§0.33).
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Request body
| field | type | description |
|---|---|---|
grace_seconds | integer | |
reason | string |
Responses
201Successor key metadata + one-time secret + Authority Finality receipt404not_found_or_not_active503Required D1 / service binding not attached (db_binding_missing)
POST/api-keys/agent/issueAgent-callable M2M issuance of a short-lived heartbeat-bound key
The doctrine-core path (§0.30 agent principal, §0.33 Authority Finality™): an agent obtains a SHORT-LIVED, HEARTBEAT-BOUND bearer key whose authority must be continuously re-earned. The key carries a near-term expiry and a heartbeat binding; missing the check-in loses the mandate (#160). Governed identically to human issuance (§0.3 evidence family + metering). Kill- switchable via KYE_KEY_AUTHORITY_AGENT_ISSUE_DISABLED.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
principal_id required | string | The agent principal (kye:agent:* / kye:principal:*) the key is bound to. |
label | string | |
scope | string | Comma-separated canonical scopes (defaults runtime:read). |
ttl_seconds | integer | Short TTL. Capped low — this is not a long-lived credential. |
heartbeat_interval_seconds | integer |
Responses
201Short-lived key metadata (incl. expires_at + heartbeat) + one-time secret + Authority Finality receipt400invalid_json or missing_principal_id403agent_issuance_disabled (kill-switch)503Required D1 / service binding not attached (db_binding_missing)
POST/api-keys/agent/{id}/renewAgent-callable heartbeat renewal (re-earn the mandate)
Re-earns a heartbeat-bound key's authority: a renewal MUST pass a fresh admissibility check (§12 PDP) within the heartbeat window, extending expires_at by one interval and stamping last_heartbeat_at. A renewal outside the window is denied — the mandate is already lost and the key must be re-issued. This is authority as a STATE you continuously re-earn, not a grant you keep. Governed (§0.3) + metered (§23).
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200Renewed — new expires_at + Authority Finality receipt403heartbeat_window_missed or not_a_heartbeat_key404not_found_or_not_active503Required D1 / service binding not attached (db_binding_missing)
Apps
GET/appsList the tenant's installed apps with KPI roll-up
Apps are tenant-installed AI agents, copilots, runbooks and partner-supplied integrations operating under the tenant's Operating Model. KPI counts active (calls in last 24h), shadow-mode and suspended apps.
Auth: Session (Clerk JWT)
Responses
200App rows (app_id, category, display_name, mode, status, calls_24h) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/appsInstall an app
Mints a kye:app URN. A freshly installed app starts active with zero call counts; shadow mode logs decisions without enforcing them.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
category required | string | One of ai_agent, copilot, runbook, integration, connector_app |
display_name required | string | |
mode | string | One of shadow, advisory, guarded, strict |
Responses
201Installed app with its minted app_id400invalid_json, invalid_category, display_name_required or invalid_mode503Required D1 / service binding not attached (db_binding_missing)
AssuranceCards
GET/assurance-cardsList the tenant's assurance cards with KPI roll-up
Assurance cards are time-bounded signed attestations mapping real evidence to regulatory framework controls, on a 90-day rotation per §0.3. KPI counts active, expiring (within 30 days), expired cards and distinct frameworks covered.
Auth: Session (Clerk JWT)
Responses
200Card rows (card_id, framework, controls, scope, status, attested_at, expires_at, verifier_url) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/assurance-cardsGenerate a new assurance card
Mints a kye:assurance-card URN with honest timestamps — attested now, expiring 90 days later — and a public verifier URL. Controls must be a non-empty array of control identifiers.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
framework required | string | One of SOC2, ISO27001, ISO42001, EU_AI_ACT, DORA, FCA_OPRES, NIST_AI_RMF, OSCAL |
controls required | array | |
scope required | string |
Responses
201Created card with verifier_url and a 90-day expires_at400invalid_json, invalid_framework, scope_required or controls_required_array503Required D1 / service binding not attached (db_binding_missing)
AuditEvents
GET/audit-eventsQuery the tenant's append-only hash-chained audit trail
Each row stores prev_hash and a SHA-256 hash over the canonical JSON of (seq, event, actor, summary, at, prev_hash) so the chain is client-verifiable by recomputing from seq=1. Rows are returned newest first.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
family | query | string | Event-name prefix filter |
from | query | string | |
to | query | string | |
q | query | string | Substring match across actor, summary and id |
limit | query | integer |
Responses
200Event rows with seq, event, actor, summary, at, prev_hash, hash503Required D1 / service binding not attached (db_binding_missing)
POST/audit-eventsAppend an event to the tenant's audit chain
Appends the next sequence entry, linking prev_hash to the prior row (or a 64-zero genesis) and hashing the canonical JSON. Normally driven by the runtime; exposed so admins can record compliance markers such as a manually attested evidence pack.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
event required | string | |
actor | string | Defaults to the caller's email or user id |
summary | string |
Responses
201Appended event with its seq, prev_hash and hash400invalid_json or missing_event503Required D1 / service binding not attached (db_binding_missing)
BehaviourModel
GET/behaviour-modelLoad the tenant's latest behaviour-model revision
Returns the highest revision of the tenant's behaviour model (allowed actions, obligations, stop-conditions, escalation paths). A tenant with no model yet gets revision 0 with empty rows.
Auth: Session (Clerk JWT)
Responses
200Latest revision with rows, signed flag, updated_at, updated_by503Required D1 / service binding not attached (db_binding_missing)
PUT/behaviour-modelReplace the tenant's behaviour model (new immutable revision)
Inserts a new unsigned revision; prior revisions are kept immutable for sign-off and replay. Rows are shape-validated and capped at 500; string fields are length-clamped.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
rows required | array |
Responses
200New revision number, accepted row count and updated_at400invalid_json or rows_array_required503Required D1 / service binding not attached (db_binding_missing)
Billing
GET/billingAll-in-one billing view (subscription + invoices + seats)
Reads the canonical kye_grants store (binding KYE_GRANTS_DB, populated by the commercial-lifecycle worker's Stripe projection): latest subscription, up to 50 invoices with hosted Stripe URLs, and attestation seats. Returns 503 db_binding_missing until the KYE_GRANTS_DB binding is attached to the Pages project.
Auth: Session (Clerk JWT)
Responses
200subscription (or null), invoices, seats503Required D1 / service binding not attached (db_binding_missing)
POST/billingRun the BPS fee simulator
Pure function per §23 §4 — 3 basis points on the transaction value with a £5 floor and £50 cap. The only supported action is bps_simulate.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
action required | string | One of bps_simulate |
value_pence | integer | Transaction value in pence |
currency | string | |
action_class | string |
Responses
200Fee breakdown: rate_bps, computed_fee_pence, final_fee_pence, was_floored, was_capped400invalid_json or unsupported_action503Required D1 / service binding not attached (db_binding_missing)
Connectors
GET/connectorsList the tenant's installed connectors with KPI roll-up
Connectors bring outside-system events into the tenant's KYE™ evidence stream; the canonical kind vocabulary is kye:dictionary:connectors. KPI counts healthy, degraded and disconnected connectors.
Auth: Session (Clerk JWT)
Responses
200Connector rows (connector_id, kind, profile_family, display_name, status, last_harvest_at, events_24h) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/connectorsInstall a connector
Mints a kye:connector URN. A freshly installed connector is disconnected until its first authenticated harvest — honest initial state, no fabricated health.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
kind required | string | |
display_name required | string | |
profile_family required | string | One of payments, open_finance, identity, agent_runtime, commerce, compliance_evidence, security_siem, health, insurance, pension, utilities, legal, open_data |
Responses
201Installed connector in disconnected status400invalid_json, invalid_kind, display_name_required or invalid_profile_family503Required D1 / service binding not attached (db_binding_missing)
Delegations
GET/delegationsList active and revoked delegations
Authority flows actor to principal to subject under a scope with an expiry; attenuations are separate rows referencing a parent_id. State is derived lazily — revoked when revoked_at is set, expired when past expires_at, otherwise active.
Auth: Session (Clerk JWT)
Responses
200Delegation rows with derived state (active / expired / revoked)503Required D1 / service binding not attached (db_binding_missing)
POST/delegationsCreate a new delegation
Issues a delegation with a TTL (default 365 days, max 730). When attenuating via parent_id, the parent must exist and belong to the caller's tenant.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
actor required | string | |
principal required | string | |
subject required | string | |
scope required | string | |
parent_id | string | Parent delegation to attenuate |
ttl_days | integer |
Responses
201Created delegation with expires_at400invalid_json or missing_fields404parent_not_found — attenuation parent missing or not owned by this tenant503Required D1 / service binding not attached (db_binding_missing)
DELETE/delegations/{id}Revoke a delegation (soft-delete)
Sets revoked_at / revoked_by and flips state to revoked; an optional reason is recorded.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string | |
reason | query | string | Optional revocation reason |
Responses
200Revoked — returns id and revoked_at404not_found_or_already_revoked503Required D1 / service binding not attached (db_binding_missing)
DriftEvents
GET/drift-eventsList drift events with open/closed counts
The runtime opens drift events automatically; humans close them. Optional state filter; counts power the tab badges.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
state | query | string | Absent returns both |
Responses
200Event rows (type, actor, severity, opened, closed, resolution) plus open/closed counts503Required D1 / service binding not attached (db_binding_missing)
POST/drift-eventsOpen a new drift event
Records a meaning-continuity drift marker. Unrecognised types default to meaning_broken and unrecognised severities to P2.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
type | string | One of meaning_broken, obligation_unmet, scope_overflow |
severity | string | One of P0, P1, P2 |
actor | string | |
detail | string |
Responses
201Opened event with its id and opened timestamp400invalid_json503Required D1 / service binding not attached (db_binding_missing)
PATCH/drift-events/{id}Close (resolve) a drift event
Marks the event closed with a resolution; unrecognised or absent resolutions default to remediated. The resolver identity is recorded.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Request body
| field | type | description |
|---|---|---|
resolution | string | One of reconfirmed, revoked, remediated |
Responses
200Closed — returns id, closed timestamp, resolution, resolved_by404not_found_or_already_closed503Required D1 / service binding not attached (db_binding_missing)
Decisions
GET/refusal-mapRefusal clusters for the caller's tenant
Returns only the non-allow decisions (deny, reject, revoke, quarantine, require_approval, require_human_review) for the caller's tenant, clustered by reason_code x capability x actor_entity_id and ranked on each axis separately. Each cluster reports how many of its refusals resolve to an Evidence Pack. dimensions reports the real recording coverage of each axis over the refused set — reason_code is null on historical rows and populates from newer decisions forward, and a null is never inferred or defaulted. Tenant scoping is applied in SQL.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
since | query | string | Window start (omit for the whole ledger) |
until | query | string | Window end (omit for the whole ledger) |
limit | query | integer | Maximum clusters returned |
Responses
200Refusal totals, per-axis rankings, axis-recording health, and the reason_code x capability x actor clusters400Invalid since/until, or since not before until503Required D1 / service binding not attached (db_binding_missing)
GET/decisionsDecision records for the calling tenant
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
since | query | string | Lower bound on decision time |
decision | query | string | Filter by outcome |
actor | query | string | |
limit | query | integer |
Responses
200Matching decision records400`invalid_since`503Required D1 / service binding not attached (db_binding_missing)
Listings
GET/my-listingsList the tenant's directory listings with KPI roll-up
Listings are the tenant's public-facing entries in the KYE™ Directory — rule packs, agents, connectors, assurance cards — each requiring moderation before becoming visible. KPI counts published, in-review and draft listings plus removals in the last 90 days.
Auth: Session (Clerk JWT)
Responses
200Listing rows (listing_id, listing_type, title, status, views_30d) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/my-listingsCreate a new directory listing (starts as draft)
Mints a kye:listing URN. A new listing starts as draft — it must be submitted and pass moderation before becoming visible; view counts start at zero.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
listing_type required | string | One of rule_pack, agent, connector, assurance_card |
title required | string |
Responses
201Created draft listing400invalid_json, invalid_listing_type, title_required or title_too_long503Required D1 / service binding not attached (db_binding_missing)
Partners
GET/partnersList partners under tenant authority
Auth: Session (Clerk JWT)
Responses
200Partner rows (name, class, assurance tier, status, since, onboarded_by)503Required D1 / service binding not attached (db_binding_missing)
POST/partnersOnboard a new partner
Registers a third-party partner under the tenant's authority. Unrecognised assurance values default to Tier 3 and unrecognised statuses to active.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
name required | string | |
class required | string | |
assurance | string | One of Tier 1, Tier 2, Tier 3 |
status | string | One of active, suspended, off-boarded |
Responses
201Onboarded partner with its minted id400invalid_json or missing_fields503Required D1 / service binding not attached (db_binding_missing)
Plugins
GET/pluginsList the tenant's installed plugins with KPI roll-up
Plugins are tenant-side extensions of the protocol — PDP rule packs, conformance probes, custom obligations, SDK shims. KPI counts enabled, failing and update-available plugins.
Auth: Session (Clerk JWT)
Responses
200Plugin rows (plugin_id, kind, display_name, version, state, health, invocations_24h, update_available) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/pluginsInstall a plugin
Mints a kye:plugin URN. A freshly installed plugin is disabled until the tenant enables it in the decision flow — honest initial state. Version must be semver-shaped.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
kind required | string | One of rule_pack, conformance_probe, obligation, sdk_shim |
display_name required | string | |
version | string |
Responses
201Installed plugin in disabled state400invalid_json, invalid_kind, display_name_required or invalid_version503Required D1 / service binding not attached (db_binding_missing)
PurposePermissions
GET/purpose-permissionsList Purpose Permission grants for the caller's tenant
Every action under a KYE-governed agent must cite an issued Purpose Permission. Status is derived per row — revoked, pending_reconfirm (past expiry), expiring (within 30 days) or active.
Auth: Session (Clerk JWT)
Responses
200Grant rows (grantee, purpose, scope, expires, derived status, issued/revoked/reconfirmed audit fields)503Required D1 / service binding not attached (db_binding_missing)
POST/purpose-permissionsIssue a new Purpose Permission
Issues an active grant with a TTL (default 90 days, max 365). The issuer identity is recorded from the session.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
grantee required | string | |
purpose required | string | |
scope required | string | |
ttl_days | integer |
Responses
201Issued grant with expires timestamp400invalid_json or missing_fields503Required D1 / service binding not attached (db_binding_missing)
PATCH/purpose-permissions/{id}Reconfirm a Purpose Permission (extend expiry)
Extends the grant by ttl_days (default 90, max 365) from now, records last_reconfirmed_at and resets status to active. Revoked grants cannot be reconfirmed.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Request body
| field | type | description |
|---|---|---|
ttl_days | integer |
Responses
200New expires, last_reconfirmed_at and reconfirmed_by404not_found_or_revoked503Required D1 / service binding not attached (db_binding_missing)
DELETE/purpose-permissions/{id}Revoke a Purpose Permission (soft-delete)
Sets revoked_at / revoked_by and flips status to revoked; an optional reason is recorded.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string | |
reason | query | string | Optional revocation reason |
Responses
200Revoked — returns id and revoked_at404not_found_or_already_revoked503Required D1 / service binding not attached (db_binding_missing)
Replay
GET/replayList the tenant's replay runs with KPI roll-up
Replay runs reference an evidence_pack_id and are signed via kye.replay.proof.v1; divergence opens a Resilience Loop ticket automatically. KPI reports replays in the last 24h, overall match rate, divergences in the last 30 days and signature failures.
Auth: Session (Clerk JWT)
Responses
200Run rows (replay_id, evidence_pack_id, decision_id, verdict, signature_ok, requested/completed timestamps) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/replayQueue a new replay of an evidence pack
Queues a run in the pending verdict — only the Replay Engine may resolve it to verified, diverged or signature_failed. Signature status is unknown (false) until the engine runs.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
evidence_pack_id required | string | |
decision_id | string | Optional single decision to replay |
Responses
201Queued run in pending verdict400invalid_json or evidence_pack_id_required503Required D1 / service binding not attached (db_binding_missing)
Risk
GET/riskList the tenant's risk assessments with KPI roll-up
Assessments are tenant-scoped, audit-chained and framework-floor-governed. KPI counts prohibited verdicts in the last 24h plus high, limited and minimal tiers across the latest 500 assessments.
Auth: Session (Clerk JWT)
Responses
200Assessment rows (assessment_id, subject_id, subject_class, tier, score, reason_codes, framework_floor, effective_at) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/riskRegister a new risk assessment for a subject
Records the assessment exactly as submitted — no fabricated scores or tier upgrades. Score is an integer 0-100.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
subject_id required | string | |
subject_class required | string | One of decision, agent, capability, scenario, operating_model, deal, rule_pack, connector |
tier | string | One of minimal, limited, high, unacceptable, prohibited |
score | integer | |
reason_codes | array | |
framework_floor | string | One of eu_ai_act, dora, gdpr, nist_ai_rmf, iso_42001, fca_opres, pci_dss, sox, none |
Responses
201Recorded assessment with its minted assessment_id400invalid_json, subject_id_required, invalid_subject_class, invalid_tier, score_must_be_0_to_100 or invalid_framework_floor503Required D1 / service binding not attached (db_binding_missing)
Scopes
GET/scopesList the tenant's declared scopes with KPI roll-up
Scopes are capability + dataset + jurisdiction triples bounding what an agent may do within a granted purpose. KPI counts active scopes, scopes bound to at least one purpose, total datasets and distinct jurisdictions.
Auth: Session (Clerk JWT)
Responses
200Scope rows (scope_id, capability, datasets, jurisdiction, bound_purposes, status) plus kpi503Required D1 / service binding not attached (db_binding_missing)
POST/scopesDeclare a new scope
Mints a kye:scope URN. A freshly declared scope starts active with zero purpose bindings — bindings are created through the Purpose Permission rail. Datasets accept an array or comma-separated string.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
capability required | string | One of read, write, delete, verify, attest, audit, delegate |
jurisdiction | string | One of GB, EU, US, SG, AU |
datasets | object |
Responses
201Declared scope with zero purpose bindings400invalid_json, invalid_capability or invalid_jurisdiction503Required D1 / service binding not attached (db_binding_missing)
StateMachines
GET/state-machine-assignmentList the tenant's state machine assignments with KPI roll-up
An assignment binds a tenant state machine to a specific entity and tracks its current state. KPI counts assignments, distinct bound entities, transitions in the last 24h and assignments with no transition in 30 days (stuck); includes distinct entity classes for the filter bar.
Auth: Session (Clerk JWT)
Responses
200Assignment rows (assignment_id, state_machine_id, entity_id, entity_class, current_state, state_since, last_transition_at, transitions_24h) plus kpi and entity_classes503Required D1 / service binding not attached (db_binding_missing)
POST/state-machine-assignmentAssign a state machine to an entity
Verifies the referenced state machine exists and belongs to the caller's tenant, then creates the assignment in the given initial state (default pending) with zero transitions.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
state_machine_id required | string | |
entity_id required | string | |
entity_class required | string | Lowercased; non-alphanumerics become underscores |
initial_state | string |
Responses
201Created assignment in its initial state400invalid_json, state_machine_id_required, entity_id_required or entity_class_required404state_machine_not_found_or_not_owned503Required D1 / service binding not attached (db_binding_missing)
Stripe
POST/stripe/checkout-sessionCreate a Stripe Checkout Session for the tenant's subscription
Creates a subscription-mode Checkout Session via the Stripe REST API, stamping the tenant id into client_reference_id and metadata, and persists a checkout intent so the webhook can correlate checkout.session.completed back to the tenant. The client redirects to the returned url.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
price_id required | string | Stripe Price id |
success_path | string | Return path on success (default /billing.html?checkout=ok) |
cancel_path | string | Return path on cancel (default /billing.html?checkout=cancel) |
Responses
200session_id, hosted checkout url and expires_at400invalid_json or bad_price_id502stripe_error — Stripe API rejected the request503Required D1 / service binding not attached (db_binding_missing)
GET/stripe/invoicesList invoices for the caller's tenant
Reads the canonical invoices table in the kye_grants store (binding KYE_GRANTS_DB), populated by the commercial-lifecycle worker's Stripe projection. Hosted Stripe invoice URLs are passed straight through.
Auth: Session (Clerk JWT)
Responses
200Invoice rows with totals, status, hosted URL and billing period (latest 100)503Required D1 / service binding not attached (db_binding_missing)
POST/stripe/portal-sessionCreate a Stripe Customer Portal session
Looks up the tenant's stripe_customer_id from its most recent non-cancelled subscription in the kye_grants store, then creates a Billing Portal session returning to /billing.html.
Auth: Session (Clerk JWT)
Responses
200Portal url for the caller's Stripe customer404no_active_subscription — create one via /stripe/checkout-session first502stripe_error — Stripe API rejected the request503Required D1 / service binding not attached (db_binding_missing)
Usage
GET/usageUsage and billing aggregates for the caller's tenant
Month-to-date decision count, evidence packs created, AI cost and token totals, a 4-week weekly decision sparkline, and a 30-day meter summary grouped by meter class. Honest zeros when tables are empty.
Auth: Session (Clerk JWT)
Responses
200decisions_month, evidence_packs_month, ai_cost_month, ai_tokens_month, weekly_decisions, meters503Required D1 / service binding not attached (db_binding_missing)
WhiteLabel
GET/white-label-configRead the active white-label config for the caller's tenant
Resolves the brand config through the tenant's active consultant link, preferring a tenant-specific row over the consultant-wide default. Read-only.
Auth: Session (Clerk JWT)
Responses
200Brand config (brand_name, colours, logo_url, domain, support_email, legal_footer)401Missing or invalid bearer token404No active white-label config applies to this tenant503db_binding_missing or white-label tables not provisioned
Widgets
GET/widgetsList deployed widgets for the caller's tenant
Each widget carries its own licence and revocation path; the embed CDN returns 410 once a widget is revoked.
Auth: Session (Clerk JWT)
Responses
200Widget rows (kind, host, pages, licence, status, installed, views_30d, revoked_at)503Required D1 / service binding not attached (db_binding_missing)
POST/widgetsGenerate a new widget embed
Mints a kye:widget URN with a fresh 24-character embed key and returns the ready-to-paste embed snippet pointing at the widget CDN.
Auth: Session (Clerk JWT)
Request body (required)
| field | type | description |
|---|---|---|
kind required | string | |
host | string | Optional host the embed is installed on |
licence | string | One of commercial, evaluation, open |
Responses
201Created widget with embed_key and embed_snippet400invalid_json or missing_kind503Required D1 / service binding not attached (db_binding_missing)
POST/widgets/{id}Rotate a widget's embed key
Issues a fresh 24-character embed key for an unrevoked widget. The only supported action is rotate.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Request body (required)
| field | type | description |
|---|---|---|
action required | string | One of rotate |
Responses
200New embed_key for the widget400unsupported_action404not_found_or_revoked503Required D1 / service binding not attached (db_binding_missing)
DELETE/widgets/{id}Revoke a widget (soft-delete)
Flips status to revoked and sets revoked_at; the embed CDN then serves 410 for this widget.
Auth: Session (Clerk JWT)
Parameters
| name | in | type | description |
|---|---|---|---|
id required | path | string |
Responses
200Revoked — returns id and revoked_at404not_found_or_already_revoked503Required D1 / service binding not attached (db_binding_missing)