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

# List Content Briefs in the active workspace

> Returns Content Briefs with cursor pagination. Each Brief has a lifecycle `status` (`draft`, `awaiting_approval`, `approved`, `writing`, `articles_to_review`, `published`, `didnt_finish`) and, once writing has started, a `run` with one target per market and language and its writing state (`queued`, `writing`, `checking`, `finishing`, `didnt_finish`, `dropped`, `written`). Start writing a Brief with `POST /briefs/{id}/run` (approve a waiting plan with `POST /briefs/{id}/run/approve`); `GET /briefs/{id}/run` has each target's errors and the Run's counts, and `POST /briefs/{id}/run/retry` writes the targets that didn't finish again. Without list options the list is in manual order (`position`) and `state` (the deprecated stored state) and `language` (the Brief's own `language`) filter exactly. Any of `search`, `sort`, `direction`, `view`, `runState`, `intent` or `market` selects the searchable list instead: `language` and `market` then match the Brief's markets, `runState` filters by its Run, `state` is a 422, and `meta.total` can be null. Keep the same parameters when following `meta.cursor`. The snake_case fields are the representation from before camelCase: deprecated, kept with their old values until the API sunset, each naming its replacement, and announced by a `DEPRECATED_FIELDS` warning. The `Surfaced-Version` header is no longer needed and is ignored. If the Briefs' progress cannot be read, the response still has every other field, with `status`, `locked` and `run` null and a `PARTIAL_DATA` warning; retry later for them.



## OpenAPI

