{
  "name": "UXXRAY API",
  "version": "v1",
  "build": "3ac28c25ce8c",
  "description": "X-ray any funnel into measured facts — design tokens, proof language, and the exact conversion tactics it uses. Deterministic Evidence Coverage Score, zero LLM interpretation.",
  "base_url": "https://api.uxxray.com/api/v1",
  "endpoints": [
    {
      "method": "GET",
      "path": "/api/v1",
      "summary": "This self-describing API root — endpoint catalog, corpus counts, links"
    },
    {
      "method": "GET",
      "path": "/api/v1/openapi.json",
      "summary": "OpenAPI 3.1 document for this API"
    },
    {
      "method": "GET",
      "path": "/api/v1/league",
      "summary": "Ranked list of teardowns (?funnel_type=, ?limit=, ?offset=). Paginated: a full page does NOT mean the whole corpus — check the x-uxxray-has-more response header and follow Link rel=\"next\" until it reads false"
    },
    {
      "method": "GET",
      "path": "/api/v1/corpus/stats",
      "summary": "Corpus-wide aggregates (pct_lack, avg_tactics, evidence)"
    },
    {
      "method": "GET",
      "path": "/api/v1/patterns",
      "summary": "The Conversion Pattern Library — every detected tactic, prevalence, quoted evidence, examples (?funnel_type=)"
    },
    {
      "method": "GET",
      "path": "/api/v1/patterns/{pattern_id}",
      "summary": "One conversion pattern, machine-readable — up to 60 verbatim on-page examples"
    },
    {
      "method": "GET",
      "path": "/api/v1/templates",
      "summary": "Page Template Library — recurring section-skeletons real pages are built from, clustered by exact structure"
    },
    {
      "method": "GET",
      "path": "/api/v1/sections",
      "summary": "Section-archetype prevalence across fully-crawled pages — how common each band is, where it typically sits, example sites; ?archetype=X returns every real instance with screenshot crop coords (the SECTION layer)"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/index",
      "summary": "THE WHOLE SHELF — every facet, every legal value, real counts, no specimens. Call this FIRST. The inventory is lumpy (hero is in the hundreds, stat_proof is single digits) and these counts are how you find that out before asking for 20 and getting 7"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/specimens",
      "summary": "FACETED SPECIMEN RETRIEVAL — you choose the breadth. scope=narrow (5, full receipts incl. verbatim evidence) | broad (20, the band's cropped image + tactic ids — the DEFAULT) | full (the whole roster for the facet: identity + score only). Select on ONE axis: kind (+archetype/template) OR playbook; funnel_type filters either. Every response publishes how many exist, so \"showing 7 of 7 available\" can never be misread as a page of many. exclude_seen_project stops a later round re-serving what you already built from. A ranked response is ONE specimen per domain by default (score is funnel-scoped, so a 4-page walk would otherwise take 4 slots) and does not rank bands with no possible crop; both reductions are disclosed and per_domain= lifts the cap"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/resolve",
      "summary": "DESIGN TASK → CORPUS VOCABULARY. \"build_review_section\" → the section archetypes, measured patterns and ready-made queries to use. Exact-match lookup: an unsupported task is reported as unsupported, never guessed"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/sections",
      "summary": "SECTION SPECIMENS with receipts — real bands from real funnels, each with crop_url (a real ~1440px webp of the band, or null with a stated reason) plus crop coords onto its full-page screenshot, the funnel's measured /100, and the verbatim on-page evidence for every tactic it runs. Ranked by score. The crops are cut at BUILD time over the listed corpus; scanning a site does not produce them (see steps[].crops_note on a bundle)"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/elements",
      "summary": "ELEMENT SPECIMENS — the atomic UI pieces real funnels are built from (price anchors, ratings, guarantees, no-card notes, countdowns), each a REAL CROPPED IMAGE cut at BUILD time from the funnel's own full-page screenshot, plus the verbatim on-page text of that element. Scanning a site does not produce this material: a walk cuts at most two regions per step and they are a different tier (see steps[].crops_note on a bundle). Unlike section receipts, the text is element-scoped: the detector recorded the rect and the string from the same DOM node on the same crawl. Ranked by the funnel's measured /100"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/pages",
      "summary": "PAGE SPECIMENS — one real crawled page per specimen, grouped by the MEASURED section sequence it shares with others (the shape). Carries the above-the-fold crop, the whole-page screenshot, the section skeleton the shape was clustered on, and the funnel's measured /100. Ranked by score"
    },
    {
      "method": "POST",
      "path": "/api/v1/visitors",
      "summary": "Issue a VISITOR CAPABILITY TOKEN — the continuity layer. It powers YOUR work (factory projects, likes) and is deliberately NOT an analytics identity: view counts deduplicate on a day-scoped IP hash and never read it. Returned ONCE and stored only as a SHA-256 hash, so it cannot be re-read. Send it back as the x-uxxray-visitor header — a header rather than a cookie so agents, which hold config but not cookies, use the same primitive as browsers"
    },
    {
      "method": "GET",
      "path": "/api/v1/visitors/me",
      "summary": "Your factory projects, resolved from the x-uxxray-visitor header. Returns a completeness note stating what is NOT listed — work from another device, from before the token existed, or created after clearing it is unreachable here and recoverable only by its own review link. Claiming full history would be a fabrication"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/engagement",
      "summary": "Batch engagement counts for the specimens on one page. Fetched at RUNTIME and never baked into the static build, because a build-time number is a claim about when the page was generated dressed up as a current one. A ref with no row is OMITTED rather than returned as 0 — never counted and measured-zero are different facts. Carries the definition of the number"
    },
    {
      "method": "POST",
      "path": "/api/v1/library/engagement",
      "summary": "Record ONE deduped engagement event for a specimen. Deduplicated per visitor per day on a one-way, day-scoped IP hash — no cookie, no device storage, no identity. Agent visits COUNT (this is an agent-oriented product); 2000 pings in five seconds count once. Build, canary and internal traffic are excluded. The response carries the exact definition of the number, and returns recorded:false when the visitor was already counted today — that is a normal outcome, not an error"
    },
    {
      "method": "PATCH",
      "path": "/api/v1/factory/sessions/{session_id}",
      "summary": "Change a session's visibility and/or record sources discovered mid-work (#177) — body needs privacy, source_specimens, or both. REQUIRES the write credential (x-uxxray-session-token header): editing is an owner action, and the review link alone must not authorise it. private means UNLISTED, not access-controlled — the id is still a capability. source_specimens APPENDS to what is already stored, it never replaces — a worker that discovers a specimen it used in round 2 does not need to resend round 1's list"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/search",
      "summary": "SPECIMEN SEARCH — free text resolved into explicit MEASURED filters (archetype, tactic, funnel type, min score), then real specimens with receipts. Deterministic, not semantic: any term it does not understand is REPORTED as unmatched rather than silently ignored"
    },
    {
      "method": "GET",
      "path": "/api/v1/library/brief",
      "summary": "THE INSPIRATION BRIEF — a SHORTLIST for one design task: resolved taxonomy, 2-3 selected specimens with receipts, what to read them for, and the originality instruction. Deterministic, so asking twice returns the same specimens; the response names its own pool size and links the full roster. For the whole library start at /api/v1/library/index instead"
    },
    {
      "method": "GET",
      "path": "/api/v1/playbooks",
      "summary": "Funnel-playbook registry + which crawled funnels match each (measured surface matches, quoted evidence; bundles carry per-funnel matches as bundle.playbooks)"
    },
    {
      "method": "GET",
      "path": "/api/v1/playbooks/{id}",
      "summary": "One funnel playbook, machine-readable: model summary + cheat sheet + how to unlock the prompt templates"
    },
    {
      "method": "GET",
      "path": "/api/v1/curriculum",
      "summary": "THE AGENT STUDY PATH (markdown) — how to learn conversion design from this corpus: top scorers → bundles → screenshots → playbook → page → section → copy"
    },
    {
      "method": "POST",
      "path": "/api/v1/rate",
      "summary": "★ Rate any result 1-5 — body {rating, context?, via: \"user\"(relayed human)|\"agent\", comment?}. Terminal responses carry the ask as _rate"
    },
    {
      "method": "GET",
      "path": "/api/v1/rate",
      "summary": "Rating summary — overall count/avg + breakdown by context"
    },
    {
      "method": "POST",
      "path": "/api/v1/scan-request",
      "summary": "Request a new site be scanned (queued for manual/batch capture) — body {url, email?}"
    },
    {
      "method": "POST",
      "path": "/api/v1/scan",
      "summary": "Live on-demand x-ray — real Browser Rendering capture, async. Body {url, mode?, steps?, maxSteps?, force?}. mode=\"auto\" (default) auto-discovers the funnel (entry → pricing/product → signup/checkout); \"single\" scans just the entry page; \"defined\" walks the exact same-domain URLs in steps[]. maxSteps caps auto/defined (default 4, max 6). Returns 202 {jobId,status} or 200 {status:done,slug} if cached within 24h; {\"force\":true} bypasses the cache."
    },
    {
      "method": "GET",
      "path": "/api/v1/scan/{jobId}",
      "summary": "Poll a live scan job — {status, slug?, score?, band?, blocked?, error?}"
    },
    {
      "method": "GET",
      "path": "/api/v1/vs/{a}/{b}",
      "summary": "Deterministic side-by-side compare page (HTML) for two teardowns"
    },
    {
      "method": "POST",
      "path": "/api/v1/playbooks/unlock",
      "summary": "Unlock a playbook's full prompt templates — body {email, playbook, source?}; returns {playbook:{prompts_md, cheat_md}}"
    },
    {
      "method": "POST",
      "path": "/api/v1/playbooks/feedback",
      "summary": "Report whether a playbook was useful — body {playbook, useful, comment?, email?, via?}"
    },
    {
      "method": "GET",
      "path": "/api/v1/factory/sessions",
      "summary": "List Design Factory sessions. Anonymous callers see ONLY sessions explicitly created with privacy:\"public\" — private sessions are reachable solely by someone who already holds the id"
    },
    {
      "method": "POST",
      "path": "/api/v1/factory/sessions",
      "summary": "Create a Design Factory session — PRIVATE by default; returns a write credential ONCE that is required to register concepts (header x-uxxray-session-token). The session_id alone is the review link: safe to hand a human, allows reading and feedback, not writing. Create — body {task, project?, target?, source_specimens?, agent?, privacy?}; external AI agents register concepts into it"
    },
    {
      "method": "GET",
      "path": "/api/v1/factory/sessions/{session_id}",
      "summary": "Get one Design Factory session, its registered concepts, latest feedback, and agent contract"
    },
    {
      "method": "POST",
      "path": "/api/v1/factory/sessions/{session_id}/concepts",
      "summary": "Register an original concept into a session. REQUIRES the write credential from session creation, sent as the x-uxxray-session-token header; the review link alone will not authorise it. artifact must carry non-empty html or css"
    },
    {
      "method": "GET",
      "path": "/api/v1/factory/sessions/{session_id}/feedback",
      "summary": "Get latest session-scoped feedback as both raw verdicts and an agent-ready refinement summary"
    },
    {
      "method": "POST",
      "path": "/api/v1/factory/sessions/{session_id}/feedback",
      "summary": "Record session-scoped concept feedback — body {concept_id, starred?, rating?, comment?, tags?}"
    },
    {
      "method": "PATCH",
      "path": "/api/v1/factory/sessions/{session_id}/concepts/{concept_id}",
      "summary": "Commit a round's winner (#159 PART B) — body {status:\"selected\"}. REQUIRES the write credential (x-uxxray-session-token header), same as concept registration: the review link alone must not decide a session. Marks this concept selected, archives every OTHER direction's root, stamps the session's decided_concept_id/decided_at, and returns round_two_seed — the winner's brief + comment, every REJECTED concept's comment labelled as such, the source specimens already used, and both round-two branches (evolve the winner vs fresh alternatives). The human chooses the branch, never the agent"
    },
    {
      "method": "GET",
      "path": "/api/v1/factory/sessions/{session_id}/concepts/{concept_id}/artifact",
      "summary": "Get the portable artifact bundle for one generated concept: HTML/CSS/tokens/adapters/source provenance when registered"
    },
    {
      "method": "POST",
      "path": "/api/v1/report/dispute",
      "summary": "Report a tactic we missed on a report — body {slug, tactic_id, url?, note?, email?, via?}; logged for detector review, not auto-applied"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}",
      "summary": "Full teardown — all device variants, score, funnel steps"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/bundle",
      "summary": "One self-contained JSON with everything for one site (?include=, ?exclude=) — the recommended single fetch for agents"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/tokens",
      "summary": "Design tokens (?format=json|w3c|tailwind|css). The json form carries the five-slot `tokens` export UNCHANGED plus a `design` object holding what those slots were derived from: the full palette ordered by usage with usage_pct, weighted ink candidates, the real type scale and weights, EVERY radius, and the measured roles with any collisions NAMED rather than resolved by inventing a colour. Page-scoped. No spacing field — nothing measures spacing"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/brief",
      "summary": "Machine-generated build-brief markdown"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/favicon",
      "summary": "Favicon image, streamed from R2"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/report",
      "summary": "Pre-rendered shareable report HTML (self-heals from D1/R2 if not yet rendered)"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/og.png",
      "summary": "Branded 1200x630 social card image, composed on demand from the latest hero screenshot"
    },
    {
      "method": "GET",
      "path": "/api/v1/teardown/{slug}/screenshot/{path}",
      "summary": "One step screenshot/crop, streamed from R2 (e.g. path=\"01-home/hero.png\")"
    }
  ],
  "counts": {
    "teardowns": 1777,
    "scanned_domains": 1777
  },
  "links": {
    "league": "https://api.uxxray.com/api/v1/league",
    "openapi": "https://api.uxxray.com/api/v1/openapi.json",
    "llms": "https://uxxray.com/llms.txt",
    "mcp": "https://api.uxxray.com/mcp"
  },
  "note": "0-hallucination — every field is a measured fact or a deterministic score; no LLM interpretation. See provenance/source_type fields on /bundle."
}