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

# Clean up, keep everything, or undo

> Takes the clean-up decision (`cleanup` | `keep` | `undo`) and writes the feeds. `400 MENTIONS_PREVIEW_NOT_READY` while collecting.



## OpenAPI

````yaml https://app.octolens.com/api/v2/openapi.json post /api/v2/onboarding/mentions/decision
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


    Public 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.
    Internal UI endpoints (`x-internal: true`) require a member session in the
    active workspace instead, and a trusted browser origin for writes.


    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/onboarding/mentions/decision:
    post:
      tags:
        - Onboarding
      summary: Clean up, keep everything, or undo
      description: >-
        Takes the clean-up decision (`cleanup` | `keep` | `undo`) and writes the
        feeds. `400 MENTIONS_PREVIEW_NOT_READY` while collecting.
      operationId: decideOnboardingMentions
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingDecisionRequest'
      responses:
        '200':
          description: 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardingDecisionResponse'
        '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:
            - write
components:
  schemas:
    OnboardingDecisionRequest:
      type: object
      properties:
        decision:
          type: string
          enum:
            - cleanup
            - keep
            - undo
        ifRevision:
          description: >-
            Refuse with 409 ONBOARDING_REVISION_CHANGED unless the state is
            still at this revision.
          type: string
          minLength: 1
          maxLength: 128
      required:
        - decision
    OnboardingDecisionResponse:
      type: object
      properties:
        alreadyDone:
          type: boolean
        result:
          $ref: '#/components/schemas/SetupCleanupResult'
        undo:
          anyOf:
            - type: object
              properties:
                until:
                  description: ISO-8601.
                  type: string
              required:
                - until
              additionalProperties: false
            - type: 'null'
        state:
          $ref: '#/components/schemas/OnboardingState'
      required:
        - alreadyDone
        - result
        - undo
        - state
      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
    SetupCleanupResult:
      description: The outcome of the Clean up commit, Keep everything, or an undo.
      type: object
      properties:
        draftId:
          type: string
        decision:
          $ref: '#/components/schemas/SetupCleanupDecision'
        decidedAt:
          type: string
        undoneAt:
          default: null
          anyOf:
            - type: string
            - type: 'null'
        removedPosts:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        restoredPosts:
          default: 0
          type: integer
          minimum: 0
          maximum: 9007199254740991
        summary:
          type: array
          items:
            $ref: '#/components/schemas/SetupCleanupSummaryLine'
        quietSettings:
          description: The settings apply wrote without asking, one plain line each.
          type: array
          items:
            type: string
        feeds:
          type: array
          items:
            $ref: '#/components/schemas/SetupCreatedFeed'
        feedsSkipped:
          default: []
          type: array
          items:
            type: object
            properties:
              ref:
                type: string
              name:
                type: string
              reason:
                type: string
            required:
              - ref
              - name
              - reason
            additionalProperties: false
        feedsStatus:
          description: >-
            `pending` while the feeds are still being planned and written after
            the decision; `ready` once `feeds` is final.
          default: ready
          type: string
          enum:
            - ready
            - pending
        undoAvailableUntil:
          anyOf:
            - type: string
            - type: 'null'
        undoSkipped:
          description: >-
            Undo skipped these because they changed after the Clean up (kept as
            they are now).
          default:
            keywordIds: []
            postIds: []
          type: object
          properties:
            keywordIds:
              type: array
              items:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
            postIds:
              type: array
              items:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
          required:
            - keywordIds
            - postIds
          additionalProperties: false
      required:
        - draftId
        - decision
        - decidedAt
        - undoneAt
        - removedPosts
        - restoredPosts
        - summary
        - quietSettings
        - feeds
        - feedsSkipped
        - feedsStatus
        - undoAvailableUntil
        - undoSkipped
      additionalProperties: false
    OnboardingState:
      type: object
      properties:
        step:
          $ref: '#/components/schemas/OnboardingStepId'
        status:
          $ref: '#/components/schemas/OnboardingStatus'
        revision:
          description: >-
            Hash of the onboarding timestamps, the draft's updatedAt and the
            marker's tail (`--if-revision`).
          type: string
        engine:
          $ref: '#/components/schemas/OnboardingEngine'
        handle:
          $ref: '#/components/schemas/OnboardingHandle'
        completed:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingStepId'
        reviewed:
          description: >-
            True after manual review approved the workspace (the post-review
            tail).
          type: boolean
        pendingReview:
          type: boolean
        trial:
          description: Null until the workspace exists (keywords confirmed).
          anyOf:
            - $ref: '#/components/schemas/OnboardingTrial'
            - type: 'null'
        view:
          description: The current step's section is set; the others may be null.
          type: object
          properties:
            website:
              anyOf:
                - $ref: '#/components/schemas/OnboardingWebsiteView'
                - type: 'null'
            keywords:
              anyOf:
                - $ref: '#/components/schemas/OnboardingKeywordsView'
                - type: 'null'
            mentions:
              anyOf:
                - $ref: '#/components/schemas/OnboardingMentionsView'
                - type: 'null'
            feeds:
              anyOf:
                - $ref: '#/components/schemas/OnboardingFeedsView'
                - type: 'null'
            slack:
              anyOf:
                - $ref: '#/components/schemas/OnboardingSlackView'
                - type: 'null'
            mcp:
              anyOf:
                - $ref: '#/components/schemas/OnboardingMcpView'
                - type: 'null'
          required:
            - website
            - keywords
            - mentions
            - feeds
            - slack
            - mcp
          additionalProperties: false
        next:
          description: Null when `status` is `done`.
          anyOf:
            - $ref: '#/components/schemas/OnboardingNext'
            - type: 'null'
      required:
        - step
        - status
        - revision
        - engine
        - handle
        - completed
        - reviewed
        - pendingReview
        - trial
        - view
        - next
      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.


        **Manual workspace review** — `WORKSPACE_PENDING_REVIEW` (403 — the
        workspace is waiting for a review by the Octolens team; every endpoint
        except `GET /api/v2/auth` answers this code until it is approved, and
        the very next request after approval succeeds; retry later).


        **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. `SETUP_DECISION_PENDING` means the onboarding
        mentions step is still removing the noise; step 3 removes it
        automatically, so try again in a few seconds.


        **Onboarding (`/api/v2/onboarding/*`)** — `MENTIONS_PREVIEW_NOT_READY`
        (400, poll), `MENTIONS_NOT_VIEWED` (409), `KEYWORDS_STEP_INVALID` (400),
        `ONBOARDING_STEP_NOT_CURRENT` (409), `ONBOARDING_REVISION_CHANGED`
        (409), `ONBOARDING_IN_PROGRESS` (409, retry in 5 s),
        `SETUP_DRAFT_NOT_FOUND` (404), `PAYWALL` / `SANDBOX_WORKSPACE` /
        `SECOND_FREE_TRIAL_FORBIDDEN` (403, with `upgradeUrl`),
        `AUTH_LOOKUP_UNAVAILABLE` (503, retry), `FEED_NOT_ONBOARDING` (400 — a
        Slack route for a feed the setup did not make) and `NOT_IMPLEMENTED`
        (501 — the route is not live yet).


        **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
        - MEMBER_REQUIRED
        - BRIEF_DISABLED
        - INVALID_BRIEF_LINK
        - PREFERENCE_CHANGED
        - BRIEF_NOT_FOUND
        - INVALID_BRIEF_WINDOW
        - INVALID_RECIPIENT_COUNT
        - 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
        - WORKSPACE_PENDING_REVIEW
        - SETUP_DECISION_PENDING
        - MENTIONS_PREVIEW_NOT_READY
        - MENTIONS_NOT_VIEWED
        - KEYWORDS_STEP_INVALID
        - ONBOARDING_STEP_NOT_CURRENT
        - ONBOARDING_REVISION_CHANGED
        - ONBOARDING_IN_PROGRESS
        - SETUP_DRAFT_NOT_FOUND
        - PAYWALL
        - SANDBOX_WORKSPACE
        - SECOND_FREE_TRIAL_FORBIDDEN
        - AUTH_LOOKUP_UNAVAILABLE
        - NOT_IMPLEMENTED
        - FEED_NOT_ONBOARDING
        - 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
    SetupCleanupDecision:
      description: >-
        `cleanup` = the proposal was applied and the posts it covers were
        removed; `keep` = Keep everything (nothing changed); `undone` = a Clean
        up that was reversed.
      type: string
      enum:
        - cleanup
        - keep
        - undone
    SetupCleanupSummaryLine:
      type: object
      properties:
        keywordRef:
          type: string
        keyword:
          type: string
        setting:
          $ref: '#/components/schemas/SetupKeywordSettingName'
        text:
          description: One plain line for the done state.
          type: string
        removedPosts:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        values:
          description: >-
            The skipped words / authors, the must-also-mention terms, or the
            platforms turned off.
          type: array
          items:
            type: string
        logic:
          description: 'additionalTerms only: any or all of the terms must appear.'
          type: string
          enum:
            - any
            - all
        split:
          description: 'additionalTerms only: a busy brand split by product area.'
          type: array
          items:
            type: object
            properties:
              label:
                type: string
              terms:
                type: array
                items:
                  type: string
            required:
              - label
              - terms
            additionalProperties: false
      required:
        - keywordRef
        - keyword
        - setting
        - text
        - removedPosts
      additionalProperties: false
    SetupCreatedFeed:
      type: object
      properties:
        ref:
          type: string
        name:
          type: string
        purpose:
          $ref: '#/components/schemas/SetupFeedPurpose'
        source:
          type: string
          enum:
            - setup
            - mentions
        viewId:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        created:
          description: '`false` when the feed already existed (idempotent re-run).'
          type: boolean
      required:
        - ref
        - name
        - purpose
        - source
        - viewId
        - created
      additionalProperties: false
    OnboardingStepId:
      description: >-
        Where the onboarding is: `account → website → keywords → mentions →
        feeds → slack → mcp → complete`.
      type: string
      enum:
        - account
        - website
        - keywords
        - mentions
        - feeds
        - slack
        - mcp
        - complete
    OnboardingStatus:
      description: >-
        `needs_input` = a decision or value is wanted · `running` = server work
        in flight, poll · `needs_human` = a person must act (open a link, read
        an email) · `pending_review` = the workspace waits for manual review ·
        `done` = nothing left to do.
      type: string
      enum:
        - needs_input
        - running
        - needs_human
        - pending_review
        - done
    OnboardingEngine:
      type: string
      enum:
        - sonar
        - classic
    OnboardingHandle:
      type: object
      properties:
        orgId:
          anyOf:
            - type: string
            - type: 'null'
        draftId:
          anyOf:
            - type: string
            - type: 'null'
        proposalId:
          anyOf:
            - type: string
            - type: 'null'
      required:
        - orgId
        - draftId
        - proposalId
      additionalProperties: false
    OnboardingTrial:
      type: object
      properties:
        plan:
          type: string
          const: Free_trial
        allowance:
          description: Trial mention allowance.
          type: integer
          minimum: 0
          maximum: 9007199254740991
        startsAt:
          description: >-
            `on_finish` until setup finishes (`onboardingFinishedAt`), then the
            ISO start.
          anyOf:
            - type: string
              const: on_finish
            - description: ISO-8601.
              type: string
        endsAt:
          description: Null until the trial clock started (design D11).
          anyOf:
            - description: ISO-8601.
              type: string
            - type: 'null'
      required:
        - plan
        - allowance
        - startsAt
        - endsAt
      additionalProperties: false
    OnboardingWebsiteView:
      type: object
      properties:
        domain:
          type: string
        scanSteps:
          type: array
          items:
            $ref: '#/components/schemas/ScanStep'
        profile:
          anyOf:
            - $ref: '#/components/schemas/ScanProfile'
            - type: 'null'
        confirmed:
          description: The profile was confirmed (propose started).
          type: boolean
      required:
        - domain
        - scanSteps
        - profile
        - confirmed
      additionalProperties: false
    OnboardingKeywordsView:
      type: object
      properties:
        proposalStatus:
          $ref: '#/components/schemas/SetupDraftStatus'
          description: >-
            `proposing → measuring (the first search pass) → sampling →
            proposing (the agent) → ready | failed`. Poll until it is one of the
            last two (`needs_narrowing` is a legacy terminal status no run
            produces any more).
        archetype:
          anyOf:
            - type: string
            - type: 'null'
        openingLine:
          anyOf:
            - type: string
            - type: 'null'
        keywords:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingKeyword'
        slots:
          type: object
          properties:
            used:
              type: integer
              minimum: 0
              maximum: 9007199254740991
            of:
              type: integer
              exclusiveMinimum: 0
              maximum: 9007199254740991
          required:
            - used
            - of
          additionalProperties: false
        problems:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingKeywordsProblem'
      required:
        - proposalStatus
        - archetype
        - openingLine
        - keywords
        - slots
        - problems
      additionalProperties: false
    OnboardingMentionsView:
      description: Step 3 ("Your mentions") as the web wizard reads it.
      type: object
      properties:
        mode:
          type: string
          const: setup
        draftId:
          type: string
        status:
          type: string
          enum:
            - collecting
            - ready
            - empty
        stage:
          type: string
          enum:
            - collecting
            - reading
            - optimizing
            - ready
        found:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        collectionEnd:
          anyOf:
            - type: string
              enum:
                - runs_finished
                - sources_accounted
                - idle
                - landed
                - stale
            - type: 'null'
        progress:
          type: number
          minimum: 0
          maximum: 100
        pairs:
          description: >-
            The collecting stage's measure: keyword × platform searches the
            worker settled (posts in, nothing, or failed) of how many the setup
            runs; every pair is settled once collecting is over. `expected` is 0
            when there is no applied setup to count.
          type: object
          properties:
            expected:
              type: integer
              minimum: 0
              maximum: 9007199254740991
            settled:
              type: integer
              minimum: 0
              maximum: 9007199254740991
          required:
            - expected
            - settled
          additionalProperties: false
        tiles:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
                enum:
                  - twitter
                  - reddit
                  - hackernews
                  - bluesky
                  - youtube
                  - linkedin
              label:
                type: string
              state:
                type: string
                enum:
                  - done
                  - busy
                  - waiting
                  - none
            required:
              - source
              - label
              - state
            additionalProperties: false
        sources:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
              label:
                type: string
              count:
                type: integer
                minimum: 0
                maximum: 9007199254740991
              state:
                type: string
                enum:
                  - arriving
                  - done
                  - pending
                  - none
              atLeast:
                type: boolean
              failed:
                type: boolean
            required:
              - source
              - label
              - count
              - state
            additionalProperties: false
        posts:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingMention'
        total:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        aboutYou:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        notAboutYou:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        proposal:
          anyOf:
            - type: object
              properties:
                lines:
                  type: array
                  items:
                    type: object
                    properties:
                      keyword:
                        type: string
                      setting:
                        type: string
                        enum:
                          - additionalTerms
                          - excludeWords
                          - excludeAuthors
                          - platforms
                      values:
                        type: array
                        items:
                          type: string
                      logic:
                        type: string
                        enum:
                          - any
                          - all
                      split:
                        type: array
                        items:
                          type: object
                          properties:
                            label:
                              type: string
                            terms:
                              type: array
                              items:
                                type: string
                          required:
                            - label
                            - terms
                          additionalProperties: false
                      text:
                        type: string
                    required:
                      - keyword
                      - setting
                      - values
                      - text
                    additionalProperties: false
              required:
                - lines
              additionalProperties: false
            - type: 'null'
        decision:
          anyOf:
            - type: string
              enum:
                - cleanup
                - keep
            - type: 'null'
        cleanedUp:
          anyOf:
            - type: object
              properties:
                removed:
                  type: integer
                  minimum: 0
                  maximum: 9007199254740991
                changes:
                  type: array
                  items:
                    type: object
                    properties:
                      keyword:
                        type: string
                      text:
                        type: string
                      setting:
                        type: string
                        enum:
                          - additionalTerms
                          - excludeWords
                          - excludeAuthors
                          - platforms
                      values:
                        type: array
                        items:
                          type: string
                      logic:
                        type: string
                        enum:
                          - any
                          - all
                      split:
                        type: array
                        items:
                          type: object
                          properties:
                            label:
                              type: string
                            terms:
                              type: array
                              items:
                                type: string
                          required:
                            - label
                            - terms
                          additionalProperties: false
                    required:
                      - keyword
                      - text
                    additionalProperties: false
                undoAvailableUntil:
                  anyOf:
                    - type: string
                    - type: 'null'
              required:
                - removed
                - changes
                - undoAvailableUntil
              additionalProperties: false
            - type: 'null'
        canContinueWithoutDecision:
          type: boolean
        proposalPending:
          description: >-
            Ready, but the automatic noise removal may still land in the
            background: the numbers update when it does. Absent = false. A
            client waiting for step 3 to finish keeps polling while it is true.
          type: boolean
        allowance:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        keywords:
          type: array
          items:
            type: string
      required:
        - mode
        - draftId
        - status
        - stage
        - found
        - collectionEnd
        - progress
        - pairs
        - tiles
        - sources
        - posts
        - total
        - aboutYou
        - notAboutYou
        - proposal
        - decision
        - cleanedUp
        - canContinueWithoutDecision
        - allowance
        - keywords
      additionalProperties: false
    OnboardingFeedsView:
      type: object
      properties:
        state:
          type: string
          enum:
            - waiting
            - ready
        feeds:
          type: array
          items:
            type: object
            properties:
              'n':
                description: The board number (1-based).
                type: integer
                exclusiveMinimum: 0
                maximum: 9007199254740991
              id:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
              name:
                type: string
              kind:
                type: string
              mentions:
                type: integer
                minimum: 0
                maximum: 9007199254740991
              description:
                type: string
            required:
              - 'n'
              - id
              - name
              - kind
              - mentions
              - description
            additionalProperties: false
      required:
        - state
        - feeds
      additionalProperties: false
    OnboardingSlackView:
      type: object
      properties:
        connected:
          type: boolean
        team:
          anyOf:
            - type: string
            - type: 'null'
        digest:
          description: The daily email digest is on (it is for every new workspace).
          type: boolean
        feeds:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingSlackFeed'
        handoff:
          description: >-
            The last unexpired, unredeemed `slack_install` link, or null (mint
            one with `POST /handoff`). Always null for a read-only API key: the
            code installs Slack, a write.
          anyOf:
            - $ref: '#/components/schemas/OnboardingHandoffLink'
            - type: 'null'
      required:
        - connected
        - team
        - digest
        - feeds
        - handoff
      additionalProperties: false
    OnboardingMcpView:
      type: object
      properties:
        endpoint:
          example: https://app.octolens.com/api/mcp/v2
          description: >-
            The MCP v2 server (`<origin>/api/mcp/v2`) the CLI writes into the
            tool's config. URL only: the tool signs in to Octolens with OAuth on
            first use. No API key is ever minted for MCP (OCT-1814).
          type: string
        auth:
          type: string
          const: oauth
        connected:
          type: boolean
        connectedAt:
          anyOf:
            - description: ISO-8601.
              type: string
            - type: 'null'
      required:
        - endpoint
        - auth
        - connected
        - connectedAt
      additionalProperties: false
    OnboardingNext:
      type: object
      properties:
        kind:
          $ref: '#/components/schemas/OnboardingNextActionKind'
        step:
          $ref: '#/components/schemas/OnboardingStepId'
        reason:
          description: >-
            Stable machine reason, e.g. `scan_running`, `profile_unconfirmed`,
            `keywords_unconfirmed`.
          type: string
        retryAfterSeconds:
          anyOf:
            - type: integer
              minimum: 0
              maximum: 9007199254740991
            - type: 'null'
        url:
          anyOf:
            - type: string
            - type: 'null'
        default:
          description: 'For `decide`: the default answer, when there is one.'
          anyOf:
            - type: boolean
            - type: 'null'
      required:
        - kind
        - step
        - reason
        - retryAfterSeconds
        - url
        - default
      additionalProperties: false
    SetupKeywordSettingName:
      type: string
      enum:
        - tag
        - exactMatch
        - additionalTerms
        - excludeWords
        - wildcardExcludeWords
        - excludeAuthors
        - platforms
        - context
    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
    ScanStep:
      type: object
      properties:
        step:
          type: string
          enum:
            - cache
            - enrichment
            - basic_info
            - navigation
            - product_areas
        status:
          type: string
          enum:
            - pending
            - ok
            - failed
            - skipped
        line:
          type: string
        ms:
          type: integer
          minimum: 0
          maximum: 9007199254740991
      required:
        - step
        - status
        - line
        - ms
      additionalProperties: false
    ScanProfile:
      description: >-
        What the domain scan learned about the company. Every field the wizard
        renders is editable.
      type: object
      properties:
        name:
          type: string
          maxLength: 191
        description:
          type: string
          maxLength: 2000
        industry:
          type: string
          maxLength: 191
        website:
          type: string
          maxLength: 300
        domainVariants:
          maxItems: 10
          type: array
          items:
            type: string
            maxLength: 255
        productAreas:
          maxItems: 8
          type: array
          items:
            $ref: '#/components/schemas/ScanProductAreaOutput'
        ownAccounts:
          maxItems: 30
          type: array
          items:
            $ref: '#/components/schemas/ScanOwnAccountOutput'
        founderHandles:
          maxItems: 10
          type: array
          items:
            type: string
            maxLength: 191
        protectedVocabulary:
          maxItems: 12
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 60
        competitors:
          maxItems: 6
          type: array
          items:
            $ref: '#/components/schemas/ScanCompetitorCandidateOutput'
        relevanceContext:
          type: string
          maxLength: 400
        productUseCases:
          maxItems: 5
          type: array
          items:
            type: string
            maxLength: 500
        companyMoat:
          type: string
          maxLength: 2000
      required:
        - name
        - description
        - industry
        - website
        - domainVariants
        - productAreas
        - ownAccounts
        - founderHandles
        - protectedVocabulary
        - competitors
        - relevanceContext
        - productUseCases
        - companyMoat
      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
    OnboardingKeyword:
      type: object
      properties:
        ref:
          description: '`k1…k5` for proposal rows, `added_<n>` for typed-in ones.'
          example: k1
          type: string
        keyword:
          type: string
        tag:
          $ref: '#/components/schemas/KeywordTag'
      required:
        - ref
        - keyword
        - tag
      additionalProperties: false
    OnboardingKeywordsProblem:
      type: object
      properties:
        code:
          type: string
          enum:
            - too_many
            - no_brand
            - too_many_brand
            - too_few_competitors
            - too_many_industry
            - refused
        ref:
          description: The keyword the problem is about, when it is about one.
          anyOf:
            - type: string
            - type: 'null'
        blocking:
          type: boolean
        message:
          type: string
      required:
        - code
        - ref
        - blocking
        - message
      additionalProperties: false
    OnboardingMention:
      type: object
      properties:
        id:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        source:
          type: string
        keyword:
          type: string
        place:
          anyOf:
            - type: object
              properties:
                preposition:
                  type: string
                  enum:
                    - in
                    - by
                name:
                  type: string
              required:
                - preposition
                - name
              additionalProperties: false
            - type: 'null'
        title:
          anyOf:
            - type: string
            - type: 'null'
        body:
          anyOf:
            - type: string
            - type: 'null'
        url:
          anyOf:
            - type: string
            - type: 'null'
        timestamp:
          type: string
        aboutYou:
          type: boolean
        giveaway:
          anyOf:
            - type: string
            - type: 'null'
        whyNot:
          anyOf:
            - type: string
            - type: 'null'
        sentimentLabel:
          anyOf:
            - type: string
            - type: 'null'
        tags:
          type: array
          items:
            type: string
      required:
        - id
        - source
        - keyword
        - place
        - title
        - body
        - url
        - timestamp
        - aboutYou
        - giveaway
        - whyNot
        - sentimentLabel
        - tags
      additionalProperties: false
    OnboardingSlackFeed:
      type: object
      properties:
        id:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        name:
          type: string
        channel:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: 'null'
        emailOnly:
          type: boolean
      required:
        - id
        - name
        - channel
        - emailOnly
      additionalProperties: false
    OnboardingHandoffLink:
      type: object
      properties:
        dest:
          $ref: '#/components/schemas/OnboardingHandoffDestination'
        url:
          example: https://app.octolens.com/go/K7Q2-MX9P
          type: string
        code:
          description: 8 Crockford base32 characters, `XXXX-XXXX`.
          example: K7Q2-MX9P
          type: string
        expiresAt:
          description: ISO-8601.
          type: string
      required:
        - dest
        - url
        - code
        - expiresAt
      additionalProperties: false
    OnboardingNextActionKind:
      description: >-
        `run` = safe to execute unasked · `poll` = re-read after
        `retryAfterSeconds` · `decide` = a preference or destructive choice,
        relay it · `ask_human` = a value only a person has · `open_url` = a
        person opens `url`, then poll.
      type: string
      enum:
        - run
        - poll
        - decide
        - ask_human
        - open_url
    ScanProductAreaOutput:
      type: object
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 60
        path:
          description: Site path, e.g. `/pricing`.
          type: string
          minLength: 1
          maxLength: 300
      required:
        - label
        - path
      additionalProperties: false
    ScanOwnAccountOutput:
      type: object
      properties:
        platform:
          type: string
          enum:
            - x
            - linkedin
            - domain
        handle:
          type: string
          minLength: 1
          maxLength: 191
      required:
        - platform
        - handle
      additionalProperties: false
    ScanCompetitorCandidateOutput:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        domain:
          type: string
          maxLength: 255
        why:
          description: 'One line: why they compete.'
          type: string
          maxLength: 300
      required:
        - name
        - why
      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
    OnboardingHandoffDestination:
      type: string
      enum:
        - slack_install
        - checkout
        - wizard
  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.