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

# Preview exact saved page improvements

> Returns bounded original and proposed content from the exact frozen source and checked proposal, or saved full-rewrite revisions. No current-Page fallback, execution or publication occurs. Missing historical provenance returns unavailable.



## OpenAPI

````yaml https://www.getsurfaced.ai/api/v1/openapi.json get /autopilot/plans/{planId}/page-improvement-preview
openapi: 3.1.0
info:
  title: Surfaced API
  version: 1.1.0
  description: >-
    > **Using an AI assistant?** Connect Claude, Cursor or VS Code to Surfaced
    without writing code: [Connect an AI
    assistant](https://www.getsurfaced.ai/docs/mcp).


    Programmatic access to Surfaced: Insights (analytics, prompts, topics,
    sources, competitors, tracking runs, the audit report), Actions, Content
    (Briefs, their runs, Articles, publishing, the blog), Knowledge, Settings
    (including the Content Agent) and Workspace (the workspace, its brand,
    members and access), plus jobs and webhooks.



    ## Quick start

    1. `GET /api/v1/me` — verify your key and see your workspaces

    2. `GET /api/v1/briefs` — list Content Briefs; one with `status` `draft` is
    ready to write

    3. `POST /api/v1/briefs/{id}/run` — start a Brief's content run

    4. `GET /api/v1/jobs/{id}` — poll until the status is `completed` or
    `failed`, with a timeout, then read the result with `GET
    /api/v1/briefs/{id}/run`


    All endpoints except `/account`, `/account/api-keys`,
    `/account/api-keys/{id}`, `/me`, `/workspace/join-requests`,
    `/workspace/join-requests/{id}/approve`,
    `/workspace/join-requests/{id}/reject` are workspace-scoped and require the
    `X-Workspace-Id` header.


    ## Errors

    Every error is `{ "error": { "code", "message", "details"? } }`; `code` is a
    closed set and fixes the HTTP status. 5xx bodies never carry the cause: the
    message is always `Internal server error`.


    ## Rate limits

    Workspace-scoped requests have a per-minute allowance per key set by the
    workspace plan; `GET /me` and the other account-level routes use a fixed 100
    per minute. Operations that start runs or other long or paid work, and the
    heaviest analytics reads, have an additional, stricter ceiling. Over the
    limit the API returns 429 `RATE_LIMITED`. Successful and rejected responses
    carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.
    A plan without API access gets 402 `BILLING_LIMIT`.


    ## Deprecations

    A deprecated operation is marked `deprecated: true`, with its removal date
    in `x-sunset`, and keeps working until then. Its responses carry the
    `Deprecation` and `Sunset` headers, a `Link` header naming the replacement
    (`rel="successor-version"`) and the migration guide (`rel="deprecation"`),
    and JSON bodies get a `warnings` array with a `DEPRECATED` entry that names
    the replacement. Before the sunset it can answer 410 `ENDPOINT_REMOVED`
    during short scheduled brownouts. Fields renamed from snake_case to
    camelCase keep their old name as an alias marked `deprecated`, with the same
    value as the camelCase field until the sunset; the response body names them
    in a `warnings` entry with code `DEPRECATED_FIELDS`. An `IGNORED_FIELDS`
    entry names request fields that were ignored and will be rejected after the
    sunset; a `PARTIAL_DATA` entry says some fields are null because they could
    not be read.


    ## Migrating to the canonical API


    The canonical routes launched on **2026-10-13**. The older routes that
    launch deprecates keep working until **2027-01-18**, then stop. Until then
    they answer normally and add `Deprecation` and `Sunset` headers, a `Link`
    header pointing at the replacement, and a `warnings` array in the JSON body.
    Routes may also be unavailable for short scheduled windows before the
    sunset; those answer 410 `ENDPOINT_REMOVED`.


    Some routes were deprecated before the launch. They keep their own removal
    date where one was announced earlier; the others stop working with the rest,
    on the date in the Sunset column.


    ### What changed in vocabulary


    | Before | Now | Meaning |

    | --- | --- | --- |

    | pages | articles | A page is now called an article. |

    | ideas | briefs | An idea is now called a brief. |

    | autopilot | content agent | Autopilot is now the content agent. |

    | engine settings | settings | Engine settings live under settings. |

    | run | run a brief | Starting a run is an action on a brief. |


    Where the old resources went:


    - `/workspace` stays; some of its routes moved to `/settings`.

    - `/tracking` is split into `/analytics`, `/workspace`, `/tracking-runs`,
    `/prompts` and `/topics`.

    - `/ideas` is now `/briefs`.

    - `/pages` is now `/articles`.

    - `/runs` is now `/briefs`.

    - `/autopilot` is now `/content-agent`.

    - `/engine` is now `/settings`.


    ### Route changes


    Paths are relative to `https://www.getsurfaced.ai/api/v1`, grouped by the
    new resource. Every old route below stops working on **2027-01-18** unless
    its note says otherwise. Path parameters keep their value; the notes say
    where one is renamed.


    **`/organization`**


    | Old route | New route |

    | --- | --- |

    | `PATCH /organization/members/{id}/role` | `PATCH
    /organization/members/{id}` |

    | `POST /organization/members/invite` | `POST /organization/members` |


    **`/workspace`**


    | Old route | New route |

    | --- | --- |

    | `PATCH /workspace/access` | `PUT /workspace/access` |

    | `PATCH /workspace/join-requests/{id}` | `POST
    /workspace/join-requests/{id}/approve` |

    | `POST /workspace/members/invite` | `POST /workspace/members` |

    | `PATCH /workspace/settings` | `PATCH /workspace` |

    | `GET /tracking/projects` | `GET /workspace` |

    | `POST /tracking/projects` | `PATCH /workspace` |

    | `GET /tracking/projects/{id}` | `GET /workspace` |


    **`/settings`**


    | Old route | New route |

    | --- | --- |

    | `GET /workspace/tracking-llms` | `GET /settings/tracking-models` |

    | `POST /workspace/tracking-llms` | `PUT /settings/tracking-models` |

    | `DELETE /workspace/tracking-llms/{slug}` | `PUT /settings/tracking-models`
    |

    | `GET /engine` | `GET /settings` |

    | `GET /engine/brand` | `GET /settings/brand-voice` |

    | `PATCH /engine/brand` | `PATCH /settings/brand-voice` |

    | `GET /engine/categories` | `GET /settings/audiences` |

    | `POST /engine/categories` | `PUT /settings/audiences` |

    | `DELETE /engine/categories/{id}` | `PUT /settings/audiences` |

    | `GET /engine/cultural` | `GET /settings/markets` |

    | `PATCH /engine/cultural` | `PUT /settings/markets` |

    | `GET /engine/personas` | `GET /settings/audiences` |

    | `POST /engine/personas` | `PUT /settings/audiences` |

    | `DELETE /engine/personas/{id}` | `PUT /settings/audiences` |

    | `GET /engine/seo` | `GET /settings/seo` |

    | `PATCH /engine/seo` | `PATCH /settings/seo` |


    - `DELETE /workspace/tracking-llms/{slug}`: The new route needs an id this
    one does not carry: look it up on the collection first.

    - `DELETE /engine/categories/{id}`: The new route needs an id this one does
    not carry: look it up on the collection first.

    - `DELETE /engine/personas/{id}`: The new route needs an id this one does
    not carry: look it up on the collection first.


    **`/analytics`**


    | Old route | New route |

    | --- | --- |

    | `GET /tracking/insights` | `GET /analytics/summary` |


    **`/tracking-runs`**


    | Old route | New route |

    | --- | --- |

    | `GET /tracking/projects/{id}/latest-run` | `GET /tracking-runs` |

    | `GET /tracking/runs` | `GET /tracking-runs` |

    | `POST /tracking/runs` | `POST /tracking-runs` |

    | `GET /tracking/runs/{id}` | `GET /tracking-runs/{id}` |

    | `POST /tracking/runs/{id}/cancel` | `POST /tracking-runs/{id}/cancel` |


    - `GET /tracking/projects/{id}/latest-run`: The new route needs an id this
    one does not carry: look it up on the collection first.


    **`/prompts`**


    | Old route | New route |

    | --- | --- |

    | `GET /tracking/projects/{id}/prompts` | `GET /prompts` |

    | `POST /tracking/projects/{id}/prompts` | `POST /prompts` |

    | `PATCH /tracking/projects/{id}/prompts/{promptId}` | `PATCH /prompts/{id}`
    |

    | `DELETE /tracking/projects/{id}/prompts/{promptId}` | `DELETE
    /prompts/{id}` |

    | `GET /tracking/prompts/{id}/topics` | `GET /prompts/{id}` |

    | `PUT /tracking/prompts/{id}/topics` | `PUT /prompts/{id}/topics` |


    - `PATCH /tracking/projects/{id}/prompts/{promptId}`: {id} takes the value
    of {promptId}.

    - `DELETE /tracking/projects/{id}/prompts/{promptId}`: {id} takes the value
    of {promptId}.


    **`/topics`**


    | Old route | New route |

    | --- | --- |

    | `GET /tracking/projects/{id}/topics` | `GET /topics` |

    | `POST /tracking/projects/{id}/topics` | `POST /topics` |

    | `PATCH /tracking/topics/{id}` | `PATCH /topics/{id}` |

    | `DELETE /tracking/topics/{id}` | `DELETE /topics/{id}` |


    **`/briefs`**


    | Old route | New route |

    | --- | --- |

    | `GET /ideas` | `GET /briefs` |

    | `POST /ideas` | `POST /briefs` |

    | `GET /ideas/{id}` | `GET /briefs/{id}` |

    | `PATCH /ideas/{id}` | `PATCH /briefs/{id}` |

    | `DELETE /ideas/{id}` | `DELETE /briefs/{id}` |

    | `POST /runs` | `GET /briefs` |

    | `GET /runs/{id}` | `GET /briefs` |

    | `GET /runs/{id}/items` | `GET /briefs` |

    | `GET /runs/{id}/items/{itemId}` | `GET /briefs` |


    - `POST /runs`: The new route needs an id this one does not carry: look it
    up on the collection first.

    - `GET /runs/{id}`: The new route needs an id this one does not carry: look
    it up on the collection first.

    - `GET /runs/{id}/items`: The new route needs an id this one does not carry:
    look it up on the collection first.

    - `GET /runs/{id}/items/{itemId}`: The new route needs an id this one does
    not carry: look it up on the collection first.


    **`/articles`**


    | Old route | New route |

    | --- | --- |

    | `GET /pages` | `GET /articles` |

    | `GET /pages/{id}` | `GET /articles/{id}` |

    | `PATCH /pages/{id}` | `PATCH /articles/{id}` |

    | `DELETE /pages/{id}` | `DELETE /articles/{id}` |

    | `GET /pages/{id}/fact-check` | `GET /articles/{id}/fact-check` |

    | `GET /pages/{id}/fact-check/dismissals` | `GET /articles/{id}/fact-check`
    |

    | `POST /pages/{id}/fact-check/dismissals` | `GET /articles/{id}/fact-check`
    |

    | `DELETE /pages/{id}/fact-check/dismissals/{issueId}` | `POST
    /articles/{id}/fact-check/issues/{issueId}/restore` |

    | `GET /pages/{id}/revisions` | `GET /articles/{id}/revisions` |

    | `POST /pages/{id}/revisions` | `POST /articles/{id}/revisions` |

    | `GET /pages/{id}/revisions/{revisionId}` | `GET
    /articles/{id}/revisions/{revisionId}` |

    | `POST /pages/{id}/revisions/{revisionId}` | `POST
    /articles/{id}/revisions/{revisionId}/restore` |


    **`/content-agent`**


    | Old route | New route |

    | --- | --- |

    | `GET /autopilot/approvals` | `GET /content-agent/approvals` |

    | `POST /autopilot/approvals/{decisionId}/review` | `POST
    /content-agent/approvals/{decisionId}/decide` |

    | `GET /autopilot/opportunities` | `GET /content-agent/opportunities` |

    | `GET /autopilot/plans` | `GET /content-agent/plans` |

    | `POST /autopilot/plans` | `POST /content-agent/plans` |

    | `GET /autopilot/plans/{planId}` | `GET /content-agent/plans/{planId}` |

    | `POST /autopilot/plans/{planId}/authorize` | `POST
    /content-agent/plans/{planId}/approve` |

    | `POST /autopilot/plans/{planId}/cancel` | `POST
    /content-agent/plans/{planId}/cancel` |

    | `GET /autopilot/plans/{planId}/page-action-review` | `GET
    /content-agent/plans/{planId}/changes` |

    | `POST /autopilot/plans/{planId}/page-action-review` | `POST
    /content-agent/plans/{planId}/changes` |

    | `GET /autopilot/plans/{planId}/page-improvement-preview` | `GET
    /content-agent/plans/{planId}/preview` |

    | `POST /autopilot/plans/{planId}/undo` | `POST
    /content-agent/plans/{planId}/undo` |

    | `GET /autopilot/policies` | `GET /content-agent/policy/history` |

    | `POST /autopilot/policies` | `PUT /content-agent/policy` |

    | `POST /autopilot/policies/{policyId}/kill` | `PUT /content-agent` |


    - `POST /autopilot/policies/{policyId}/kill`: The new route needs an id this
    one does not carry: look it up on the collection first.


    ### Webhook event renames


    Until **2027-01-18** every event is delivered under both its old and its new
    name, with an identical payload. A webhook subscribed to both names receives
    two deliveries, so subscribe to the new name only.


    | Old event | New event |

    | --- | --- |

    | `page.created` | `article.created` |

    | `page.ready` | `article.ready` |

    | `page.needs_changes` | `article.needs_changes` |

    | `page.published` | `article.published` |

    | `page.unpublished` | `article.unpublished` |

    | `page.archived` | `article.archived` |

    | `run.completed` | `brief.run.completed` |

    | `run.item.completed` | `brief.run.item.completed` |

    | `tracking.run.completed` | `tracking_run.completed` |


    ### Renamed fields


    These fields moved from snake_case to camelCase. Until **2027-01-18** a
    response carries both names with the same value (the old one is a deprecated
    alias, and the response adds a `DEPRECATED_FIELDS` warning), and a request
    accepts either name. Sending both names for one field with different values
    answers 422 `VALIDATION_ERROR`, except on an update, where the name that
    still carries the stored value yields to the one that changed it (so a body
    read with GET can be sent back edited). Read and send the new name.


    **Behaviour change:** on `POST /account/api-keys` the camelCase request
    fields `scopedWorkspaceIds` and `expiresAt` used to be ignored and now
    apply. An explicit `scopedWorkspaceIds: null` means unrestricted, subject to
    the calling key's bounds.


    **API key (`GET`/`POST /account/api-keys`)**


    | Old field | New field |

    | --- | --- |

    | `key_prefix` | `keyPrefix` |

    | `scoped_workspace_ids` | `scopedWorkspaceIds` |

    | `last_used_at` | `lastUsedAt` |

    | `expires_at` | `expiresAt` |

    | `created_at` | `createdAt` |


    **Create API key body (`POST /account/api-keys`)**


    | Old field | New field |

    | --- | --- |

    | `scoped_workspace_ids` | `scopedWorkspaceIds` |

    | `expires_at` | `expiresAt` |


    **Organization (`GET /organization`)**


    | Old field | New field |

    | --- | --- |

    | `created_at` | `createdAt` |


    **Join request (`GET /workspace/join-requests`)**


    | Old field | New field |

    | --- | --- |

    | `workspace_id` | `workspaceId` |

    | `user_id` | `userId` |

    | `requested_at` | `requestedAt` |

    | `reviewed_at` | `reviewedAt` |

    | `reviewed_by` | `reviewedBy` |

    | `requester.full_name` | `requester.fullName` |


    **Access policy (`GET /workspace/access`)**


    | Old field | New field |

    | --- | --- |

    | `allowed_domains` | `allowedDomains` |

    | `default_role` | `defaultRole` |


    **Job (`GET /jobs/{id}`)**


    | Old field | New field |

    | --- | --- |

    | `created_at` | `createdAt` |

    | `completed_at` | `completedAt` |


    **Webhook (`GET`/`POST /webhooks`)**


    | Old field | New field |

    | --- | --- |

    | `created_at` | `createdAt` |

    | `last_delivered_at` | `lastDeliveredAt` |

    | `failure_count` | `failureCount` |


    **Brief (`/briefs`)**


    | Old field | New field |

    | --- | --- |

    | `idea_text` | `title` |

    | `primary_keyword` | `keyword` |

    | `created_at` | `createdAt` |

    | `updated_at` | `updatedAt` |


    ### Replaced fields


    These legacy fields have no camelCase twin with the same value: the new
    representation moved, reshaped or dropped them. Until **2027-01-18** a
    response still carries each one with its old value (deprecated, with the
    same `DEPRECATED_FIELDS` warning). Read what replaces it.


    **Brief (`/briefs`)**


    | Old field | Read instead |

    | --- | --- |

    | `workspace_id` | Not part of the canonical Brief: it is the workspace the
    request's `X-Workspace-Id` names. |

    | `state` | The stored record state (`pending`, `generating`, `generated`,
    `failed`). Read `status` for the Brief's lifecycle and `run.state` for its
    Run. |

    | `input_tags` | Read `tags` (an empty list where this is null). |

    | `metadata` | Not part of the canonical Brief. The linked Gap's scope it
    holds is `gap.targetScope`. |

    | `source_suggestion_id` | Read `gap.suggestionId`. |

    | `source_gap_ids` | Read `gap.gapIds`. |

    | `article_structure` | Read `content.articleStructure`. |

    | `cultural_tone` | Read `content.culturalTone`. |

    | `formality` | Read `content.formality`. |

    | `address_formality` | Read `content.addressFormality`. |

    | `word_count_min` | Read `content.wordCount.min`. |

    | `word_count_target` | Read `content.wordCount.target`. |

    | `word_count_max` | Read `content.wordCount.max`. |

    | `required_sections` | Read `content.requiredSections` (an empty list where
    this is null). |

    | `block_preferences` | Read `content.blockPreferences`. |

    | `template_id` | Read `content.templateId`. |

    | `faq_position` | Read `content.faqPosition`. |

    | `cta` | Read `content.cta`. |

    | `target_regions` | Read `content.targetRegions` (an empty list where this
    is null). |

    | `target_markets` | Read `markets`, the Brief's saved markets and
    languages. |

    | `locale` | Read `markets`, the Brief's saved markets and languages. |

    | `locales` | Read `markets`, the Brief's saved markets and languages. |

    | `cultural_overrides` | Not part of the canonical Brief, and not writable
    through the API. |


    ### Brief behaviour changes


    - `Surfaced-Version` is ignored. Every `/briefs` response has one shape: the
    camelCase Brief plus the legacy keys. A client that sent `Surfaced-Version:
    2026-11-01` now gets it too, so read `status` and `run.state` (not `state`),
    `run` (not `progress`) and `markets` (not `targets`).

    - `GET /briefs`: any of `search`, `sort`, `direction`, `view`, `runState`,
    `intent` or `market` now selects the searchable list for every client;
    without them the list keeps its manual order and its `state` and `language`
    filters. The sort values `created_at`, `idea_text` and `primary_keyword` are
    now `createdAt`, `title` and `keyword` (the old ones answer 422 naming the
    new one). The Run filter is `runState`; `state` with a Run-only value such
    as `running` answers 422 naming it.

    - `POST /briefs` honours `Idempotency-Key`, which it used to ignore: a
    reused UUID key replays the first create, and is 409 `CONFLICT` for
    different Briefs. A key that is not a UUID, or one on more than 100 Briefs,
    is ignored with an `IGNORED_HEADER` warning.

    - `PATCH /briefs/{id}` checks each field's type, so a wrong type answers 422
    `VALIDATION_ERROR` instead of silently clearing the field (null still clears
    it), and answers 409 `CONFLICT` once the Brief's Run has started, because
    the edit would change the input of work already written or in progress;
    create a new Brief for a variation. Fields it does not know are still
    ignored, now with an `IGNORED_FIELDS` warning, and will be rejected after
    **2027-01-18**.

    - If a Brief's progress cannot be read, a response still carries every other
    field, with `status`, `locked` and `run` null and a `PARTIAL_DATA` warning.


    ## Connect an AI assistant (MCP)


    Surfaced runs an MCP server at `https://www.getsurfaced.ai/mcp` (streamable
    HTTP). Its tools are operations of this API: your workspaces and their
    brand, analytics, prompts, sources, competitors, tracking runs, the audit
    report, Actions, Briefs and their runs, Articles and publishing, the Content
    Agent and the knowledge base. Each tool call is an API request, with the
    same permissions, plan access and rate limits. The step-by-step guide for
    people who are not developers is [Connect an AI
    assistant](https://www.getsurfaced.ai/docs/mcp).


    ### claude.ai and Claude Desktop


    Open Settings, then Connectors, and add a custom connector with the URL
    `https://www.getsurfaced.ai/mcp`. Sign in to Surfaced when asked and approve
    the request. The assistant then acts as you, in every workspace you belong
    to, with the permissions of your role in each.


    ### Claude Code


    ```bash

    claude mcp add --transport http surfaced https://www.getsurfaced.ai/mcp
    --header "Authorization: Bearer sk_live_..."

    ```


    Create the key at [API
    keys](https://www.getsurfaced.ai/app/settings/account/api-keys). A read-only
    key runs only the read tools, and a key limited to some workspaces sees only
    those. Leave the header out to sign in with your Surfaced account instead.


    ### Cursor


    Add the server to `~/.cursor/mcp.json`, or leave out `headers` to sign in
    with your Surfaced account:


    ```json

    {
      "mcpServers": {
        "surfaced": {
          "url": "https://www.getsurfaced.ai/mcp",
          "headers": {
            "Authorization": "Bearer sk_live_..."
          }
        }
      }
    }

    ```


    ### Tools


    27 tools. The ones marked "Starts paid work" use the workspace's plan quota
    or AI spend, so the assistant is told to confirm with you first. Key scope
    is what an API key needs to run the tool: a read-only key runs only the
    `read` ones. Tools that act in a workspace take a `workspaceId`;
    `list_workspaces` gives the ids.


    **Account**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `list_workspaces` | Get the authenticated user and accessible workspaces |
    read |


    **Workspace**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `get_workspace` | Get the workspace | read |

    | `update_workspace` | Change workspace settings | write |


    **Insights**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `get_analytics_summary` | Get the visibility summary | read |

    | `get_analytics_trend` | Get the daily visibility trend | read |

    | `list_tracking_runs` | List tracking runs | read |

    | `start_tracking_run` | Start a tracking run. Starts paid work. | write |

    | `list_prompts` | List the prompts | read |

    | `list_sources` | List the sources AI platforms cite | read |

    | `list_competitors` | List tracked competitors | read |

    | `get_audit_report` | Get the Audit report | read |


    **Improve**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `list_actions` | List actions | read |

    | `get_action` | Get an action with its evidence | read |

    | `accept_action` | Accept an action | write |

    | `write_brief_for_action` | Write a Brief for a content-gap action. Starts
    paid work. | write |

    | `complete_action` | Mark an action done | write |

    | `decline_action` | Decline an action | write |


    **Content**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `list_briefs` | List Content Briefs in the active workspace | read |

    | `create_briefs` | Create one or more Content Briefs | write |

    | `get_brief` | Get a Content Brief | read |

    | `list_articles` | List Articles | read |

    | `get_article` | Get an Article | read |

    | `publish_article` | Publish an Article to the hosted blog | write |

    | `get_brief_run` | Get a Brief's Run | read |

    | `start_brief_run` | Start a Brief's Run. Starts paid work. | write |


    **Content Agent**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `get_content_agent` | Get whether Content Agent is on | read |


    **Knowledge**


    | Tool | What it does | Key scope |

    | --- | --- | --- |

    | `search_knowledge` | Search the knowledge base | read |
servers:
  - url: https://www.getsurfaced.ai/api/v1
    description: Production
  - url: /api/v1
    description: This deployment
security:
  - bearerAuth: []
paths:
  /autopilot/plans/{planId}/page-improvement-preview:
    get:
      tags:
        - Autopilot
      summary: Preview exact saved page improvements
      description: >-
        Returns bounded original and proposed content from the exact frozen
        source and checked proposal, or saved full-rewrite revisions. No
        current-Page fallback, execution or publication occurs. Missing
        historical provenance returns unavailable.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - schema:
            type: string
            format: uuid
          required: true
          name: planId
          in: path
        - schema:
            type: string
            format: uuid
          required: true
          name: targetId
          in: query
        - schema:
            type: string
            format: uuid
          required: false
          name: expectedSourceGateReceiptId
          in: query
      responses:
        '200':
          description: Exact saved preview or explicit unavailable state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageImprovementPreviewResponse'
        '400':
          description: '`WORKSPACE_REQUIRED`: the `X-Workspace-Id` header is missing'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: >-
            `BILLING_LIMIT`: the workspace's plan does not include API access,
            or a plan quota was reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Caller lacks read scope or view-content permission
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Plan or target is not available in this workspace
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The saved proposal or expected gate cannot be verified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '410':
          description: >-
            `ENDPOINT_REMOVED`: the endpoint is in a scheduled brownout ahead of
            its sunset, or has been removed. `error.details.successor` names the
            replacement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid preview request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            `RATE_LIMITED`: request limit exceeded. See the `X-RateLimit-*`
            response headers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: >-
            `INTERNAL_ERROR`: unexpected failure. The message is always
            `Internal server error`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      deprecated: true
components:
  parameters:
    WorkspaceId:
      name: X-Workspace-Id
      in: header
      required: true
      description: >-
        UUID of the workspace to act in. Take it from `GET /me`
        (`workspaces[].id`). A key scoped to specific workspaces is rejected
        with 403 `WORKSPACE_NOT_ALLOWED` for any other id; a missing header is
        400 `WORKSPACE_REQUIRED`.
      schema:
        type: string
        format: uuid
  schemas:
    PageImprovementPreviewResponse:
      type: object
      properties:
        data:
          oneOf:
            - type: object
              properties:
                planId:
                  type: string
                  format: uuid
                targetId:
                  type: string
                  format: uuid
                strategy:
                  type: string
                  enum:
                    - metadata
                    - faq_or_schema
                    - internal_links
                    - update_existing_page
                state:
                  type: string
                  enum:
                    - unavailable
                message:
                  type: string
                  maxLength: 500
                runHref:
                  type:
                    - string
                    - 'null'
              required:
                - planId
                - targetId
                - strategy
                - state
                - message
                - runHref
            - type: object
              properties:
                planId:
                  type: string
                  format: uuid
                targetId:
                  type: string
                  format: uuid
                strategy:
                  type: string
                  enum:
                    - metadata
                    - faq_or_schema
                    - internal_links
                    - update_existing_page
                state:
                  type: string
                  enum:
                    - available
                attemptId:
                  type: string
                  format: uuid
                sourcePageId:
                  type: string
                  format: uuid
                sourceGateReceiptId:
                  type:
                    - string
                    - 'null'
                  format: uuid
                sourceHash:
                  type: string
                  pattern: ^[a-f0-9]{64}$
                proposalHash:
                  type: string
                  pattern: ^[a-f0-9]{64}$
                sourceUrl:
                  type:
                    - string
                    - 'null'
                  format: uri
                capturedAt:
                  type: string
                  format: date-time
                evaluatedAt:
                  type:
                    - string
                    - 'null'
                  format: date-time
                originalRevisionId:
                  type:
                    - string
                    - 'null'
                  format: uuid
                proposedRevisionId:
                  type:
                    - string
                    - 'null'
                  format: uuid
                original:
                  type: object
                  properties:
                    title:
                      type: string
                      maxLength: 10000
                    description:
                      type:
                        - string
                        - 'null'
                      maxLength: 10000
                    blocks:
                      type: array
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                            minLength: 1
                            maxLength: 500
                          type:
                            type: string
                            enum:
                              - paragraph
                              - heading
                              - cta
                              - faq
                              - list
                              - quote
                              - internalLink
                              - table
                              - sources
                              - summary
                          content:
                            type: string
                            maxLength: 100000
                            default: ''
                          text:
                            type: string
                            maxLength: 100000
                          label:
                            type: string
                            maxLength: 10000
                          url:
                            type: string
                            maxLength: 2048
                          level:
                            type: integer
                            minimum: 1
                            maximum: 6
                          listType:
                            type: string
                            enum:
                              - ordered
                              - unordered
                          style:
                            type: string
                            enum:
                              - tldr
                              - key-takeaways
                              - highlights
                          items:
                            anyOf:
                              - type: array
                                items:
                                  type: string
                                  maxLength: 100000
                              - type: array
                                items:
                                  type: object
                                  properties:
                                    question:
                                      type: string
                                      maxLength: 10000
                                    answer:
                                      type: string
                                      maxLength: 100000
                                  required:
                                    - question
                                    - answer
                          author:
                            type: string
                            maxLength: 1000
                          sourceIds:
                            type: array
                            items:
                              type: string
                              maxLength: 500
                            maxItems: 500
                          sources:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  pattern: ^[a-f0-9]{12}$
                                url:
                                  type: string
                                  format: uri
                                title:
                                  type: string
                                  minLength: 1
                                publisher:
                                  type: string
                                snippet:
                                  type: string
                                publishedAt:
                                  type: string
                                locale:
                                  type: string
                              required:
                                - id
                                - title
                            maxItems: 500
                        required:
                          - id
                          - type
                      maxItems: 2000
                    sources:
                      type:
                        - array
                        - 'null'
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                            pattern: ^[a-f0-9]{12}$
                          url:
                            type: string
                            format: uri
                          title:
                            type: string
                            minLength: 1
                          publisher:
                            type: string
                          snippet:
                            type: string
                          publishedAt:
                            type: string
                          locale:
                            type: string
                        required:
                          - id
                          - title
                      maxItems: 500
                  required:
                    - title
                    - description
                    - blocks
                proposed:
                  type: object
                  properties:
                    title:
                      type: string
                      maxLength: 10000
                    description:
                      type:
                        - string
                        - 'null'
                      maxLength: 10000
                    blocks:
                      type: array
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                            minLength: 1
                            maxLength: 500
                          type:
                            type: string
                            enum:
                              - paragraph
                              - heading
                              - cta
                              - faq
                              - list
                              - quote
                              - internalLink
                              - table
                              - sources
                              - summary
                          content:
                            type: string
                            maxLength: 100000
                            default: ''
                          text:
                            type: string
                            maxLength: 100000
                          label:
                            type: string
                            maxLength: 10000
                          url:
                            type: string
                            maxLength: 2048
                          level:
                            type: integer
                            minimum: 1
                            maximum: 6
                          listType:
                            type: string
                            enum:
                              - ordered
                              - unordered
                          style:
                            type: string
                            enum:
                              - tldr
                              - key-takeaways
                              - highlights
                          items:
                            anyOf:
                              - type: array
                                items:
                                  type: string
                                  maxLength: 100000
                              - type: array
                                items:
                                  type: object
                                  properties:
                                    question:
                                      type: string
                                      maxLength: 10000
                                    answer:
                                      type: string
                                      maxLength: 100000
                                  required:
                                    - question
                                    - answer
                          author:
                            type: string
                            maxLength: 1000
                          sourceIds:
                            type: array
                            items:
                              type: string
                              maxLength: 500
                            maxItems: 500
                          sources:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  pattern: ^[a-f0-9]{12}$
                                url:
                                  type: string
                                  format: uri
                                title:
                                  type: string
                                  minLength: 1
                                publisher:
                                  type: string
                                snippet:
                                  type: string
                                publishedAt:
                                  type: string
                                locale:
                                  type: string
                              required:
                                - id
                                - title
                            maxItems: 500
                        required:
                          - id
                          - type
                      maxItems: 2000
                    sources:
                      type:
                        - array
                        - 'null'
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                            pattern: ^[a-f0-9]{12}$
                          url:
                            type: string
                            format: uri
                          title:
                            type: string
                            minLength: 1
                          publisher:
                            type: string
                          snippet:
                            type: string
                          publishedAt:
                            type: string
                          locale:
                            type: string
                        required:
                          - id
                          - title
                      maxItems: 500
                  required:
                    - title
                    - description
                    - blocks
                language:
                  type: string
                  maxLength: 100
                runHref:
                  type:
                    - string
                    - 'null'
              required:
                - planId
                - targetId
                - strategy
                - state
                - attemptId
                - sourcePageId
                - sourceGateReceiptId
                - sourceHash
                - proposalHash
                - sourceUrl
                - capturedAt
                - evaluatedAt
                - originalRevisionId
                - proposedRevisionId
                - original
                - proposed
                - language
                - runHref
      required:
        - data
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - UNAUTHORIZED
                - PERMISSION_DENIED
                - FEATURE_NOT_AVAILABLE
                - FEATURE_FLAGS_UNAVAILABLE
                - SCOPE_DENIED
                - BAD_REQUEST
                - NOT_FOUND
                - CONFLICT
                - VALIDATION_ERROR
                - BILLING_LIMIT
                - RATE_LIMITED
                - ENDPOINT_REMOVED
                - WORKSPACE_REQUIRED
                - WORKSPACE_NOT_ALLOWED
                - INTERNAL_ERROR
                - CONFIG_MISSING
                - AUTH_UNAVAILABLE
                - SHOPIFY_NOT_CONNECTED
                - PAGE_CONTENT_CHANGED
                - DUPLICATE_PAGE
                - OWNED_DOMAINS_LIMIT_REACHED
              description: >-
                Machine-readable error code. The HTTP status is fixed per code,
                e.g. `BILLING_LIMIT` is 402 and `VALIDATION_ERROR` is 422.
            message:
              type: string
              description: >-
                Human-readable message. On a 5xx `INTERNAL_ERROR` or
                `CONFIG_MISSING` it is always `Internal server error`; the cause
                is never sent.
            details:
              type: object
              additionalProperties: {}
              description: Extra context on 4xx errors. Never present on a 5xx.
          required:
            - code
            - message
      required:
        - error
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key (sk_live_...) created at
        https://www.getsurfaced.ai/app/settings/account/api-keys

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.