---
title: "KYE Protocol™ Site API | API reference"
description: "KYE Protocol™ Site API: 23 operations from site.yaml, a published KYE Protocol™ OpenAPI contract."
url: https://kyeprotocol.com/developers/api/site/
lang: en
source: "KYE Protocol"
---

> KYE Protocol™ Site API: 23 operations from site.yaml, a published KYE Protocol™ OpenAPI contract.

API reference

# KYE Protocol™ Site API

23 operations · `site.yaml`

**Servers**

`https://kyeprotocol.com`

**Version**

1.0.0

**Source**

[site.yaml](https://kyeprotocol.com/developers/api/site.yaml)

Public-site Pages Functions surface for kyeprotocol.com — the lead-capture forms (Audit Pilot™, PoC, GovernedUI access, contact), DSAR intake, expert-review wall, the Consultant Marketplace™ public listing, the public stats ticker, the quiz evidence sink, the §38 Comms Engine™ unsubscribe surface, the Clerk webhook receiver and the public self-audit trust surface.

Operations are unauthenticated public form endpoints unless noted. Form endpoints are rate-limited per salted IP hash (the raw IP is never stored) and carry a hidden `website` honeypot field — a non-empty honeypot is silently discarded with a 200 so bots learn nothing. Outbound email always routes through the KYE™ Comms Engine™ (constitution §38); privileged actions emit the §0.3 evidence-event family.

Internal 1 LeadCapture 7 Dsar 3 ExpertReviews 3 Marketplace 2 PublicData 2 Comms 2 Trust 1 Webhooks 2

## Internal

GET `/api/_internal/healthcheck-workers` Fan-out healthcheck of every deployed KYE™ Worker (operator-only)

Probes each deployed KYE™ Worker's `/healthz` via the Service bindings configured on the kye-protocol Pages project (the only reachable path because every Worker runs with `workers_dev=false` and no public route). Returns one JSON payload with a per-worker status, latency and version, plus a summary block; the healthcheck-workers.yml workflow archives it to `_diagnostics/healthcheck/<ts>.json`. Gated by the `HEALTHCHECK_INTERNAL_TOKEN` shared secret when set.

**Auth:** HealthcheckBearer

### Responses

- `200` Every probed Worker reported healthy
- `207` Multi-status — at least one Worker reported unhealthy (same body shape as the 200).
- `401` Bearer token missing or mismatched

## LeadCapture

GET `/api/audit-pilot` Endpoint self-description for the Audit Pilot™ application form

Returns a small JSON descriptor (expected method + the pilot-apply page URL) so a GET probe of the form endpoint is self-documenting rather than a 405.

### Responses

- `200` Endpoint descriptor

POST `/api/audit-pilot` Submit an Audit Pilot™ application (public)

Receives the pilot-apply form (JSON or form-data), validates the enum fields (role, company size, industry, urgency, SKU), blocks free-email-provider addresses, requires all four consent clauses (tos, privacy, aup, authority), emits a signed `kye.consent.acceptance.v1` record, persists application + consent to D1, then in the background dispatches the §38 admin alert + applicant confirmation (suppressed for detected test traffic) and enqueues the §22 onboarding and §27 commercial-lifecycle workflows. Rate limit: 3 submissions per 24 h per IP hash. Honeypot field `website`.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `full_name` **required** | string |  |
| `email` **required** | string | Work email — free providers are rejected |
| `role` **required** | string | One of CISO, CDO, Head of AI, Head of Compliance, Procurement Lead, Engineering Lead, Other |
| `company` **required** | string |  |
| `company_size` **required** | string | One of <100, 100-1k, 1k-10k, 10k-50k, 50k+ |
| `industry` **required** | string | One of Financial Services - Banking, Financial Services - Insurance, Financial Services - Payments/PSP, Healthcare, Pharma, Public Sector, Other Regulated, Other |
| `regulatory_regime` | object | One or more applicable regulators (unknown values are dropped) |
| `ai_workflow` **required** | string |  |
| `urgency` **required** | string | One of This quarter, Next quarter, This half, Within 12 months, Exploring |
| `sku_id` **required** | string | One of KYE-DISCOVERY-001, KYE-AUDIT-PILOT-001, KYE-REG-PILOT-001, KYE-GOVOPS-PILOT-001, KYE-EDGE-PILOT-001, KYE-SANDBOX-PILOT-001, KYE-CKAN-AUDIT-PILOT-001, KYE-CKAN-GOVOPS-PILOT-001, UNDECIDED |
| `heard_from` | string |  |
| `consent_tos` | object | Must be accepted |
| `consent_privacy` | object | Must be accepted |
| `consent_aup` | object | Must be accepted |
| `consent_authority` | object | Must be accepted |
| `consent_dpa` | object | Optional DPA clause |
| `website` | string | Honeypot — leave empty |

### Responses

- `200` Application accepted (or honeypot silently discarded). The signed consent record is echoed back so the client can store the receipt.
- `400` Validation failure. Error codes include \`invalid\_body\`, \`missing\_field:<name>\`, \`invalid\_sku\_id\`, \`invalid\_email\_shape\`, \`free\_email\_provider\_blocked\`, \`invalid\_role\`, \`invalid\_company\_size\`, \`invalid\_industry\`, \`invalid\_urgency\`, \`ai\_workflow\_too\_short\`, \`co…
- `429` Per-IP-hash rate limit exceeded. The \`retry-after\` header carries the wait in seconds.

GET `/api/contact` Endpoint self-description for the contact form

### Responses

- `200` Hint that the endpoint accepts POSTed contact forms

POST `/api/contact` Submit the contact-modal form (public)

Receives the contact-modal submission from any KYE Protocol™ page (JSON or form-urlencoded), validates + spam-filters it, dispatches the §38 `contact.inbound.v1` admin notification (with one-click Approve/Reject buttons for partner/trainer/auditor topics, §27 §4 dual-channel admin) and a `contact.applicant-ack.v1` receipt to the submitter, then persists an audit row to D1. Rate limit: 5 submissions per IP per 5 minutes. Honeypot field `website`. When the mail binding is unattached the endpoint answers 503 with `fallback: "mailto"` so the client degrades to a mailto: link — a submission is never silently dropped.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `name` **required** | string |  |
| `email` **required** | string |  |
| `organisation` **required** | string |  |
| `phone` **required** | string |  |
| `position` **required** | string |  |
| `topic` | string | Routing topic (default \`general\`; partner/trainer/auditor get admin action buttons) |
| `message` **required** | string |  |
| `accept` **required** | object | Terms + privacy acceptance — required truthy |
| `website` | string | Honeypot — leave empty |

### Responses

- `200` Mail dispatched (or honeypot silently discarded)
- `400` Validation failure — \`invalid\_body\`, \`missing\_field:<name>\`, \`invalid\_email\`, \`terms\_not\_accepted\`, \`message\_too\_long\`.
- `429` Per-IP-hash rate limit exceeded. The \`retry-after\` header carries the wait in seconds.
- `502` The mail-sender dispatch failed; client should fall back to mailto
- `503` Mail binding unattached on this deployment; client should fall back to mailto

POST `/api/engage-access` Request access to a GovernedUI™ SKU tier (public)

Single canonical entrypoint for every GovernedUI™ access request from the public site (constitution §27 §4 dual-channel admin). Maps the `tier` field to one of the 5 canonical GovernedUI SKUs (pilot → KYE-GUI-PILOT-001, department → KYE-GUI-DEPT-001, enterprise → KYE-GUI-ENT-001, regulated → KYE-GUI-REG-001, national → KYE-GUI-NAT-001), persists the request to D1, emits the engagement-access evidence event to the audit chain, then dispatches the §38 admin alert with Approve / Request-changes buttons plus an applicant confirmation. The Stripe payment link is returned after admin approval, never inline. Rate limit: 3 per 24 h per IP hash. Honeypot field `website`. Work email required.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `tier` **required** | string | One of pilot, department, enterprise, regulated, national |
| `name` **required** | string |  |
| `email` **required** | string | Work email — free providers are rejected |
| `organisation` **required** | string |  |
| `phone` | string |  |
| `role` **required** | string |  |
| `agent_classes` | string | Scope — agent classes to govern |
| `protected_systems` | string | Scope — systems the agents touch |
| `business_units` | string |  |
| `regulatory_exposure` | string |  |
| `deployment_topology` | string |  |
| `message` | string |  |
| `consent_accepted` **required** | boolean | Must be truthy |
| `website` | string | Honeypot — leave empty |

### Responses

- `200` Request recorded (or honeypot discarded with \`request\_id: "discarded"\`)
- `400` Validation failure — \`json\_parse\_failed\`, \`unknown\_tier\` (body lists the valid tiers), \`missing\_required\_field\`, \`invalid\_email\`, \`work\_email\_required\`, \`consent\_required\`.
- `429` Per-IP-hash rate limit exceeded. The \`retry-after\` header carries the wait in seconds.
- `500` Unhandled handler exception (message echoed)

OPTIONS `/api/consultant-lead` CORS preflight for the consultant-lead capture form

Answers 204 allowing GET/POST/OPTIONS with the content-type header from the https://kyeprotocol.com origin, cacheable for 24 h. The GET/POST operations themselves are declared in the consultant-programme OpenAPI document (§0 — one declaration per operation).

### Responses

- `204` Preflight accepted (empty body)

POST `/api/v1/poc/apply` Submit a Proof-of-Concept application (public)

Backs /poc.html. Maps the `tier` field to the canonical PoC SKU (discovery/undecided → KYE-POC-DISCOVERY-001, audit → KYE-POC-AUDIT-001, reg → KYE-POC-REG-001, govops → KYE-POC-GOVOPS-001, edge → KYE-POC-EDGE-001, sandbox → KYE-POC-SANDBOX-001), persists the `kye.poc.application.v1` shape to D1, then dispatches the §38 admin alert with Approve / Request-changes buttons and the applicant confirmation (§27 §4 dual-channel admin). Rate limit: 3 per 24 h per IP hash. Honeypot field `website`. Work email required.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `tier` **required** | string | One of discovery, audit, reg, govops, edge, sandbox, undecided |
| `name` **required** | string |  |
| `email` **required** | string | Work email — free providers are rejected |
| `company` **required** | string |  |
| `role` | string |  |
| `workflow` | string | The AI workflow to govern in the PoC |
| `frameworks` | string | Regulatory frameworks in scope |
| `website` | string | Honeypot — leave empty |

### Responses

- `200` Application recorded (or honeypot discarded with \`application\_id: "discarded"\`)
- `400` Validation failure — \`json\_parse\_failed\`, \`unknown\_tier\` (body lists the valid tiers), \`missing\_required\_field\`, \`invalid\_email\`, \`work\_email\_required\`.
- `429` Per-IP-hash rate limit exceeded. The \`retry-after\` header carries the wait in seconds.
- `500` Unhandled handler exception (message echoed)

## Dsar

GET `/api/dsar` Endpoint self-description for the DSAR intake

### Responses

- `200` Hint describing the expected POST body

POST `/api/dsar` File a Data Subject Access Request (public)

Self-service DSAR intake under GDPR Art. 15–21 and the equivalent rights in UK GDPR, CCPA/CPRA, PIPEDA, LGPD, PIPL and POPIA (backs /dsar.html). Mints a stable `kye:dsar-request:<date>.<hex>` reference, persists a WORM row to D1 (status transitions are append-only via supersession), emits a §0.3 `kye.evidence.decision_map.v1` event (`dsar_request.filed`), then sends the §38 admin alert with one-click Acknowledge/Reject buttons and a subject acknowledgement carrying the statutory deadline. Rate limit: 5 requests per 24 h per IP hash.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `controller` **required** | string | The data controller the request targets |
| `right` **required** | string | One of access, rectification, erasure, restriction, portability, objection, withdraw\_consent, ccpa\_know, ccpa\_delete, ccpa\_opt\_out |
| `regime` | string | One of gdpr, uk\_gdpr, ccpa, pipeda, lgpd, pipl, popia, other |
| `email` **required** | string |  |
| `name` | string |  |
| `detail` | string | Specific items / time window |
| `consent` **required** | boolean | Must be \`true\` |

### Responses

- `201` Request filed; statutory window computed per regime
- `400` Validation failure — body carries \`reason\` ∈ {invalid\_json, controller\_required, invalid\_right, invalid\_regime, invalid\_email, consent\_required}.
- `429` More than 5 requests in 24 h from the same IP hash

OPTIONS `/api/dsar` CORS preflight for the DSAR intake

Answers 204 with the allowed methods (POST, GET, OPTIONS) and allowed request header (content-type). No allow-origin header is set — the form is same-origin on kyeprotocol.com.

### Responses

- `204` Preflight accepted (empty body)

## ExpertReviews

GET `/api/expert-review` List published expert reviews (public)

Lists reviews for the expert wall. Only `status=published` is publicly readable; requesting `pending` or `rejected` returns 401 (moderation listing is owners-only via the Admin Console). Without the D1 binding the endpoint degrades to an honest empty list (`not_provisioned: true`) so the page still renders.

### Parameters

| name | in | type | description |
| --- | --- | --- | --- |
| `status` | query | string | Only \`published\` is allowed unauthenticated |
| `limit` | query | integer |  |

### Responses

- `200` Published reviews, newest first
- `401` Non-published status requested without owner auth

POST `/api/expert-review` Submit an expert review for moderation (public)

Accepts a review of a named KYE™ artefact (JSON or form-data), validates the verdict enum and the optional LinkedIn profile URL (https linkedin.com /in or /pub paths only), emits a `kye.evidence.audit_event.v1` to the AI Call Ledger queue, inserts the row as `status=pending`, then notifies the moderation inbox via the §38 `expert-review.brief.v1` template with one-click Approve-&-publish / Reject buttons and sends the submitter the `expert-review.applicant-ack.v1` receipt (both fail-soft — the persisted row is canonical). Rate limit: 3 submissions per 24 h per day-salted IP hash. Honeypot field `website`.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `name` **required** | string |  |
| `role` **required** | string |  |
| `affiliation` **required** | string |  |
| `email` **required** | string | Used for the receipt + embed code; never published |
| `artefact` **required** | string | The KYE™ artefact reviewed |
| `review_text` **required** | string |  |
| `verdict` **required** | string | One of approved, approved\_with\_suggestions, changes\_requested, under\_discussion |
| `linkedin` | string | Optional public LinkedIn profile URL (https only) |
| `website` | string | Honeypot — leave empty |

### Responses

- `200` Review queued for moderation (or honeypot discarded)
- `400` Validation failure — \`invalid\_body\`, \`missing\_field:<name>\`, \`invalid\_email\`, \`invalid\_verdict\`, \`review\_too\_long\`, \`invalid\_linkedin\` (each with a human \`hint\`).
- `429` Per-IP-hash rate limit exceeded. The \`retry-after\` header carries the wait in seconds.
- `500` D1 insert failed (truncated cause in \`hint\`, review id echoed)
- `503` D1 binding not yet attached on this deployment (\`not\_provisioned\`)

OPTIONS `/api/expert-review` CORS preflight for the expert-review endpoint

Answers 204 allowing GET/POST/OPTIONS with the content-type header from the https://kyeprotocol.com origin, cacheable for 24 h.

### Responses

- `204` Preflight accepted (empty body)

## Marketplace

GET `/api/marketplace/consultants` List visible consultants for the public marketplace

Returns every consultant whose status is `onboarding` or `certified` (master → professional → associate, then newest first) for the Consultant Marketplace™ page, with facet counts for the filter UI. Email addresses are never returned and applicant lead rows are never exposed. Edge-cached for 5 minutes. Without the D1 binding (or before the table exists) the endpoint degrades to an honest empty list (`not_provisioned: true`).

### Parameters

| name | in | type | description |
| --- | --- | --- | --- |
| `sector` | query | string | Keep only consultants whose sectors include this tag |
| `jurisdiction` | query | string | ISO-3166 alpha-2 country filter |
| `level` | query | string | Certification-level filter |
| `q` | query | string | Case-insensitive name / bio match |
| `limit` | query | integer |  |

### Responses

- `200` Visible consultants + facet counts
- `500` Query failed (truncated message echoed)

OPTIONS `/api/marketplace/consultants` CORS preflight for the marketplace listing

Answers 204 allowing GET/OPTIONS with the content-type header from the https://kyeprotocol.com origin, cacheable for 24 h.

### Responses

- `204` Preflight accepted (empty body)

## PublicData

GET `/api/v1/public/stats` Public counters for the hero live ticker

Returns the small public counter set consumed by `assets/live-ticker.js` (evidence packs verified, decisions governed, frameworks mapped). Tier 1 reads bounded COUNT queries from D1 when bound; otherwise it serves the repo-derived snapshot (`source` pins which tier answered). Edge-cached 60 s to match the client poll. Emits a fire-and-forget `kye.compliance.attestation.v1` per response (§0.3).

### Responses

- `200` Current public counters

POST `/api/v1/quiz/respond` Record a completed quiz response (public, best-effort)

Evidence sink for the public quiz widget (/quiz.html → assets/quiz.js): the widget POSTs a `kye.quiz.response.v1` envelope on completion so even the lead-capture funnel lands in the WORM audit chain (§0.3). The client treats this as fire-and-forget, so the handler is resilient — it persists when D1 is bound, emits the audit event when the audit-chain base URL is set, and always answers 200 for a well-formed body so a transient backend wobble never breaks the share/score UX.

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `quiz_id` **required** | string |  |
| `score` **required** | number |  |
| `band` **required** | string | Scoring band the result fell into |
| `answers` | array | Raw answer list (persisted as JSON, capped at 8000 chars) |
| `submitted_at` | string |  |

### Responses

- `200` Response accepted; persistence + evidence flags reported honestly
- `400` \`invalid\_body\` (unparseable JSON) or \`missing\_required\_field\` (quiz\_id, score, band)
- `500` Unhandled handler exception (truncated message echoed)

## Comms

GET `/comms/unsubscribe` Human one-click unsubscribe (renders a confirmation page)

Destination of the canonical `link.unsubscribe` footer link in every §38 / §62 outbound email (PECR reg. 22, CAN-SPAM §5, GDPR Art. 21 — one click, no login, honoured immediately). Verifies the signed token in `?u=`, records the suppression through the ONE canonical comms-engine suppress path (the recipient travels as a one-way hash, never the raw email), then renders an HTML confirmation page. A backend hiccup still confirms intent with a 202 — the suppress call is idempotent on the next click.

### Parameters

| name | in | type | description |
| --- | --- | --- | --- |
| `u` **required** | query | string | Signed unsubscribe token minted into the email footer |

### Responses

- `200` Suppression recorded; confirmation page rendered
- `202` Token verified but the suppress backend errored — intent acknowledged, will be actioned
- `400` Token missing, altered or expired (error page rendered)

POST `/comms/unsubscribe` RFC 8058 one-click unsubscribe (mail-client POST)

Mail clients POST `List-Unsubscribe=One-Click` here; the signed token rides in `?u=` and/or the form/JSON body (`u` or `token`). No HTML is returned — a 2xx text/plain is the RFC 8058 contract. Suppression goes through the same canonical comms-engine path as the GET flow.

### Request body

| field | type | description |
| --- | --- | --- |
| `u` | string |  |
| `token` | string |  |

### Responses

- `200` Suppression recorded ("unsubscribed")
- `202` Token verified, suppress backend errored — accepted for action
- `400` Missing or invalid token

## Trust

GET `/trust/self-audit/live/{[path}]` Fetch a production-signed live self-audit bundle (public)

Public read side of §0.3 self-governance-on-production: serves the Ed25519-signed live self-audit bundles the kye-self-audit-daemon Worker publishes to the shared R2 bucket. The catch-all segment addresses one partition — `latest`, `latest/bundle.json`, `<YYYY-MM-DD>/` or `<YYYY-MM-DD>/bundle.json`; an empty segment resolves to `latest`. Any other shape is rejected (no arbitrary R2 traversal). Anyone can verify the signature against /trust/self-audit-jwks.json with `node scripts/verify-self-audit.mjs`. Edge-cached 5 minutes.

### Parameters

| name | in | type | description |
| --- | --- | --- | --- |
| `[path` **required** | path | string | Catch-all partition selector — \`latest\` or a \`YYYY-MM-DD\` date, optionally followed by \`/bundle.json\`. |

### Responses

- `200` The signed bundle bytes, verbatim
- `404` Non-addressable path shape (\`not\_found\`) or no bundle published for the partition yet (\`no\_live\_bundle\`)
- `503` R2 binding not attached on this deployment (\`r2\_binding\_unavailable\`) — honest machine-readable fail, never a synthesised bundle

## Webhooks

GET `/webhooks/clerk` Webhook receiver self-description

Returns the receiver hint plus the list of the 18 routed Clerk event types, so a GET probe of the configured webhook URL is self-documenting.

### Responses

- `200` Receiver hint + routed event list

POST `/webhooks/clerk` Receive a Clerk webhook delivery (Svix-signed)

Verifies the Svix v1 signature (svix-id / svix-timestamp / svix-signature headers) against the configured signing secret, then projects each of the 18 routed Clerk event types (user.\*, session.\*, organization.\*, organizationMembership.\*, organizationInvitation.\*, invitation.\*) to a `kye.evidence.<bucket>.<verb>.v1` envelope persisted to R2 and fanned out on the webhook queue when those bindings are wired. Recognised events are always ACKed 200 even if a downstream projection fails (the audit chain is canonical; the D1 projection is rebuildable) — Clerk's retry budget is small and a half-applied projection is worse than a missed one. With the secret unset the endpoint answers 503 so Clerk retries with backoff.

### Parameters

| name | in | type | description |
| --- | --- | --- | --- |
| `svix-id` **required** | header | string |  |
| `svix-timestamp` **required** | header | string |  |
| `svix-signature` **required** | header | string | Space-separated \`v1,<base64>\` signature tokens |

### Request body (required)

| field | type | description |
| --- | --- | --- |
| `type` | string |  |
| `data` | object |  |

### Responses

- `200` Delivery verified and acknowledged
- `400` \`missing\_svix\_headers\` or \`invalid\_json\`
- `401` \`invalid\_signature\` or \`signature\_verification\_failed\`
- `503` \`webhook\_secret\_unconfigured\` — signing secret unset; Clerk will retry
