Dashboard
The customer's account UI — one of four management modalities, alongside OpenAPI, MCP, and Terraform. All four read and write the same canonical management API; the dashboard is the human-facing surface.
Signing in
Three paths into the dashboard — magic link, Continue with GitHub, Continue with Google. All three resolve to the same account row keyed by email; the platform stores your email and nothing else. Mechanism in account-login; step-by-step in guides/sign-up-and-login. The session itself is D1-backed and revocable — sign out anywhere kills the session immediately.
Per site key, the dashboard exposes:
| Surface | Purpose |
|---|---|
| Public site key | Used in the widget's sitekey attribute |
| Secret | Used by the customer backend when calling /siteverify. Rotatable. |
| Allowed domains | Origin allowlist for the widget |
| Aggregate stats | sessions started, sessions client-completed, sessions server-verified — counts only |
| Integration health diagnostics | Derived from gaps between the three counts above |
What is intentionally absent
- No game pool config. Pool selection is a widget-side
games=attribute — see widget. There is no server-side allowlist or pinned set. See principles. - No game allowlist. Customers choose their own games per page; the platform doesn't gate which ones they're allowed to use.
- No user data anywhere. Per-site-key aggregate counters only — no IPs, no UAs, no geo, no per-user history. See privacy.
- No risk thresholds, score histograms, or behavioral charts. The score is game metadata, not a security signal.
Integration health diagnostics
The three counters are deliberately structured so their gaps tell you something useful:
sessions started–sessions client-completed≈ user drop-off (game too hard, slow load, accessibility gap)sessions client-completed–sessions server-verified≈ customer integration problem (forgot to call/siteverify, bad secret, network issue on backend)
That's the entire analytics layer. Anything more would require collecting data we structurally refuse to collect.
Secret rotation
Customers can rotate the secret at any time. The dashboard issues a new secret; the old secret continues to verify tokens for a short overlap window so the customer can deploy the new one without downtime. (Specific window length will be documented when the rotation flow ships in phase 3.)
Site key shape
- Public key:
cpt_pub_... - Secret:
cpt_sec_...
Prefixes make leaked-secret detection (e.g. GitHub secret scanning) trivial to wire up later.
Management API tokens
Account-level credentials for the non-UI management modalities (OpenAPI, MCP, Terraform). Distinct from the per-site-key cpt_sec_... (which is for runtime /siteverify only).
| Surface | Purpose |
|---|---|
| Token issue | Mint a new management token (cpt_pat_...). Shown once at issue time; not retrievable afterwards. |
| Token list | Names, prefixes, last-used timestamp, scope (when scopes ship). Never the full token. |
| Token revoke | Immediate; revoked tokens stop authenticating on the next request. |
A leaked management token compromises account configuration; a leaked cpt_sec_... only compromises one site key's verification. Different blast radii — different rotation cadences are appropriate.
Paid-tier surfaces
Visible only on paid site keys. Free-tier accounts do not see these sections.
| Surface | Purpose |
|---|---|
| Hosted verification toggle | Enables the hosted-verification forwarder for the site key |
| Destination configuration | Webhook URL and / or email address. Both can be enabled simultaneously. |
| Webhook signing key | Issued when a webhook destination is added; used by the customer's handler to verify incoming requests |
| Forwarder URL | The Caputchin URL the customer points their form's action at |
Hosted-verification configuration follows the same posture as the rest of the dashboard: aggregate counters on delivery success / failure, no per-submission history. See ADR-0007 for the rationale.
Scoreboards (Post-MVP)
Per-site-key, per-game leaderboard view. Surfaces top-N entries plus an aggregate score distribution. Entries display the session-scoped 3-letter handle collected by the widget; rows whose nickname was never submitted render as ---. No sort, filter, or query by nickname is exposed — the column is display-only. Same privacy posture as the rest of the dashboard: no per-user dimension, no IPs / UAs / geo, no cross-session linkage. See ADR-0014 for the design.