Web UI
npm start serves, from the same process and port as the API, a marketing page at / and a browser app at /app. In production the landing page is https://clearexchange.ai and the app is https://app.clearexchange.ai (at its root), with links rewritten to match; see Hostnames. The app is for demos and for understanding the flow. It uses the public API like any client.
- Agent console (
/app#agent). Type a question, set the max price, usage tier, limit and (under Advanced) agent ID, minimum relevance and licence filters (commercial use, exclude share-alike, reusable across end users, cacheable, no credit required). An optional Agent API key field (under Advanced) sendsX-Agent-Key, so the console can act as an authenticated agent. Offers come back as a ruled list with publisher, title, snippet, all-in price, the key licence terms as chips with their one-line summary (see Licence terms) and a relevance meter; a note, a filter funnel, the routing panel and the band note explain that order comes from relevance, the max price only excludes, and the cheapest offer inside the relevance band is selected (it gets the only filled Accept button). Each offer shows its published and updated dates with their age ("Undated" if the source gives none); when the question asks for a period ("this month", "latest", "2009") a note says so and each offer carries a Recent, Stale, Updated recently or Undated badge whose tooltip gives the relevance adjustment. When nothing clears the 0.5 floor and the near-top band, a "No good match" callout says why instead of a list. The minimum relevance slider starts at the exchange's 0.5 floor and can only raise it. Accept an offer to see the signed entitlement (raw token, decoded claims,cxscope, expiry countdown) and verify it for its own content, for a wider tier (refused) and as a tampered copy (bad signature). Retrieve the content with and without the token, then report ground, cite, present and engage as a SPUR batch. A side panel lists every API call the page made, with request and response. - Operator (
/app#operator). Totals (paid retrievals, gross, clearing fee, publisher net, telemetry events), totals per publisher, ledger entries, the telemetry stream (filterable by entitlement), an Agent keys card (issue a key with a label, budget and rate limit, shown once; list spend and remaining budget; revoke), ledger rows that show whether the buyer was verified, and supply adapters with their live or stub status and health. - Coverage (
/coverage). Verticals, publishers, item counts, date ranges and base prices per tier, with demo supply labelled. Rendered once at startup from the same data asGET /v1/coverage, as static HTML with no script. See Demo supply. - Publishers (
/app#publishers). Per publisher: earnings (gross and net of the fee) and the rate card (tier prices, default terms, rules and raw JSON). Read-only.
The operator and publisher views read /v1/admin/*. If ADMIN_TOKEN is set, the UI asks for it, keeps it in sessionStorage (that browser tab only), sends it as a Bearer header on admin calls and never logs it.
The operator view is a demo convenience, not a hardened admin console. A strict CSP and textContent-only rendering limit what injected content could do, but any script that did run in the page could read a token held in sessionStorage. There are no per-user accounts, roles or audit trail. Use it on trusted machines, and rotate ADMIN_TOKEN if you think it has leaked.
How it is built
Plain ES modules and CSS in web/, with no framework, bundler, build step or dependencies:
| File | Role |
|---|---|
index.html | Marketing page (no script) |
app.html, app.js | The app: DOM and API calls |
lib.js | Pure helpers, unit-tested under node:test |
tokens.css | Every colour, font, size, spacing step, radius and shadow, as CSS variables |
fonts/ | The self-hosted headline serif (Newsreader, two WOFF2 files) and its licence (OFL.txt) |
base.css | Shared primitives: wordmark, buttons, badges, focus ring |
site.css, styles.css | Marketing and app layout |
favicon.svg | The mark, used as favicon (the wordmark is the name set in the headline serif) |
docs.css, docs.js | The documentation site's layout, search and copy buttons (pages are rendered from docs/; see Docs site) |
src/http/static.tsserves a fixed allowlist of these files, read once at startup and held in memory: no per-request filesystem access and no traversal surface. A missing file stops startup. Restart the server to pick up edits (npm run devwatches only the TypeScript modules).- Responses carry a strict Content-Security-Policy (this origin only for scripts, styles, images and fonts, no inline script or style, no framing),
nosniffand ETags. app.jsrenders every server value withtextContent, neverinnerHTML.test/ui.test.tsenforces this, checks the page has no inline scripts, checks every element ID the script uses exists, and checks there are no raw colours outsidetokens.css.app.jsandlib.jscarry JSDoc types checked bytsc -p web/tsconfig.json(part ofnpm run typecheck).- To rebrand, change
tokens.cssandfavicon.svg. The identity is editorial: warm paper and ink neutrals with one oxblood accent, hairline and heavy rules instead of cards and shadows, near-square corners, headlines in Newsreader (SIL OFL 1.1, about 48 KB, self-hosted under/ui/fonts/so the CSP only needsfont-src 'self'), the system sans for UI and the system mono for data. AA contrast in light and dark mode; a test checks the main light-theme pairs, and another rejects gradients, blur, gradient text and hype words on the pages. - The landing page's worked example is a static snapshot of a run against the seeded catalogue. Update it by hand if prices, the fee or the catalogue change.
createHttpServer(exchange, { ui: false })gives an API-only server.
Why no framework: it would add hundreds of packages and a build pipeline for a UI of a few screens, and in the planned confidential VM everything in the image becomes part of the attested measurement.
Copy
All public copy leads with what the service gives agent builders (one endpoint for relevant, licensed, citable content with clear terms and predictable prices), then publishers. Describe the service; do not moralise or frame it negatively. Anything not built stays marked as roadmap in the status table.