> ## 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.

# Get a plan with its evidence trail

> Returns the plan, its publication receipts and measured outcomes. Locale lanes stay separate: this never presents them as translations.



## OpenAPI

````yaml https://www.getsurfaced.ai/api/v1/openapi.json get /content-agent/plans/{planId}
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:
  /content-agent/plans/{planId}:
    get:
      tags:
        - Settings
      summary: Get a plan with its evidence trail
      description: >-
        Returns the plan, its publication receipts and measured outcomes. Locale
        lanes stay separate: this never presents them as translations.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - schema:
            type: string
            format: uuid
          required: true
          name: planId
          in: path
      responses:
        '200':
          description: Plan, publications and outcomes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContentAgentPlanDetail'
        '400':
          description: '`WORKSPACE_REQUIRED`: the `X-Workspace-Id` header is missing'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid API key
          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 workspace read access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Plan not found in this workspace
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: '`planId` is not a UUID'
          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'
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:
    ContentAgentPlanDetail:
      type: object
      properties:
        data:
          type: object
          properties:
            plan:
              type: object
              properties:
                id:
                  type: string
                  format: uuid
                objectiveHash:
                  type: string
                approval:
                  type: object
                  properties:
                    eligible:
                      type: boolean
                    reason:
                      type:
                        - string
                        - 'null'
                    briefId:
                      type:
                        - string
                        - 'null'
                  required:
                    - eligible
                    - reason
                    - briefId
                title:
                  type: string
                strategy:
                  type: string
                  enum:
                    - create_article
                    - update_existing_article
                    - metadata
                    - faq_or_schema
                    - internal_links
                    - no_action
                status:
                  type: string
                  enum:
                    - planned
                    - authorized
                    - running
                    - all_ready
                    - publishing
                    - published
                    - measuring
                    - verified_closed
                    - ineffective
                    - no_action
                    - partial
                    - needs_review
                    - failed
                    - cancelled
                requiredTargetCount:
                  type: integer
                  minimum: 0
                lanes:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      required:
                        type: boolean
                      targetKey:
                        type: string
                      locale:
                        type: string
                      marketName:
                        type: string
                      title:
                        type: string
                      angle:
                        type: string
                      audienceNuance:
                        type: string
                      primaryKeyword:
                        type: string
                      searchIntent:
                        type: string
                      rationale:
                        type: string
                      evidenceCovered:
                        type: number
                      evidenceRequired:
                        type: number
                      riskLane:
                        type: string
                        enum:
                          - green
                          - yellow
                          - red
                      status:
                        type: string
                        enum:
                          - planned
                          - authorized
                          - queued
                          - generating
                          - repairing
                          - needs_review
                          - ready
                          - publishing
                          - published
                          - measuring
                          - verified_closed
                          - ineffective
                          - failed
                          - skipped
                          - no_action
                          - cancelled
                      phaseLabel:
                        type: string
                      progress:
                        type: number
                      blocker:
                        type: object
                        properties:
                          title:
                            type: string
                          detail:
                            type: string
                          actionLabel:
                            type: string
                        required:
                          - title
                          - detail
                      generationRetry:
                        type: object
                        properties:
                          state:
                            type: string
                            enum:
                              - available
                              - exhausted
                              - human_review
                              - not_retryable
                          attemptNo:
                            type:
                              - number
                              - 'null'
                          maxAttempts:
                            type: number
                          retryNotBefore:
                            type:
                              - string
                              - 'null'
                        required:
                          - state
                          - attemptNo
                          - maxAttempts
                      updatedAt:
                        type: string
                    required:
                      - id
                      - targetKey
                      - locale
                      - marketName
                      - title
                      - angle
                      - audienceNuance
                      - primaryKeyword
                      - riskLane
                      - status
                      - phaseLabel
                      - progress
                      - updatedAt
                receipts:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                      headline:
                        type: string
                      detail:
                        type: string
                      reasonCode:
                        type: string
                      nextAction:
                        type: string
                      occurredAt:
                        type: string
                      targetLabel:
                        type: string
                      actorLabel:
                        type: string
                      tone:
                        type: string
                        enum:
                          - neutral
                          - progress
                          - success
                          - attention
                    required:
                      - id
                      - headline
                      - occurredAt
                      - actorLabel
                      - tone
                canRequestGenerationRetry:
                  type: boolean
                canManageLocalActions:
                  type: boolean
                canReviewExternalPublication:
                  type: boolean
                changeReviews:
                  type: object
                  additionalProperties:
                    type: object
                    properties:
                      projectId:
                        type: string
                        format: uuid
                      planId:
                        type: string
                        format: uuid
                      targetId:
                        type: string
                        format: uuid
                      strategy:
                        type: string
                        enum:
                          - metadata
                          - faq_or_schema
                          - internal_links
                      state:
                        type: string
                        enum:
                          - pending
                          - resolved
                          - stale
                          - unavailable
                      sourceGateReceiptId:
                        type:
                          - string
                          - 'null'
                        format: uuid
                      decisionReceiptId:
                        type:
                          - string
                          - 'null'
                        format: uuid
                      presentation:
                        type:
                          - object
                          - 'null'
                        properties:
                          reason:
                            type: string
                            enum:
                              - checker_health
                              - quality_score
                              - managing_editor
                          summary:
                            type: string
                          canonicalCheckers:
                            type: object
                            properties:
                              healthy:
                                type: integer
                              required:
                                type: integer
                            required:
                              - healthy
                              - required
                          shipQualityPassed:
                            type: boolean
                          terminalManagingEditorAction:
                            type: string
                          evaluatedAt:
                            type: string
                            format: date-time
                        required:
                          - reason
                          - summary
                          - canonicalCheckers
                          - shipQualityPassed
                          - terminalManagingEditorAction
                          - evaluatedAt
                      allowedOutcomes:
                        type: array
                        items:
                          type: string
                          enum:
                            - approve
                            - reject
                            - request_repair
                      executionAuthorized:
                        type: boolean
                      canRepair:
                        type: boolean
                      canApprove:
                        type: boolean
                      resolvedOutcome:
                        type:
                          - string
                          - 'null'
                        enum:
                          - approve
                          - reject
                          - request_repair
                    required:
                      - projectId
                      - planId
                      - targetId
                      - strategy
                      - state
                      - sourceGateReceiptId
                      - decisionReceiptId
                      - presentation
                      - allowedOutcomes
                      - executionAuthorized
                      - canRepair
                      - canApprove
                      - resolvedOutcome
                changeReviewErrors:
                  type: object
                  additionalProperties:
                    type: string
                authorityReceipt:
                  type: object
                  properties:
                    autonomyMode:
                      type: string
                      enum:
                        - assisted
                        - approval_batch
                        - draft
                        - publish_pilot
                    livePublicationIntent:
                      type: boolean
                    riskLane:
                      type: string
                      enum:
                        - green
                        - yellow
                        - red
                    policyVersion:
                      type: integer
                    decisionDetails:
                      oneOf:
                        - type: object
                          properties:
                            state:
                              type: string
                              enum:
                                - available
                            selectedIntervention:
                              type: string
                              enum:
                                - create_article
                                - update_existing_article
                                - metadata
                                - faq_or_schema
                                - internal_links
                                - no_action
                            sourceRecommendation:
                              type: string
                              enum:
                                - create_article
                                - update_existing_article
                                - metadata
                                - faq_or_schema
                                - internal_links
                                - no_action
                            noActionReason:
                              type:
                                - string
                                - 'null'
                            reasonCodes:
                              type: array
                              items:
                                type: string
                            alternatives:
                              type: array
                              items:
                                type: object
                                properties:
                                  intervention:
                                    type: string
                                    enum:
                                      - create_article
                                      - update_existing_article
                                      - metadata
                                      - faq_or_schema
                                      - internal_links
                                      - no_action
                                  rank:
                                    type:
                                      - number
                                      - 'null'
                                  evidenceEligible:
                                    type: boolean
                                  policyEligible:
                                    type: boolean
                                  policyDecision:
                                    type: string
                                    enum:
                                      - allow
                                      - require_approval
                                      - deny
                                      - not_applicable
                                  reasonCodes:
                                    type: array
                                    items:
                                      type: string
                                required:
                                  - intervention
                                  - rank
                                  - evidenceEligible
                                  - policyEligible
                                  - policyDecision
                                  - reasonCodes
                          required:
                            - state
                            - selectedIntervention
                            - sourceRecommendation
                            - noActionReason
                            - reasonCodes
                            - alternatives
                        - type: object
                          properties:
                            state:
                              type: string
                              enum:
                                - unavailable
                          required:
                            - state
                  required:
                    - autonomyMode
                    - riskLane
                    - policyVersion
                    - decisionDetails
                publicationEligibility:
                  type: object
                  properties:
                    eligible:
                      type: boolean
                    autonomyEligible:
                      type: boolean
                    activePolicy:
                      type: boolean
                    destinationConnected:
                      type: boolean
                    reason:
                      type:
                        - string
                        - 'null'
                  required:
                    - eligible
                    - autonomyEligible
                    - activePolicy
                    - destinationConnected
                    - reason
                measurementOutcomes:
                  type: object
                  properties:
                    verified:
                      type: number
                    ineffective:
                      type: number
                    awaiting:
                      type: number
                  required:
                    - verified
                    - ineffective
                    - awaiting
              required:
                - id
                - title
                - status
                - requiredTargetCount
                - lanes
                - receipts
            publications:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  targetId:
                    type: string
                    format: uuid
                  articleId:
                    type: string
                    format: uuid
                  resourceType:
                    type: string
                    enum:
                      - article
                  handle:
                    type: string
                  status:
                    type: string
                  verificationState:
                    type: string
                  remoteAdminUrl:
                    type:
                      - string
                      - 'null'
                  remotePublicUrl:
                    type:
                      - string
                      - 'null'
                  remoteVerifiedAt:
                    type:
                      - string
                      - 'null'
                  remotePublishedAt:
                    type:
                      - string
                      - 'null'
                  liveCheckedAt:
                    type:
                      - string
                      - 'null'
                  failureCode:
                    type:
                      - string
                      - 'null'
                  failureMessage:
                    type:
                      - string
                      - 'null'
                  approvedAt:
                    type: string
                  autoLive:
                    type: boolean
                required:
                  - id
                  - targetId
                  - articleId
                  - resourceType
                  - handle
                  - status
                  - verificationState
                  - remoteAdminUrl
                  - remotePublicUrl
                  - remoteVerifiedAt
                  - remotePublishedAt
                  - liveCheckedAt
                  - failureCode
                  - failureMessage
                  - approvedAt
                  - autoLive
            outcomes:
              type: array
              items:
                type: object
                properties:
                  measurementId:
                    type:
                      - string
                      - 'null'
                    format: uuid
                  targetId:
                    type: string
                    format: uuid
                  targetStatus:
                    type: string
                  targetPublicUrl:
                    type:
                      - string
                      - 'null'
                  state:
                    type: string
                    enum:
                      - awaiting_evidence
                      - verified_closed
                      - ineffective
                      - needs_review
                      - waived
                      - evidence_missing
                  checkpointDay:
                    type: integer
                  marketLabel:
                    type: string
                  locale:
                    type: string
                  before:
                    type: object
                    properties:
                      brandMentionRate:
                        type: number
                      citationRate:
                        type: number
                      answerCoverage:
                        type: number
                    required:
                      - brandMentionRate
                      - citationRate
                      - answerCoverage
                  after:
                    type:
                      - object
                      - 'null'
                    properties:
                      brandMentionRate:
                        type: number
                      citationRate:
                        type: number
                      answerCoverage:
                        type: number
                    required:
                      - brandMentionRate
                      - citationRate
                      - answerCoverage
                  criteriaPassed:
                    type: integer
                  criteriaTotal:
                    type: integer
                  observedAt:
                    type:
                      - string
                      - 'null'
                  reviewReasonCode:
                    type:
                      - string
                      - 'null'
                  baselineSampleCount:
                    type: integer
                  observedSampleCount:
                    type:
                      - integer
                      - 'null'
                  publicationAnchor:
                    type:
                      - object
                      - 'null'
                    properties:
                      source:
                        type: string
                      liveAt:
                        type: string
                      publicUrl:
                        type: string
                      revisionRef:
                        type: string
                    required:
                      - source
                      - liveAt
                      - publicUrl
                      - revisionRef
                  nextAction:
                    type:
                      - object
                      - 'null'
                    properties:
                      opportunityId:
                        type: string
                        format: uuid
                      recommendedAction:
                        type: string
                        enum:
                          - create_article
                          - update_existing_article
                          - metadata
                          - faq_or_schema
                          - internal_links
                          - no_action
                      rationale:
                        type: string
                      requiresMaterialDifference:
                        type: boolean
                    required:
                      - opportunityId
                      - recommendedAction
                      - rationale
                      - requiresMaterialDifference
                required:
                  - measurementId
                  - targetId
                  - targetStatus
                  - targetPublicUrl
                  - state
                  - checkpointDay
                  - marketLabel
                  - locale
                  - before
                  - after
                  - criteriaPassed
                  - criteriaTotal
                  - observedAt
                  - reviewReasonCode
                  - baselineSampleCount
                  - observedSampleCount
                  - publicationAnchor
                  - nextAction
            actionOutcomes:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  targetId:
                    type: string
                    format: uuid
                  action:
                    type: string
                    enum:
                      - update_existing_article
                      - metadata
                      - faq_or_schema
                      - internal_links
                  kind:
                    type: string
                    enum:
                      - implementation
                      - restoration
                      - visibility_followup
                  state:
                    type: string
                    enum:
                      - implementation_verified
                      - restored
                      - scheduled
                      - observed
                      - evidence_unavailable
                  checkpointDay:
                    type: integer
                    minimum: 0
                  headline:
                    type: string
                  detail:
                    type: string
                  observedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                  dueAt:
                    type: string
                    format: date-time
                  closureMeasurementId:
                    type:
                      - string
                      - 'null'
                    format: uuid
                required:
                  - id
                  - targetId
                  - action
                  - kind
                  - state
                  - checkpointDay
                  - headline
                  - detail
                  - observedAt
                  - dueAt
                  - closureMeasurementId
          required:
            - plan
            - publications
            - outcomes
            - actionOutcomes
      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.