> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CreatePageReviewAPI mirrors erdo_review_pages: start a review of one or more


> external page URLs with the real landing critic. Returns a review id immediately (status=pending); poll GET /v1/page-reviews/:reviewID for the typed findings.




## OpenAPI

````yaml /api/openapi.json post /v1/page-reviews
openapi: 3.0.0
info:
  title: Erdo API
  description: >-
    Erdo's REST API: query and write datasets, run agents, manage threads,
    integrations, pages, evals, workstreams, and experiments. Authenticate with
    a Bearer API key (erdo_api_...) or scoped token (erdo_token_...).
  version: '2026-07-25'
servers:
  - url: https://api.erdo.ai
    description: Production
security:
  - bearerAuth: []
paths:
  /v1/page-reviews:
    post:
      summary: >
        CreatePageReviewAPI mirrors erdo_review_pages: start a review of one or
        more
      description: >
        external page URLs with the real landing critic. Returns a review id
        immediately (status=pending); poll GET /v1/page-reviews/:reviewID for
        the typed findings.
      operationId: POST:mcp.CreatePageReviewAPI
      requestBody:
        content:
          application/json:
            schema:
              properties:
                audience:
                  type: string
                goal:
                  type: string
                urls:
                  items:
                    type: string
                  type: array
              type: object
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  audience:
                    description: |
                      the create request. Empty when none was declared.
                    title: >
                      Audience is the operator's declared target audience (ICP),
                      echoed back from
                    type: string
                  created_at:
                    format: date-time
                    type: string
                  error:
                    title: >
                      Error is the whole-review failure reason, set only when
                      status='failed'.
                    type: string
                  goal:
                    description: >
                      back from the create request. Empty when none was
                      declared.
                    title: >
                      Goal is the operator's declared campaign goal for the
                      reviewed pages, echoed
                    type: string
                  id:
                    format: uuid
                    type: string
                  input_urls:
                    items:
                      type: string
                    type: array
                  report:
                    $ref: '#/components/schemas/types.PageReviewReport'
                  status:
                    type: string
                  updated_at:
                    format: date-time
                    type: string
                type: object
          description: Success response
        default:
          $ref: '#/components/responses/APIError'
