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

# Propose a keyword setup for a scanned draft

> **Beta — behind the `sonar-onboarding` feature flag.** Workspaces the flag is off for get `404 FEATURE_DISABLED`.

Starts the setup agent on a draft returned by `POST /api/v2/setup/scan`: it proposes keyword candidates, measures each one's monthly volume, scores a sample of real posts, and decides a setup of at most 5 keywords with scoping options, feeds and platforms — every decision with its `reasons[]` and a plain `explanation`. **Asynchronous:** answers `202` with the draft id and status at once; poll `GET /api/v2/setup/proposals/{id}` until the status is `ready`, `needs_narrowing` or `failed` (typically 40–75 s). A draft with a running or current proposal gets that one back (`force: true` starts over once it has finished; while a run is live it returns `409 SETUP_PROPOSAL_RUNNING`). A run that stops mid-way is finished by the degradation ladder on the next poll (`propose_interrupted`). Rate-limited to 5 new proposals per workspace per day (`429 RATE_LIMITED`). Creates nothing in the workspace and consumes no mention quota.



## OpenAPI

````yaml https://app.octolens.com/api/v2/openapi.json post /api/v2/setup/propose
openapi: 3.1.0
info:
  title: Octolens API
  version: 2.0.0
  description: >-
    The Octolens API lets you query mentions, manage keywords, and configure
    feeds programmatically. Every action available in the Octolens UI is
    available here.


    ### Authentication


    All v2 endpoints require an API key. Create one in **Settings > API** and
    pass it as `Authorization: Bearer <key>`. Keys are scoped to the
    organization they were minted in; you cannot access another org's data.


    API keys carry a scope — `read`, `write` (implies read), or `admin` (implies
    write). Each endpoint documents the scope it needs via the
    `x-required-scope` OpenAPI extension and the scope badge in the docs
    surface.


    ### Rate limiting


    The v2 API is rate-limited at **500 requests per hour per organization**,
    across all keys for that org. The limit resets at the top of each clock hour
    (fixed hourly window — the counter is keyed by the UTC hour bucket, not a
    rolling trailing hour).


    Every 2xx response carries three headers so clients can pace themselves:

    * `X-RateLimit-Limit` — the hourly cap (500)

    * `X-RateLimit-Remaining` — requests left in the current window

    * `X-RateLimit-Reset` — Unix timestamp (seconds) when the window resets


    When the cap is hit, the endpoint returns **429 Rate Limited** with an
    additional `Retry-After` header (seconds until the next window). The
    response body is the standard `ErrorResponse` with `code: "RATE_LIMITED"`.


    `POST /api/v2/mentions/export` additionally has its own, stricter limits —
    **5 exports per hour and 2 concurrent exports per organization** — because a
    single export fans out into up to 500 backing queries. See that endpoint's
    description; prefer one filtered export over polling exports on a schedule.


    ### Response timing


    Every response also carries a standard `Server-Timing` header with the
    server-side breakdown of that request — `auth` (credential verification,
    plan gate and rate limiting), `handler` (the endpoint's own work) and
    `total`, in milliseconds. Use it to tell whether a slow call was spent in
    the API's request pipeline, in the query behind the endpoint, or on the
    network in between; browsers surface it automatically in devtools. Responses
    rejected before the endpoint ran carry `auth` and `total` only, and an
    endpoint may publish extra metrics of its own alongside these three — parse
    the header as a list, not as a fixed triple.


    ### Error handling


    All non-2xx responses share the same `ErrorResponse` envelope: `{ error: {
    code, message, status, details? } }`. The `code` field is a stable
    `ApiErrorCode` enum — branch on it programmatically instead of parsing
    `message`. See the `ApiErrorCode` schema for the full catalog grouped by
    category.


    `VALIDATION_ERROR` (400) responses include a `details` array with per-field
    Zod issues — inspect `details[i].path` to pinpoint which input was rejected.


    Unknown paths under `/api/v2/` (including the bare `/api/v2` root) answer
    **404** with the same envelope (`code: "NOT_FOUND"`) and a message pointing
    back at this spec — never an HTML error page. Legacy `/api/v1/` paths behave
    the same with v1's lowercase-code envelope (`code: "not_found"`).


    ### Discovery


    Machine-readable entry points, for agents and tooling:

    * `GET /api/v2/openapi.json` — this document (also mirrored at
    `https://octolens.com/openapi.json`)

    * `GET /api/v2/docs` — human-readable reference rendered from it

    * `GET /.well-known/oauth-protected-resource/api/v2` — RFC 9728
    protected-resource metadata for this API, declaring `scopes_supported:
    [read, write, admin]`

    * `GET /.well-known/oauth-protected-resource` — the same metadata for the
    MCP server (`/api/mcp/v2`, OAuth)


    ### Building filter bodies


    Endpoints that accept `simpleFilters` / `advancedFilters` (e.g. `POST
    /api/v2/mentions`, `PATCH /api/v2/feeds/{id}`) take a structured object that
    can be tricky to hand-craft. If you just have a natural-language description
    of what you want ("negative posts about pricing on reddit in the last
    week"), call `POST /api/v2/ai/filter-wizard` with that prompt and it will
    return a ready-to-use filter object you can pass straight through.
servers:
  - url: https://app.octolens.com
    description: Production
  - url: http://localhost:3000
    description: Local development
security:
  - ApiKey:
      - read
paths:
  /api/v2/setup/propose:
    post:
      tags:
        - Setup
      summary: Propose a keyword setup for a scanned draft
      description: >-
        **Beta — behind the `sonar-onboarding` feature flag.** Workspaces the
        flag is off for get `404 FEATURE_DISABLED`.


        Starts the setup agent on a draft returned by `POST /api/v2/setup/scan`:
        it proposes keyword candidates, measures each one's monthly volume,
        scores a sample of real posts, and decides a setup of at most 5 keywords
        with scoping options, feeds and platforms — every decision with its
        `reasons[]` and a plain `explanation`. **Asynchronous:** answers `202`
        with the draft id and status at once; poll `GET
        /api/v2/setup/proposals/{id}` until the status is `ready`,
        `needs_narrowing` or `failed` (typically 40–75 s). A draft with a
        running or current proposal gets that one back (`force: true` starts
        over once it has finished; while a run is live it returns `409
        SETUP_PROPOSAL_RUNNING`). A run that stops mid-way is finished by the
        degradation ladder on the next poll (`propose_interrupted`).
        Rate-limited to 5 new proposals per workspace per day (`429
        RATE_LIMITED`). Creates nothing in the workspace and consumes no mention
        quota.
      operationId: proposeSetup
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetupProposeRequest'
      responses:
        '202':
          description: 202 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SetupProposalStatus'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden (insufficient plan or permissions)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - ApiKey:
            - read
components:
  schemas:
    SetupProposeRequest:
      type: object
      properties:
        draftId:
          description: The `draft_…` id a scan returned.
          example: draft_k3v9x0q2m1
          type: string
          minLength: 1
          maxLength: 64
        force:
          description: Start a new proposal even when the draft already has a current one.
          type: boolean
      required:
        - draftId
    SetupProposalStatus:
      type: object
      properties:
        draftId:
          description: The draft the proposal belongs to — also the `{id}` to poll.
          type: string
        domain:
          type: string
        status:
          $ref: '#/components/schemas/SetupDraftStatus'
          description: >-
            `proposing → measuring → sampling → proposing (the agent) → ready |
            needs_narrowing | failed`. Poll until it is one of the last three.
        stale:
          description: 'The profile was edited after this proposal: propose again.'
          type: boolean
        tier:
          anyOf:
            - $ref: '#/components/schemas/SetupModelTier'
            - type: 'null'
        model:
          anyOf:
            - type: string
            - type: 'null'
        guideVersion:
          type: string
        agentFallback:
          type: boolean
        startedAt:
          anyOf:
            - type: string
            - type: 'null'
        finishedAt:
          anyOf:
            - type: string
            - type: 'null'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/SetupProposeStep'
        degraded:
          type: array
          items:
            $ref: '#/components/schemas/SetupProposeDegradation'
        timeline:
          type: array
          items:
            $ref: '#/components/schemas/SetupTimelineEntry'
        proposal:
          description: Present once the status is `ready` or `needs_narrowing`.
          anyOf:
            - $ref: '#/components/schemas/SetupProposal'
            - type: 'null'
      required:
        - draftId
        - domain
        - status
        - stale
        - tier
        - model
        - guideVersion
        - agentFallback
        - startedAt
        - finishedAt
        - steps
        - degraded
        - timeline
        - proposal
      additionalProperties: false
    ErrorResponse:
      description: >-
        Standard error envelope returned for all non-2xx responses. The `code`
        field is stable — safe to branch on programmatically. `code`, `message`
        and `status` are always present; anything else is additive and per-code
        (`details` on `VALIDATION_ERROR`, `upgradeUrl`/`upgradeCommand` on
        `UPGRADE_REQUIRED`).
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              $ref: '#/components/schemas/ApiErrorCode'
              description: >-
                Machine-readable error code. See `ApiErrorCode` for the full
                list.
              example: NOT_FOUND
            message:
              description: Human-readable error message.
              example: Resource not found
              type: string
            status:
              description: HTTP status code — always matches the response status.
              example: 404
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            details:
              description: >-
                Present on `VALIDATION_ERROR` responses. Contains Zod issues
                describing each failing field (path, code, message).
              type: array
              items: {}
            upgradeUrl:
              description: >-
                Present on `UPGRADE_REQUIRED` responses (additive — see
                `ApiErrorCode`): the URL of the upgrade page for this workspace.
              example: https://app.octolens.com/me/upgrade?src=agents
              type: string
            upgradeCommand:
              description: >-
                Present on `UPGRADE_REQUIRED` responses (additive): the CLI
                command that mints a single-use, already-authenticated link into
                the upgrade page.
              example: octolens upgrade
              type: string
          required:
            - code
            - message
            - status
          additionalProperties: false
      required:
        - error
      additionalProperties: false
    SetupDraftStatus:
      description: >-
        Where the draft is in the setup engine. The scan moves `scanning →
        scanned` (or `failed`).
      type: string
      enum:
        - scanning
        - scanned
        - proposing
        - measuring
        - sampling
        - ready
        - needs_narrowing
        - failed
        - applied
    SetupModelTier:
      type: string
      enum:
        - balanced
        - thorough
    SetupProposeStep:
      type: object
      properties:
        stage:
          $ref: '#/components/schemas/SetupProposeStage'
        status:
          type: string
          enum:
            - running
            - ok
            - failed
            - skipped
        line:
          description: What the wizard prints as a progress line.
          type: string
        startedAt:
          type: string
        ms:
          anyOf:
            - type: integer
              minimum: 0
              maximum: 9007199254740991
            - type: 'null'
      required:
        - stage
        - status
        - line
        - startedAt
        - ms
      additionalProperties: false
    SetupProposeDegradation:
      type: object
      properties:
        code:
          $ref: '#/components/schemas/SetupProposeDegradationCode'
        message:
          type: string
      required:
        - code
        - message
      additionalProperties: false
    SetupTimelineEntry:
      description: 'The "behind the curtain" line: a stage or a tool call and its timing.'
      type: object
      properties:
        kind:
          type: string
          enum:
            - stage
            - tool
        name:
          type: string
        status:
          type: string
        ms:
          anyOf:
            - type: integer
              minimum: 0
              maximum: 9007199254740991
            - type: 'null'
      required:
        - kind
        - name
        - status
        - ms
      additionalProperties: false
    SetupProposal:
      description: >-
        A typed, gated, approvable workspace setup: keywords, scoping options,
        feeds, platforms and whole-setup totals, every decision with its
        reasons.
      type: object
      properties:
        id:
          type: string
          minLength: 1
        draftId:
          type: string
          minLength: 1
        domain:
          type: string
          minLength: 1
        skillVersion:
          type: string
        model:
          type: string
        archetype:
          $ref: '#/components/schemas/SetupArchetype'
        archetypeReason:
          type: string
        keywords:
          type: array
          items:
            $ref: '#/components/schemas/SetupProposedKeyword'
        options:
          default: []
          type: array
          items:
            $ref: '#/components/schemas/SetupScopingOption'
        feeds:
          type: array
          items:
            $ref: '#/components/schemas/SetupProposedFeed'
        platforms:
          type: object
          properties:
            'on':
              type: array
              items:
                type: string
            'off':
              default: []
              type: array
              items:
                type: object
                properties:
                  source:
                    type: string
                  reason:
                    type: string
                required:
                  - source
                  - reason
                additionalProperties: false
          required:
            - 'on'
            - 'off'
          additionalProperties: false
        totals:
          $ref: '#/components/schemas/SetupTotals'
        warnings:
          default: []
          type: array
          items:
            $ref: '#/components/schemas/SetupWarning'
        measurement:
          type: object
          properties:
            measuredAt:
              type: string
            sources:
              type: array
              items:
                type: string
            unmeasured:
              default: []
              type: array
              items:
                type: string
          required:
            - measuredAt
            - sources
            - unmeasured
          additionalProperties: false
        confidence:
          type: number
          minimum: 0
          maximum: 1
        agentFallback:
          description: >-
            True when the setup agent did not deliver and the proposal was
            composed by code from the candidates and the rule tools (reasons
            from rules only, template explanations).
          default: false
          type: boolean
      required:
        - id
        - draftId
        - domain
        - skillVersion
        - model
        - archetype
        - archetypeReason
        - keywords
        - options
        - feeds
        - platforms
        - totals
        - warnings
        - measurement
        - confidence
        - agentFallback
      additionalProperties: false
    ApiErrorCode:
      description: >-
        Stable, machine-readable error code. Grouped as follows:


        **Auth / request shape** — `UNAUTHORIZED` (401), `FORBIDDEN` (403),
        `RATE_LIMITED` (429), `VALIDATION_ERROR` (400 — response carries a
        `details` array with Zod issues), `INTERNAL_ERROR` (500).


        **Agency master-key org selection** — `ORGANIZATION_REQUIRED` (400 — an
        agency master key must pass the `x-octolens-organization-id` header to
        select a target workspace), `FORBIDDEN_ORGANIZATION` (403 — the
        requested organization does not belong to the key's agency).


        **Plan / quota walls** — `UPGRADE_REQUIRED` (403 — an Agents-plan wall:
        a cap was hit (the lifetime search allowance or the monthly mention
        quota), or the endpoint is not part of the Agents plan at all — on that
        plan only search, org/usage/auth introspection, the upgrade link and
        monitoring recommendations are available, and every other endpoint
        answers this code before validating input; the envelope carries two
        ADDITIVE fields alongside the standard three, `upgradeUrl` (the upgrade
        page) and `upgradeCommand` (`octolens upgrade`, which mints a single-use
        authenticated link into that page), so an agent gets an actionable next
        step, not just prose), `QUOTA_EXCEEDED` (402 — the monthly mention quota
        is exhausted on a non-Agents plan; enable flex pricing or upgrade). The
        search routes also send `X-Octolens-Searches-Remaining` /
        `X-Octolens-Mentions-Remaining` response headers on success so clients
        can self-throttle before either wall.


        **Not found (404)** — generic `NOT_FOUND` plus domain-specific variants:
        `FEED_NOT_FOUND`, `KEYWORD_NOT_FOUND`, `KEYWORDS_NOT_FOUND`,
        `POST_NOT_FOUND`, `MENTION_NOT_FOUND`, `SUMMARY_NOT_FOUND`,
        `SEARCH_NOT_FOUND`, `SUGGESTION_NOT_FOUND`, `COMPANY_NOT_FOUND`,
        `ORG_NOT_FOUND`, `SETTINGS_NOT_FOUND`, `NOTIFICATION_NOT_FOUND`,
        `VIEW_NOT_FOUND`, `ALERT_NOT_FOUND`, `ATTENTION_ITEM_NOT_FOUND`. A
        resource id that exists but belongs to another workspace answers the
        SAME not-found as an unused id — existence is never leaked across
        tenants. `FEATURE_DISABLED` (404) — the endpoint is behind a feature
        flag (beta) that is off for this workspace.


        **Authorization (403)** — generic `FORBIDDEN` (the caller is not
        permitted to perform the operation, e.g. insufficient API-key scope).
        There are deliberately NO per-resource 403 codes: cross-workspace access
        reads as the resource's 404.


        **Business-rule violations (400)** — `INVALID_INPUT` (semantically
        invalid arguments), `KEYWORD_LIMIT_EXCEEDED` (plan cap hit),
        `KEYWORD_OPERATOR_SYNTAX` (the keyword carries search-operator syntax —
        quotes, a leading `+`, a `-term`, `AND`/`OR`, a mid-string `*` — which
        is matched literally and would never hit; the envelope adds
        `suggestion`, the stripped string to track instead, plus `alternatives`
        and `excludeWords`), `LAST_ADMIN` (refuses to remove the only admin),
        `ITEM_EXISTS` (duplicate), `INVALID_DOMAIN`, `INVALID_TIMEZONE`,
        `SLACK_NOT_CONNECTED` / `SLACK_CHANNEL_NOT_FOUND` /
        `SLACK_CHANNEL_REQUIRED` (Slack destination validation),
        `WEBHOOK_URL_INVALID` (webhook URL rejected), `NO_DESTINATIONS` /
        `NO_SAMPLE_MENTION` (notification test delivery preconditions),
        `INVALID_DESTINATION` (a destination is missing/mismatched its
        type-specific sub-object, or two share a type) /
        `DESTINATION_INDEX_OUT_OF_RANGE` (feed destination index),
        `FILTER_LIMIT_EXCEEDED` (the effective mention query has more than 50
        caller-authored conditions), `FILTER_CONDITION_LIMIT_EXCEEDED` (a saved
        feed would have more than 50 conditions across both filter sides),
        `ENGAGEMENT_METRICS_DISABLED` (engagement filtering is unavailable in
        this deployment), and `ATTENTION_ACTION_NOT_APPLY` (the attention item's
        action is not an `apply`).


        **Conflict (409)** — `FEED_FILTER_WRITE_CONFLICT` means another writer
        changed the saved feed after it was read; fetch the latest feed and
        retry the filter update.


        **Operation failures (500)** — `ENGAGE_FAILED` /
        `RELEVANCE_UPDATE_FAILED` carry a specific code so clients can
        distinguish a failed mutation from a generic `INTERNAL_ERROR`.
      type: string
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - RATE_LIMITED
        - VALIDATION_ERROR
        - INTERNAL_ERROR
        - ORGANIZATION_REQUIRED
        - FORBIDDEN_ORGANIZATION
        - MASTER_KEY_REQUIRED
        - AGENCY_INACTIVE
        - NOT_AN_AGENCY_WORKSPACE
        - AGENCY_NOT_FOUND
        - WORKSPACE_NOT_IN_AGENCY
        - ANCHOR_WORKSPACE
        - AGENCY_WORKSPACE_LIMIT
        - INVITE_FANOUT_TOO_LARGE
        - SUPERADMIN_REMOVE_FORBIDDEN
        - USER_REQUIRED
        - FLEX_REQUIRES_CAP
        - FLEX_REQUIRES_BUDGET
        - AGENCY_BILLING_NOT_CONFIGURED
        - WORKSPACE_CREATE_FAILED
        - WORKSPACE_SETUP_FAILED
        - WORKSPACE_ACCESS_FAILED
        - UPGRADE_REQUIRED
        - QUOTA_EXCEEDED
        - NOT_FOUND
        - FEED_NOT_FOUND
        - KEYWORD_NOT_FOUND
        - KEYWORDS_NOT_FOUND
        - POST_NOT_FOUND
        - MENTION_NOT_FOUND
        - SUMMARY_NOT_FOUND
        - SEARCH_NOT_FOUND
        - SUGGESTION_NOT_FOUND
        - COMPANY_NOT_FOUND
        - ORG_NOT_FOUND
        - SETTINGS_NOT_FOUND
        - NOTIFICATION_NOT_FOUND
        - VIEW_NOT_FOUND
        - ALERT_NOT_FOUND
        - ATTENTION_ITEM_NOT_FOUND
        - FEATURE_DISABLED
        - MENTION_KEYWORD_UNKNOWN
        - EXPORT_LINK_UNAVAILABLE
        - INVALID_INPUT
        - KEYWORD_LIMIT_EXCEEDED
        - KEYWORD_OPERATOR_SYNTAX
        - LAST_ADMIN
        - ITEM_EXISTS
        - INVALID_DOMAIN
        - INVALID_TIMEZONE
        - SLACK_NOT_CONNECTED
        - SLACK_CHANNEL_NOT_FOUND
        - SLACK_CHANNEL_REQUIRED
        - WEBHOOK_URL_INVALID
        - NO_DESTINATIONS
        - NO_SAMPLE_MENTION
        - INVALID_DESTINATION
        - DESTINATION_INDEX_OUT_OF_RANGE
        - FILTER_LIMIT_EXCEEDED
        - FILTER_CONDITION_LIMIT_EXCEEDED
        - ENGAGEMENT_METRICS_DISABLED
        - ATTENTION_ACTION_NOT_APPLY
        - FEED_FILTER_WRITE_CONFLICT
        - ENGAGE_FAILED
        - RELEVANCE_UPDATE_FAILED
    SetupProposeStage:
      type: string
      enum:
        - candidates
        - measure
        - sample
        - agent
        - validate
    SetupProposeDegradationCode:
      description: >-
        The degradation ladder: `candidates_failed` → the deterministic domain
        fallback; `measurement_failed` → the prior band, every keyword
        unverified; `sample_failed` → only the brand feed is visible;
        `agent_failed` / `agent_budget_exceeded` / `agent_invalid` → the
        code-only composition (`agentFallback`); `propose_interrupted` → the run
        died mid-way and was finished by the code-only composition from what it
        had (prior band, brand feed only).
      type: string
      enum:
        - candidates_failed
        - measurement_failed
        - sample_failed
        - agent_failed
        - agent_budget_exceeded
        - agent_invalid
        - propose_interrupted
    SetupArchetype:
      description: >-
        The brand route (R3): distinctive name × volume band (`quiet` ≤ 30/mo,
        `standard` 31–300, `high_volume` > 300), or a confirmed common word
        (`common_word`; `hard_case` when its relevant volume is over ~2,500/mo).
      type: string
      enum:
        - quiet
        - standard
        - high_volume
        - common_word
        - hard_case
    SetupProposedKeyword:
      type: object
      properties:
        ref:
          description: Stable id inside the proposal, e.g. `kw_1`.
          type: string
          minLength: 1
        keyword:
          type: string
        tag:
          $ref: '#/components/schemas/KeywordTag'
        route:
          $ref: '#/components/schemas/SetupKeywordRoute'
        exactMatch:
          default: true
          type: boolean
        caseSensitive:
          default: false
          type: boolean
        additionalTerms:
          default: []
          type: array
          items:
            type: string
        additionalTermsLogic:
          default: any
          type: string
          enum:
            - any
            - all
        excludeWords:
          default: []
          type: array
          items:
            type: string
        wildcardExcludeWords:
          default: []
          type: array
          items:
            type: string
        excludeAuthors:
          default: []
          type: array
          items:
            type: string
        platforms:
          description: >-
            `null` = every plan-allowed source; an array narrows this keyword
            (P2).
          default: null
          anyOf:
            - type: array
              items:
                $ref: '#/components/schemas/KeywordPlatform'
            - type: 'null'
        context:
          description: >-
            X5: one sentence, ≤ 200 chars, naming the company and what this
            catches.
          type: string
        volume:
          $ref: '#/components/schemas/SetupKeywordVolume'
        sample:
          default: null
          anyOf:
            - $ref: '#/components/schemas/SetupKeywordSample'
            - type: 'null'
        feedRef:
          type: string
          minLength: 1
        explanation:
          description: >-
            Plain language for the wizard: 1–3 sentences, second person, no rule
            ids.
          type: string
        reasons:
          default: []
          type: array
          items:
            $ref: '#/components/schemas/SetupReason'
        confidence:
          type: number
          minimum: 0
          maximum: 1
        flags:
          default: []
          type: array
          items:
            type: string
            enum:
              - high_volume
              - must_split
              - confirm_handle
              - unverified
              - sparse
              - job_spam_wall
              - language_wall
              - generic_anchor_rejected
        selectedByDefault:
          default: true
          type: boolean
        optionGroup:
          type: string
        approvals:
          description: >-
            Per keyword setting: `quiet` (applied at creation) or `cleanup`
            (proposed after collection).
          default: []
          type: array
          items:
            $ref: '#/components/schemas/SetupSettingApproval'
        approval:
          $ref: '#/components/schemas/SetupApprovalBySetting'
          description: >-
            Per-setting approval class (E5). A setting not listed takes the
            engine default: the brand's additional terms (`own_brand`) are
            `cleanup` — they arrive through the Clean up — and everything else
            is `quiet`.
          default: {}
      required:
        - ref
        - keyword
        - tag
        - route
        - exactMatch
        - caseSensitive
        - additionalTerms
        - additionalTermsLogic
        - excludeWords
        - wildcardExcludeWords
        - excludeAuthors
        - platforms
        - context
        - volume
        - sample
        - feedRef
        - explanation
        - reasons
        - confidence
        - flags
        - selectedByDefault
        - approvals
        - approval
      additionalProperties: false
    SetupScopingOption:
      type: object
      properties:
        id:
          type: string
          minLength: 1
        keywordRef:
          type: string
          minLength: 1
        label:
          type: string
          minLength: 1
        description:
          type: string
        explanation:
          description: >-
            Plain language for the wizard: 1–3 sentences, second person, no rule
            ids.
          default: ''
          type: string
        additionalTerms:
          type: array
          items:
            type: string
        projectedMonthly:
          type: number
          minimum: 0
        selectedByDefault:
          type: boolean
      required:
        - id
        - keywordRef
        - label
        - description
        - explanation
        - additionalTerms
        - projectedMonthly
        - selectedByDefault
      additionalProperties: false
    SetupProposedFeed:
      type: object
      properties:
        ref:
          type: string
          minLength: 1
        name:
          type: string
        purpose:
          $ref: '#/components/schemas/SetupFeedPurpose'
        keywordRefs:
          type: array
          items:
            type: string
        contentTerms:
          type: array
          items:
            type: string
        visible:
          default: true
          type: boolean
        mergedIntoRef:
          type: string
        source:
          description: >-
            `setup` = the setup agent's per-keyword feed (F1–F3); `mentions` = a
            topic feed found in the good mentions of the first collection (needs
            the Content filter, E8).
          default: setup
          type: string
          enum:
            - setup
            - mentions
        gate:
          type: object
          properties:
            weeklySample:
              type: number
              minimum: 0
            relevanceRate:
              type: number
              minimum: 0
              maximum: 1
            dominantClusterShare:
              type: number
              minimum: 0
              maximum: 1
            duplicateOf:
              type: string
          required:
            - weeklySample
            - relevanceRate
            - dominantClusterShare
          additionalProperties: false
      required:
        - ref
        - name
        - purpose
        - keywordRefs
        - visible
        - source
        - gate
      additionalProperties: false
    SetupTotals:
      type: object
      properties:
        projectedMonthly:
          type: number
          minimum: 0
        dedupedMonthly:
          type: number
          minimum: 0
        allowance:
          default: 5000
          type: number
          exclusiveMinimum: 0
        band:
          type: string
          enum:
            - pass
            - confirm
            - blocked
        narrowingApplied:
          default: []
          type: array
          items:
            type: object
            properties:
              step:
                type: string
                enum:
                  - drop_industry_anchor_set
                  - tighten_anchors
                  - source_off
                  - split
              target:
                type: string
              savedMonthly:
                anyOf:
                  - type: number
                    minimum: 0
                  - type: 'null'
              reason:
                type: string
            required:
              - step
              - target
              - savedMonthly
              - reason
            additionalProperties: false
      required:
        - projectedMonthly
        - dedupedMonthly
        - allowance
        - band
        - narrowingApplied
      additionalProperties: false
    SetupWarning:
      type: object
      properties:
        code:
          type: string
        keyword:
          type: string
        message:
          type: string
      required:
        - code
        - message
      additionalProperties: false
    KeywordTag:
      description: >-
        Classification of what this keyword represents. Used to route posts to
        brand / competitor / industry-context workflows and pick the right AI
        prompt.
      example: own_brand
      type: string
      enum:
        - own_brand
        - competitor
        - industry_term
    SetupKeywordRoute:
      type: string
      enum:
        - bare
        - anchored
        - domain_variant
        - handle_variant
        - brand_copy
    KeywordPlatform:
      description: >-
        Platform a text or subreddit keyword can monitor. Review sources are
        configured through Settings → Platforms → Customer reviews instead.
      type: string
      enum:
        - dev
        - github
        - hackernews
        - linkedin
        - producthunt
        - reddit
        - stackoverflow
        - twitter
        - youtube
        - tiktok
        - medium
        - reddit_comment
        - bluesky
        - newsletter
        - podcasts
        - news
        - firehose
        - instagram
    SetupKeywordVolume:
      description: A range and a band — never a point estimate.
      type: object
      properties:
        low:
          type: number
          minimum: 0
        high:
          type: number
          minimum: 0
        band:
          $ref: '#/components/schemas/SetupVolumeBand'
        basis:
          description: >-
            `analog` = the string is already tracked bare elsewhere, `sampled` =
            capped counts at adaptive windows, `prior` = no measurement,
            `sparse` = under 10 raw matches.
          type: string
          enum:
            - analog
            - sampled
            - prior
            - sparse
        window:
          type: string
          enum:
            - 30d
            - 7d
            - 1d
        perSource:
          default: []
          type: array
          items:
            type: object
            properties:
              source:
                type: string
              count:
                type: number
                minimum: 0
              window:
                type: string
                enum:
                  - 30d
                  - 7d
                  - 1d
              factor:
                type: number
                exclusiveMinimum: 0
              low:
                type: number
                minimum: 0
              high:
                type: number
                minimum: 0
            required:
              - source
              - count
              - window
              - factor
              - low
              - high
            additionalProperties: false
        unverifiedSources:
          default: []
          type: array
          items:
            type: string
        unmeasuredSources:
          default: []
          type: array
          items:
            type: string
      required:
        - low
        - high
        - band
        - basis
        - window
        - perSource
        - unverifiedSources
        - unmeasuredSources
      additionalProperties: false
    SetupKeywordSample:
      type: object
      properties:
        scored:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        relevanceRate:
          type: number
          minimum: 0
          maximum: 1
        clusters:
          default: []
          type: array
          items:
            type: object
            properties:
              label:
                type: string
              share:
                type: number
                minimum: 0
                maximum: 1
            required:
              - label
              - share
            additionalProperties: false
      required:
        - scored
        - relevanceRate
        - clusters
      additionalProperties: false
    SetupReason:
      type: object
      properties:
        basis:
          description: >-
            `rule` = a coded rule (ref is its catalogue id), `guide` = a guide
            section, `measurement` = a measured number, `judgement` = the
            agent's labelled judgement.
          type: string
          enum:
            - rule
            - guide
            - measurement
            - judgement
        ref:
          description: Rule id, guide anchor or measurement name.
          type: string
          minLength: 1
        text:
          description: One short sentence, quotable verbatim.
          type: string
          minLength: 1
      required:
        - basis
        - ref
        - text
      additionalProperties: false
    SetupSettingApproval:
      type: object
      properties:
        setting:
          $ref: '#/components/schemas/SetupKeywordSettingName'
        terms:
          description: The values of the setting this class covers.
          type: array
          items:
            type: string
        approval:
          $ref: '#/components/schemas/SetupApprovalClass'
        reason:
          description: Why this class (internal, never rendered).
          type: string
      required:
        - setting
        - terms
        - approval
        - reason
      additionalProperties: false
    SetupApprovalBySetting:
      type: object
      properties:
        tag:
          $ref: '#/components/schemas/SetupApprovalClass'
        exactMatch:
          $ref: '#/components/schemas/SetupApprovalClass'
        additionalTerms:
          $ref: '#/components/schemas/SetupApprovalClass'
        excludeWords:
          $ref: '#/components/schemas/SetupApprovalClass'
        wildcardExcludeWords:
          $ref: '#/components/schemas/SetupApprovalClass'
        excludeAuthors:
          $ref: '#/components/schemas/SetupApprovalClass'
        platforms:
          $ref: '#/components/schemas/SetupApprovalClass'
        context:
          $ref: '#/components/schemas/SetupApprovalClass'
      additionalProperties: false
    SetupFeedPurpose:
      description: >-
        `tag` is kept parseable only so the gate can NAME the refusal: F4 was
        retired on 16 Sep — every feed is the agent's (F1–F3), a tag feed fails
        F1.
      type: string
      enum:
        - brand
        - competitors
        - topic
        - merged
        - tag
    SetupVolumeBand:
      description: 'R2 bands on monthly volume: ≤ 30 · 31–300 · > 300 · > ~2,500.'
      type: string
      enum:
        - quiet
        - standard
        - high
        - top_end
    SetupKeywordSettingName:
      type: string
      enum:
        - tag
        - exactMatch
        - additionalTerms
        - excludeWords
        - wildcardExcludeWords
        - excludeAuthors
        - platforms
        - context
    SetupApprovalClass:
      description: >-
        `quiet` = written at apply with no approval; `cleanup` = proposed by the
        Clean up after the first collection and written by its one click.
      type: string
      enum:
        - quiet
        - cleanup
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        Clerk API key. Create one in Settings → API Keys. Pass as
        `Authorization: Bearer <key>`. Keys carry a scope — `read`, `write`
        (implies read) or `admin` (implies write) — chosen when the key is
        minted. Each operation's `security` requirement (and its
        `x-required-scope` extension) names the minimum scope it needs; request
        the least-privileged key that covers the operations you call.
      x-key-scopes:
        read: >-
          Read-only access: list/query mentions, keywords, feeds, analytics, org
          metadata.
        write: >-
          Read plus mutations: create/update/delete keywords, feeds, tags,
          filters, notifications. Implies `read`.
        admin: >-
          Write plus org administration: member management, invitations, agency
          workspaces. Implies `write`.

````

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