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

# Create the trial workspace

> Confirms the keywords and creates the free-trial workspace. Idempotent: a repeat answers `alreadyApplied: true`. `409 ONBOARDING_IN_PROGRESS` while another call holds the lock (retry in 5 s); `403 SECOND_FREE_TRIAL_FORBIDDEN` / `PAYWALL` / `SANDBOX_WORKSPACE`. The trial clock starts when setup finishes, not here.



## OpenAPI

````yaml https://app.octolens.com/api/v2/openapi.json post /api/v2/onboarding/trial
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/trial:
    post:
      tags:
        - Onboarding
      summary: Create the trial workspace
      description: >-
        Confirms the keywords and creates the free-trial workspace. Idempotent:
        a repeat answers `alreadyApplied: true`. `409 ONBOARDING_IN_PROGRESS`
        while another call holds the lock (retry in 5 s); `403
        SECOND_FREE_TRIAL_FORBIDDEN` / `PAYWALL` / `SANDBOX_WORKSPACE`. The
        trial clock starts when setup finishes, not here.
      operationId: startOnboardingTrial
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingTrialRequest'
      responses:
        '200':
          description: 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardingTrialResponse'
        '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:
    OnboardingTrialRequest:
      type: object
      properties:
        draftId:
          description: >-
            The `draft_…` id. Optional on every onboarding route: defaults to
            the onboarding's current draft.
          example: draft_k3v9x0q2m1
          type: string
          minLength: 1
          maxLength: 64
        selections:
          $ref: '#/components/schemas/SetupApplySelections'
          description: 'Sonar: what the person kept. Omitted = the proposal''s defaults.'
        keywords:
          description: >-
            The raw keywords: the classic engine, or a Sonar onboarding whose
            proposal failed or timed out (send them with no draftId and no
            selections).
          maxItems: 20
          type: array
          items:
            type: object
            properties:
              ref:
                type: string
                maxLength: 64
              keyword:
                type: string
                minLength: 1
                maxLength: 191
              tag:
                $ref: '#/components/schemas/KeywordTag'
            required:
              - keyword
              - tag
        ifRevision:
          description: >-
            Refuse with 409 ONBOARDING_REVISION_CHANGED unless the state is
            still at this revision.
          type: string
          minLength: 1
          maxLength: 128
    OnboardingTrialResponse:
      type: object
      properties:
        alreadyApplied:
          description: >-
            True when this onboarding's trial already existed (idempotent
            re-run).
          type: boolean
        pendingReview:
          type: boolean
        trial:
          $ref: '#/components/schemas/OnboardingTrial'
        state:
          $ref: '#/components/schemas/OnboardingState'
      required:
        - alreadyApplied
        - pendingReview
        - trial
        - 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
    SetupApplySelections:
      type: object
      properties:
        keywordRefs:
          description: The keywords the user kept.
          maxItems: 50
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
        optionIds:
          default: []
          maxItems: 50
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
        confirmedHandles:
          description: Refs of handle-shaped keywords the user confirmed (G3).
          default: []
          maxItems: 50
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
        platformsOff:
          default: []
          maxItems: 50
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
        tags:
          description: >-
            Type changes the user made on the keywords step, per kept proposal
            keyword. The keyword moves to the feed of its new type; every gate
            runs on the edited setup.
          default: []
          maxItems: 50
          type: array
          items:
            $ref: '#/components/schemas/SetupKeywordTagOverride'
        addedKeywords:
          description: >-
            Keywords the user typed in on the keywords step. Each is written as
            typed (no settings), in the feed of its type, and passes every
            keyword gate first (operator syntax is refused).
          default: []
          maxItems: 5
          type: array
          items:
            $ref: '#/components/schemas/SetupAddedKeyword'
        refinements:
          description: >-
            U8 (OCT-1739): how the user left a kept proposal keyword's counting
            rule on the keywords step — its must-also-mention terms and skip
            words replace the proposal's; every keyword gate runs on the edited
            setup.
          default: []
          maxItems: 50
          type: array
          items:
            $ref: '#/components/schemas/SetupKeywordRefinement'
      required:
        - keywordRefs
    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
    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
    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
    SetupKeywordTagOverride:
      type: object
      properties:
        ref:
          description: The proposal keyword's ref.
          type: string
          minLength: 1
          maxLength: 64
        tag:
          $ref: '#/components/schemas/KeywordTag'
      required:
        - ref
        - tag
    SetupAddedKeyword:
      type: object
      properties:
        keyword:
          type: string
          minLength: 1
          maxLength: 191
        tag:
          $ref: '#/components/schemas/KeywordTag'
        additionalTerms:
          description: >-
            U8 (OCT-1739): the must-also-mention terms as the user left them.
            Present = written as given (an empty list = counted on every post);
            absent = as assessed when the keyword was added.
          maxItems: 15
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 191
        excludeWords:
          description: >-
            U8 (OCT-1739): the skip words as the user left them. Present =
            written as given; absent = as assessed.
          maxItems: 100
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 191
      required:
        - keyword
        - tag
    SetupKeywordRefinement:
      type: object
      properties:
        ref:
          type: string
          minLength: 1
          maxLength: 64
        additionalTerms:
          description: >-
            Counted only when the post also mentions one of these. Present =
            replaces the proposal's (an empty list = every post that names the
            keyword); absent = the proposal's stand.
          maxItems: 15
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 191
        excludeWords:
          description: >-
            Posts with any of these words are skipped. Present = replaces the
            proposal's; absent = the proposal's stand.
          maxItems: 100
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 191
      required:
        - ref
    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
    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
    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
    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.