components:
  schemas:
    types.PageReviewReport:
      description: |
        submitted URL. Version guards the shape as the report grows.
      properties:
        category_scores:
          allOf:
            - $ref: '#/components/schemas/types.PageReviewCategoryScores'
          description: |
            scored pages. Nil when no page scored.
          title: >
            CategoryScores is the rounded mean of the per-page category scores
            across
        pages:
          items:
            $ref: '#/components/schemas/types.PageReviewPage'
          type: array
        score:
          description: >
            per-page scores across pages that scored (a critic-error page has
            none). Nil when no page scored. Deterministic and persisted at
            report time, never recomputed on read.
          format: int64
          title: >
            Score is the review's overall conversion score — the rounded MEAN of
            the
          type: integer
        version:
          format: int64
          type: integer
      title: >
        PageReviewReport is the persisted, typed result of a review — one entry
        per
      type: object
    types.PageReviewCategoryScores:
      description: >
        corpus's four principle families — the same CLARITY / CREDIBILITY /
        ACTION / FRICTION sections the critic's findings are grounded in — so a
        low sub-score points at exactly which family of principles the page is
        losing on. Each is the deterministic conversion score (100 minus
        per-severity costs, floored at 5) computed over ONLY that family's
        defects; a family with no defects scores 100. Defects the critic did not
        classify count toward the overall score, never a guessed family.
      properties:
        action:
          format: int64
          type: integer
        clarity:
          format: int64
          type: integer
        credibility:
          format: int64
          type: integer
        friction:
          format: int64
          type: integer
      title: |
        PageReviewCategoryScores breaks the conversion score into the shared CRO
      type: object
    types.PageReviewPage:
      description: >
        (and Strengths/Defects empty) when that URL could not be captured or
        reviewed — a single unreachable page never fails the whole review; it
        reports its own reason here while the other pages still land findings.
      properties:
        captures:
          description: >
            one per viewport (desktop-fold, desktop-full, mobile-fold,
            mobile-full). The report reader (a UI) needs to see what the critic
            saw and anchor findings to the capture they cite. Each URL is signed
            at GET time and expires (~1h); empty on capture-failure pages and
            reports predating this field.
          items:
            $ref: '#/components/schemas/types.PageReviewCapture'
          title: >
            Captures are the persisted screenshots the critic judged this page
            from —
          type: array
        category_scores:
          allOf:
            - $ref: '#/components/schemas/types.PageReviewCategoryScores'
          description: >
            (clarity, credibility, action, friction). Set alongside Score, nil
            on an error page.
          title: >
            CategoryScores is the same conversion score scoped to each principle
            family
        context:
          description: >
            to whom, and at what stage — the frame its findings are judged
            relative to. Surfacing it lets the report reader see (and challenge)
            the premise behind every finding. Empty on error pages and reports
            predating it.
          title: >
            Context is the critic's committed reading of what this page is
            selling,
          type: string
        defects:
          items:
            $ref: '#/components/schemas/types.PageCriticDefect'
          type: array
        error:
          type: string
        lighthouse:
          allOf:
            - $ref: '#/components/schemas/types.PageReviewLighthouse'
          description: >
            Insights (mobile). Optional — absent when PSI was unavailable or
            timed out.
          title: |
            Lighthouse is the page's real Google Lighthouse scores via PageSpeed
        score:
          description: >
            per defect by severity, floored at 5; a page with no defects scores
            100). Set on the success path, nil on an error page (nothing was
            reviewed). Persisted at report time — never recomputed on read.
          format: int64
          title: >
            Score is this page's deterministic conversion score (100 minus a
            fixed cost
          type: integer
        strengths:
          items:
            type: string
          type: array
        url:
          type: string
      title: >
        PageReviewPage is the critic's findings for ONE reviewed URL. Error is
        set
      type: object
    types.PageReviewCapture:
      description: >
        is the stable reference (thread-images bucket); URL is a signed,
        expiring (~1h) serving URL stamped on each GET — never persisted,
        re-fetch the review for a fresh one.
      properties:
        bucket_key:
          type: string
        label:
          title: |
            desktop-fold | desktop-full | mobile-fold | mobile-full
          type: string
        media_type:
          type: string
        url:
          type: string
      title: >
        PageReviewCapture is one persisted screenshot of a reviewed page.
        BucketKey
      type: object
    types.PageCriticDefect:
      description: >
        explainability contract Windtunnel renders and later calibrates against.
        A claim without its reasoning and observable evidence is an opinion
        anyone could offer about any page; the parts make it a discriminator's
        finding, not a designer's opinion:

          - Issue    — the claim: what is wrong, naming the specific element.
          - Basis    — how strongly the violated principle is known, exactly as the shared corpus marks it: measured\_prior (a real experiment corpus with a number), replicated\_finding (observed repeatedly across published CRO practice), or craft\_prior (defensible reasoning the experiment loop settles). DECLARED as a field so a UI can badge it and a calibration loop can weigh it — never derived by scanning the prose, and never restated as a prose prefix ("CLARITY principle, craft prior: …" made every justification read as boilerplate).
          - Because  — the reasoning, as natural prose: the specific conversion principle it violates and why it costs THIS page conversions, carrying the measured prior's number when there is one. Never a restatement of the issue, and never a citation the corpus does not contain — the justification is what makes a finding trustworthy, so it must be real.
          - Evidence — the ONE observation the claim hinges on: the element or quoted copy and the viewport it is seen at. Minimal by design — the captures render beside the finding, so evidence locates and falsifies, it does not narrate the page.
          - Fix      — the MINIMAL change the evidence entails. Empty string when no fix is obvious: the critic is a discriminator, not a designer, and must not invent a redesign the evidence does not dictate.
      properties:
        basis:
          title: >
            measured_prior | replicated_finding | craft_prior; empty on
            pre-basis reports
          type: string
        because:
          type: string
        category:
          description: >
            violated principle belongs to — clarity | credibility | action |
            friction, the corpus's own section headers. DECLARED by the critic
            (like Viewports), never derived by scanning the prose, so a
            per-category score is exactly "which findings, in which family".
            Empty when the critic did not classify it; such a defect counts
            toward the overall score only, never a guessed category.
          title: >
            Category is which of the shared CRO corpus's four principle families
            the
          type: string
        evidence:
          type: string
        fix:
          type: string
        issue:
          type: string
        severity:
          title: |
            high | medium | low
          type: string
        viewports:
          description: >
            mobile-full) where this defect is observable — DECLARED by the
            critic, never derived by scanning the prose. A UI uses them to place
            the finding next to the capture it cites.
          items:
            type: string
          title: >
            Viewports are the capture labels (desktop-fold, desktop-full,
            mobile-fold,
          type: array
      title: >
        PageCriticDefect is one conversion defect the critic found on a page, in
        the
      type: object
    types.PageReviewLighthouse:
      description: >
        the PageSpeed Insights API on the mobile strategy. Each is 0..100
        (Lighthouse's own 0..1 category score ×100, rounded). Optional by
        design: absent when PSI is unavailable, rate-limited, or times out — a
        missing Lighthouse never fails the review. FetchedAt stamps when the
        measurement was taken. Each category is a POINTER because Lighthouse
        itself reports null for a category whose lab run failed (observed live:
        performance=null on a page the other three categories scored fine) — a
        non-pointer int rendered that null as a fake catastrophic 0. An absent
        category is absent, never zero.
      properties:
        accessibility:
          format: int64
          type: integer
        best_practices:
          format: int64
          type: integer
        fetched_at:
          format: date-time
          type: string
        performance:
          format: int64
          type: integer
        seo:
          format: int64
          type: integer
      title: >
        PageReviewLighthouse is the page's real Google Lighthouse scores,
        fetched from
      type: object
  responses:
    APIError:
      content:
        application/json:
          schema:
            externalDocs:
              url: https://pkg.go.dev/encore.dev/beta/errs#Error
            properties:
              code:
                description: Error code
                example: not_found
                externalDocs:
                  url: https://pkg.go.dev/encore.dev/beta/errs#ErrCode
                type: string
              details:
                description: Error details
                type: object
              message:
                description: Error message
                type: string
            title: APIError
            type: object
      description: Error response
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).

````