````yaml https://www.getsurfaced.ai/api/v1/openapi.json get /briefs
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:
  /briefs:
    get:
      tags:
        - Content
      summary: List Content Briefs in the active workspace
      description: >-
        Returns Content Briefs with cursor pagination. Each Brief has a
        lifecycle `status` (`draft`, `awaiting_approval`, `approved`, `writing`,
        `articles_to_review`, `published`, `didnt_finish`) and, once writing has
        started, a `run` with one target per market and language and its writing
        state (`queued`, `writing`, `checking`, `finishing`, `didnt_finish`,
        `dropped`, `written`). Start writing a Brief with `POST
        /briefs/{id}/run` (approve a waiting plan with `POST
        /briefs/{id}/run/approve`); `GET /briefs/{id}/run` has each target's
        errors and the Run's counts, and `POST /briefs/{id}/run/retry` writes
        the targets that didn't finish again. Without list options the list is
        in manual order (`position`) and `state` (the deprecated stored state)
        and `language` (the Brief's own `language`) filter exactly. Any of
        `search`, `sort`, `direction`, `view`, `runState`, `intent` or `market`
        selects the searchable list instead: `language` and `market` then match
        the Brief's markets, `runState` filters by its Run, `state` is a 422,
        and `meta.total` can be null. Keep the same parameters when following
        `meta.cursor`. The snake_case fields are the representation from before
        camelCase: deprecated, kept with their old values until the API sunset,
        each naming its replacement, and announced by a `DEPRECATED_FIELDS`
        warning. The `Surfaced-Version` header is no longer needed and is
        ignored. If the Briefs' progress cannot be read, the response still has
        every other field, with `status`, `locked` and `run` null and a
        `PARTIAL_DATA` warning; retry later for them.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - schema:
            type: string
            description: '`meta.cursor` of the previous page'
          required: false
          name: cursor
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size, default 25, max 100
          required: false
          name: limit
          in: query
        - schema:
            type: string
            enum:
              - pending
              - generating
              - generated
              - failed
              - new
              - approved
              - ready
            description: >-
              Deprecated: the stored state (`pending`, `generating`,
              `generated`, `failed`). Only without list options; use `status` on
              each Brief, or `runState`.
          required: false
          name: state
          in: query
        - schema:
            type: string
            description: >-
              Without list options: the Brief's own `language`, exactly. With
              list options: a language of its markets (repeatable).
          required: false
          name: language
          in: query
        - schema:
            type: string
            maxLength: 300
            description: 'List option: text in the title, keyword, notes or category'
          required: false
          name: search
          in: query
        - schema:
            type: string
            enum:
              - createdAt
              - title
              - keyword
            description: 'List option: sort key, default `createdAt`'
          required: false
          name: sort
          in: query
        - schema:
            type: string
            enum:
              - asc
              - desc
            description: 'List option: default `desc`'
          required: false
          name: direction
          in: query
        - schema:
            type: string
            enum:
              - all
              - progress
              - drafted
              - active
            description: >-
              List option: `progress` (the Run is queued or running), `drafted`
              (it completed), `active` (not paused) or `all` (default)
          required: false
          name: view
          in: query
        - schema:
            type: array
            items:
              type: string
              enum:
                - not_started
                - queued
                - running
                - completed
                - partial
                - failed
                - cancelled
                - paused
            description: >-
              List option: the state of the Brief's Run (`run.state`),
              `not_started` for none, or `paused`. Repeatable.
          required: false
          name: runState
          in: query
        - schema:
            type: array
            items:
              type: string
            description: 'List option: the Brief''s `intent`. Repeatable.'
          required: false
          name: intent
          in: query
        - schema:
            type: array
            items:
              type: string
            description: >-
              List option: a market of the Brief (country code or market name).
              Repeatable.
          required: false
          name: market
          in: query
      responses:
        '200':
          description: Paginated Content Briefs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListBriefsResponse'
        '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 `read` scope or workspace access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            `VALIDATION_ERROR`: a malformed list option, or `state` combined
            with list options
          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:
    ListBriefsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Brief'
        meta:
          type: object
          properties:
            cursor:
              type:
                - string
                - 'null'
            hasMore:
              type: boolean
            total:
              type:
                - number
                - 'null'
              description: Null when a list option makes an exact count impractical
          required:
            - cursor
            - hasMore
      required:
        - data
        - meta
    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
    Brief:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          description: The Brief's content objective
        keyword:
          type:
            - string
            - 'null'
        persona:
          type:
            - string
            - 'null'
        region:
          type:
            - string
            - 'null'
        language:
          type:
            - string
            - 'null'
          description: The Brief's own language field
        intent:
          type:
            - string
            - 'null'
        tags:
          type: array
          items:
            type: string
        notes:
          type:
            - string
            - 'null'
        category:
          type:
            - string
            - 'null'
        source:
          type:
            - string
            - 'null'
          description: 'How the Brief was created: `manual`, `csv`, `api` or `automation`'
        position:
          type:
            - number
            - 'null'
          description: Manual order; the default list order
        status:
          type:
            - string
            - 'null'
          enum:
            - draft
            - awaiting_approval
            - approved
            - writing
            - articles_to_review
            - published
            - didnt_finish
          description: >-
            Where the Brief is in its lifecycle. `draft`: no Run has started; it
            can be run, and edited unless `locked` (which decides edits).
            `awaiting_approval`: its execution mode asks for approval before
            writing and a plan prepared for its current settings waits for it.
            `approved`: its plan was approved and writing has not started yet;
            only in workspaces where Approve and Start writing are separate
            steps (elsewhere approving a plan starts writing at once).
            `writing`: a target of its Run is `queued`, `writing`, `checking` or
            `finishing`; also a Brief in the Content Agent's backlog (`run` is
            null until the agent starts it). `articles_to_review`: no target is
            being written and at least one saved Article is not published.
            `published`: no target is being written and EVERY market is either
            published or `dropped`, with at least one published. `didnt_finish`:
            writing has stopped and the Brief is not finished — at least one
            market failed, or its Article was archived. To wait for writing,
            poll while a target in `run.targets` is `queued`, `writing`,
            `checking` or `finishing`, not while `status` is `writing`. Null
            when the Brief's progress could not be read; the response then
            carries a `PARTIAL_DATA` warning and `status`, `locked` and `run`
            are all null.
        locked:
          type:
            - boolean
            - 'null'
          description: >-
            Whether edits are refused (409); this, not `status`, decides it.
            True once the Brief's Run has started, and also when its stored
            state moved past `pending` without a Run found (a Brief generated
            before Runs were recorded, or whose Run is gone), which is the one
            case of a `draft` that cannot be edited. Null when the Brief's
            progress could not be read; the response then carries a
            `PARTIAL_DATA` warning and `status`, `locked` and `run` are all
            null.
        run:
          type:
            - object
            - 'null'
          properties:
            id:
              type: string
              format: uuid
            state:
              type: string
              enum:
                - queued
                - running
                - completed
                - partial
                - failed
                - cancelled
              description: >-
                The Run as a whole: `queued` and `running` while targets are
                written, then `completed` (every target written), `partial`
                (some failed), `failed` or `cancelled`
            paused:
              type: boolean
              description: New work on this Brief is paused
            executionMode:
              type:
                - string
                - 'null'
              description: >-
                How the Brief starts writing: `manual`, `review_plan`,
                `autopilot` or `legacy`
            latestActivityAt:
              type:
                - string
                - 'null'
              format: date-time
            targets:
              type: array
              items:
                type: object
                properties:
                  marketRef:
                    type:
                      - string
                      - 'null'
                  language:
                    type:
                      - string
                      - 'null'
                  locale:
                    type:
                      - string
                      - 'null'
                  state:
                    type: string
                    enum:
                      - queued
                      - writing
                      - checking
                      - finishing
                      - didnt_finish
                      - dropped
                      - written
                    description: >-
                      Where writing this market and language stands: `queued`
                      (not started yet), `writing`, `checking` (the Article is
                      saved and its quality check is running), `didnt_finish`
                      (writing failed, or the saved Article did not pass its
                      check; it can be retried or changed), `dropped` (a person
                      stopped this market: it is not written unless it is
                      resumed, which a plain retry never does) or `written` (the
                      Article is saved, and may be published). `finishing` is
                      reserved for a later writing step and not produced yet:
                      treat it as still being written.
                  articleId:
                    type:
                      - string
                      - 'null'
                    format: uuid
                    description: >-
                      The Article this target saved, even if archived since;
                      null until one is saved
                required:
                  - marketRef
                  - language
                  - locale
                  - state
                  - articleId
              description: One entry per market and language the Run writes
          required:
            - id
            - state
            - paused
            - executionMode
            - latestActivityAt
            - targets
          description: >-
            The Brief's Run, one target per market and language; null until it
            starts. The Run resource has its counts, errors and saved plan. Null
            when the Brief's progress could not be read; the response then
            carries a `PARTIAL_DATA` warning and `status`, `locked` and `run`
            are all null.
        markets:
          type: array
          items:
            type: object
            properties:
              marketRef:
                type:
                  - string
                  - 'null'
              name:
                type:
                  - string
                  - 'null'
              countries:
                type: array
                items:
                  type: string
              language:
                type:
                  - string
                  - 'null'
            required:
              - marketRef
              - name
              - countries
              - language
          description: >-
            Saved markets and languages; empty means the workspace's default
            markets apply
        content:
          type: object
          properties:
            articleStructure:
              type:
                - string
                - 'null'
            culturalTone:
              type:
                - string
                - 'null'
            formality:
              type:
                - string
                - 'null'
            addressFormality:
              type:
                - string
                - 'null'
            wordCount:
              type: object
              properties:
                min:
                  type:
                    - number
                    - 'null'
                target:
                  type:
                    - number
                    - 'null'
                max:
                  type:
                    - number
                    - 'null'
              required:
                - min
                - target
                - max
            requiredSections:
              type: array
              items:
                type: string
            blockPreferences:
              type:
                - object
                - 'null'
              additionalProperties: {}
            templateId:
              type:
                - string
                - 'null'
            faqPosition:
              type:
                - string
                - 'null'
            cta:
              type:
                - object
                - 'null'
              additionalProperties: {}
            targetRegions:
              type: array
              items:
                type: string
              description: Regions the research focuses on
          required:
            - articleStructure
            - culturalTone
            - formality
            - addressFormality
            - wordCount
            - requiredSections
            - blockPreferences
            - templateId
            - faqPosition
            - cta
            - targetRegions
        gap:
          type:
            - object
            - 'null'
          properties:
            gapIds:
              type: array
              items:
                type: string
            suggestionId:
              type:
                - string
                - 'null'
            targetScope: {}
          required:
            - gapIds
            - suggestionId
          description: The Gap this Brief was created from, if any
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type:
            - string
            - 'null'
          format: date-time
        workspace_id:
          type: string
          format: uuid
          description: >-
            Deprecated, removed at the API sunset. Not part of the canonical
            Brief: it is the workspace the request's `X-Workspace-Id` names.
          deprecated: true
        state:
          type: string
          enum:
            - pending
            - generating
            - generated
            - failed
            - new
            - approved
            - ready
          description: >-
            Deprecated, removed at the API sunset. The stored record state
            (`pending`, `generating`, `generated`, `failed`). Read `status` for
            the Brief's lifecycle and `run.state` for its Run.
          deprecated: true
        input_tags:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Deprecated, removed at the API sunset. Read `tags` (an empty list
            where this is null).
          deprecated: true
        metadata:
          type:
            - object
            - 'null'
          additionalProperties: {}
          description: >-
            Deprecated, removed at the API sunset. Not part of the canonical
            Brief. The linked Gap's scope it holds is `gap.targetScope`.
          deprecated: true
        source_suggestion_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Deprecated, removed at the API sunset. Read `gap.suggestionId`.
          deprecated: true
        source_gap_ids:
          type:
            - array
            - 'null'
          items:
            type: string
          description: Deprecated, removed at the API sunset. Read `gap.gapIds`.
          deprecated: true
        article_structure:
          type:
            - string
            - 'null'
          description: >-
            Deprecated, removed at the API sunset. Read
            `content.articleStructure`.
          deprecated: true
        cultural_tone:
          type:
            - string
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.culturalTone`.
          deprecated: true
        formality:
          type:
            - string
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.formality`.
          deprecated: true
        address_formality:
          type:
            - string
            - 'null'
          description: >-
            Deprecated, removed at the API sunset. Read
            `content.addressFormality`.
          deprecated: true
        word_count_min:
          type:
            - number
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.wordCount.min`.
          deprecated: true
        word_count_target:
          type:
            - number
            - 'null'
          description: >-
            Deprecated, removed at the API sunset. Read
            `content.wordCount.target`.
          deprecated: true
        word_count_max:
          type:
            - number
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.wordCount.max`.
          deprecated: true
        required_sections:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Deprecated, removed at the API sunset. Read
            `content.requiredSections` (an empty list where this is null).
          deprecated: true
        block_preferences:
          type:
            - object
            - 'null'
          additionalProperties: {}
          description: >-
            Deprecated, removed at the API sunset. Read
            `content.blockPreferences`.
          deprecated: true
        template_id:
          type:
            - string
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.templateId`.
          deprecated: true
        faq_position:
          type:
            - string
            - 'null'
          description: Deprecated, removed at the API sunset. Read `content.faqPosition`.
          deprecated: true
        cta:
          type:
            - object
            - 'null'
          additionalProperties: {}
          description: Deprecated, removed at the API sunset. Read `content.cta`.
          deprecated: true
        target_regions:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Deprecated, removed at the API sunset. Read `content.targetRegions`
            (an empty list where this is null).
          deprecated: true
        target_markets:
          type:
            - array
            - 'null'
          items: {}
          description: >-
            Deprecated, removed at the API sunset. Read `markets`, the Brief's
            saved markets and languages.
          deprecated: true
        locale:
          type:
            - string
            - 'null'
          description: >-
            Deprecated, removed at the API sunset. Read `markets`, the Brief's
            saved markets and languages.
          deprecated: true
        locales:
          type:
            - array
            - 'null'
          items:
            type: string
          description: >-
            Deprecated, removed at the API sunset. Read `markets`, the Brief's
            saved markets and languages.
          deprecated: true
        cultural_overrides:
          type:
            - object
            - 'null'
          additionalProperties: {}
          description: >-
            Deprecated, removed at the API sunset. Not part of the canonical
            Brief, and not writable through the API.
          deprecated: true
        idea_text:
          type: string
          description: >-
            Deprecated alias of `title`, removed at the API sunset. Read
            `title`.
          deprecated: true
        primary_keyword:
          type:
            - string
            - 'null'
          description: >-
            Deprecated alias of `keyword`, removed at the API sunset. Read
            `keyword`.
          deprecated: true
        created_at:
          type: string
          format: date-time
          description: >-
            Deprecated alias of `createdAt`, removed at the API sunset. Read
            `createdAt`.
          deprecated: true
        updated_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            Deprecated alias of `updatedAt`, removed at the API sunset. Read
            `updatedAt`.
          deprecated: true
      required:
        - id
        - title
        - keyword
        - persona
        - region
        - language
        - intent
        - tags
        - notes
        - category
        - source
        - position
        - status
        - locked
        - run
        - markets
        - content
        - gap
        - createdAt
        - updatedAt
        - workspace_id
        - state
        - input_tags
        - metadata
        - source_suggestion_id
        - source_gap_ids
        - article_structure
        - cultural_tone
        - formality
        - address_formality
        - word_count_min
        - word_count_target
        - word_count_max
        - required_sections
        - block_preferences
        - template_id
        - faq_position
        - cta
        - target_regions
        - target_markets
        - locale
        - locales
        - cultural_overrides
  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.