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

# Activate a new Autopilot policy version

> Atomically appends and activates an immutable policy version. Requires API `write` scope, `manage_settings`, and the exact workspace rollout flag. `requestId` is the idempotency identity: replaying the same request returns the original policy and receipt.



## OpenAPI

````yaml https://www.getsurfaced.ai/api/v1/openapi.json post /autopilot/policies
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/policies:
    post:
      tags:
        - Autopilot
      summary: Activate a new Autopilot policy version
      description: >-
        Atomically appends and activates an immutable policy version. Requires
        API `write` scope, `manage_settings`, and the exact workspace rollout
        flag. `requestId` is the idempotency identity: replaying the same
        request returns the original policy and receipt.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivateAutopilotPolicyRequest'
      responses:
        '201':
          description: Policy version activated (or idempotently replayed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutopilotPolicyCommandResponse'
        '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 `write` scope, workspace access, `manage_settings`, or
            rollout access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Policy version changed or idempotency identity was reused
          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: Policy failed the action, market, route, owner, or deployment gates
          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:
    ActivateAutopilotPolicyRequest:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        policyOwnerId:
          type: string
          format: uuid
        expectedLatestVersion:
          type: integer
          minimum: 0
        requestId:
          type: string
          format: uuid
        policy:
          type: object
          properties:
            schema:
              type: string
              enum:
                - autopilot_policy.v1
            version:
              type: integer
              exclusiveMinimum: 0
            enabled:
              type: boolean
            pacing:
              type: object
              properties:
                version:
                  type: number
                  enum:
                    - 1
                allowPaidOverage:
                  type: boolean
              required:
                - version
                - allowPaidOverage
              additionalProperties: false
            mode:
              type: string
              enum:
                - approval_batch
                - draft
                - publish_pilot
            scope:
              type: object
              properties:
                projectIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                topicAllowlist:
                  type: array
                  items:
                    type: string
                    minLength: 1
                routeAllowlist:
                  type: array
                  items:
                    type: string
                    minLength: 1
                marketRefs:
                  type: array
                  items:
                    type: string
                    minLength: 1
                excludedTopics:
                  type: array
                  items:
                    type: string
                    minLength: 1
              required:
                - projectIds
                - routeAllowlist
                - marketRefs
            actions:
              type: array
              items:
                type: string
                enum:
                  - create_article
                  - update_existing_page
                  - internal_links
                  - metadata
                  - faq_or_schema
            budgets:
              type: object
              properties:
                monthlyMillicents:
                  type: integer
                  minimum: 0
                perPlanMillicents:
                  type: integer
                  minimum: 0
                monitoringMonthlyMillicents:
                  type: integer
                  minimum: 0
                maxActionsPerWeek:
                  type: integer
                  minimum: 0
                maxPagesPerWeek:
                  type: integer
                  minimum: 0
              required:
                - monthlyMillicents
                - perPlanMillicents
                - maxActionsPerWeek
                - maxPagesPerWeek
            thresholds:
              type: object
              properties:
                minOpportunityConfidence:
                  type: number
                  minimum: 0
                  maximum: 1
                minExpectedImpact:
                  type: number
                  minimum: 0
                  maximum: 1
                minQuality:
                  type: number
                  minimum: 0
                  maximum: 100
              required:
                - minOpportunityConfidence
                - minExpectedImpact
                - minQuality
            approvals:
              type: object
              properties:
                green:
                  type: string
                  enum:
                    - automatic
                    - batch
                yellow:
                  type: string
                  enum:
                    - batch
                    - explicit
                red:
                  type: string
                  enum:
                    - explicit
                publishing:
                  type: string
                  enum:
                    - never
                    - green_only
                    - policy_allowed
              required:
                - green
                - yellow
                - red
                - publishing
            livePublication:
              type: object
              properties:
                provider:
                  type: string
                  enum:
                    - shopify
                connectionId:
                  type: string
                  format: uuid
                resourceType:
                  type: string
                  enum:
                    - page
              required:
                - provider
                - connectionId
                - resourceType
              additionalProperties: false
            cooldownDays:
              type: integer
              minimum: 0
            killSwitch:
              type: boolean
          required:
            - schema
            - version
            - enabled
            - mode
            - scope
            - actions
            - budgets
            - thresholds
            - approvals
            - cooldownDays
            - killSwitch
      required:
        - projectId
        - policyOwnerId
        - expectedLatestVersion
        - requestId
        - policy
      additionalProperties: false
    AutopilotPolicyCommandResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/AutopilotPolicyCommandResult'
      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
    AutopilotPolicyCommandResult:
      type: object
      properties:
        policyId:
          type: string
          format: uuid
        receiptId:
          type: string
          format: uuid
        version:
          type: integer
          exclusiveMinimum: 0
        active:
          type: boolean
        idempotent:
          type: boolean
      required:
        - policyId
        - receiptId
        - version
        - active
        - idempotent
  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.