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

# List keyword suggestions

> AI-generated tweaks for keyword config. Pass `keywordId` for per-keyword view, or omit for org-wide enriched list.



## OpenAPI

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


    ### Authentication


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


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


    ### Rate limiting


    The v2 API is rate-limited at **500 requests per hour per organization**,
    across all keys for that org. The limit resets at the top of each hour
    (sliding hourly window).


    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"`.


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


    ### 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: []
paths:
  /api/v2/keywords/suggestions:
    get:
      tags:
        - Keywords
      summary: List keyword suggestions
      description: >-
        AI-generated tweaks for keyword config. Pass `keywordId` for per-keyword
        view, or omit for org-wide enriched list.
      operationId: listKeywordSuggestions
      parameters:
        - in: query
          name: keywordId
          schema:
            description: Narrow to one keyword. Omit to list org-wide.
            type: integer
            minimum: -9007199254740991
            maximum: 9007199254740991
          description: Narrow to one keyword. Omit to list org-wide.
        - in: query
          name: withVolume
          schema:
            $ref: '#/components/schemas/QueryBoolean'
            description: >-
              Pass the string `"true"` or `"false"`. When omitted or `"true"`,
              the org-wide `SuggestionsWithVolume` shape is returned. When
              `"false"`, returns the `SuggestionCounts` per-keyword count
              summary instead.
          description: >-
            Pass the string `"true"` or `"false"`. When omitted or `"true"`, the
            org-wide `SuggestionsWithVolume` shape is returned. When `"false"`,
            returns the `SuggestionCounts` per-keyword count summary instead.
        - in: query
          name: cursor
          schema:
            description: >-
              Opaque pagination cursor — pass back `nextCursor` from the
              previous response. Only applies to the org-wide
              (`SuggestionsWithVolume`) shape.
            type: string
          description: >-
            Opaque pagination cursor — pass back `nextCursor` from the previous
            response. Only applies to the org-wide (`SuggestionsWithVolume`)
            shape.
        - in: query
          name: limit
          schema:
            description: >-
              Max suggestions to return per page (1–100). Default 25. Only
              applies to the org-wide (`SuggestionsWithVolume`) shape.
            type: integer
            minimum: 1
            maximum: 100
          description: >-
            Max suggestions to return per page (1–100). Default 25. Only applies
            to the org-wide (`SuggestionsWithVolume`) shape.
      responses:
        '200':
          description: 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeywordSuggestionsListResponse'
        '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'
components:
  schemas:
    QueryBoolean:
      description: Boolean query parameter — the string "true" or "false".
      example: 'false'
      type: string
      enum:
        - 'true'
        - 'false'
    KeywordSuggestionsListResponse:
      description: >-
        Shape depends on the query: per-keyword list, count summary, or full
        enriched list with volume.
      anyOf:
        - $ref: '#/components/schemas/KeywordSuggestionList'
        - $ref: '#/components/schemas/SuggestionCounts'
        - $ref: '#/components/schemas/SuggestionsWithVolume'
    ErrorResponse:
      description: >-
        Standard error envelope returned for all non-2xx responses. The `code`
        field is stable — safe to branch on programmatically.
      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: {}
          required:
            - code
            - message
            - status
          additionalProperties: false
      required:
        - error
      additionalProperties: false
    KeywordSuggestionList:
      description: Per-keyword suggestion bundle with a snapshot of current settings.
      type: object
      properties:
        keyword:
          $ref: '#/components/schemas/KeywordSettingsPeek'
        suggestions:
          description: All non-expired suggestions for the requested keyword.
          type: array
          items:
            $ref: '#/components/schemas/KeywordSuggestion'
      required:
        - keyword
        - suggestions
      additionalProperties: false
    SuggestionCounts:
      description: >-
        Count of pending suggestions per keyword, for showing an indicator in
        the keyword list.
      type: array
      items:
        type: object
        properties:
          keywordId:
            description: Keyword id.
            type: integer
            minimum: -9007199254740991
            maximum: 9007199254740991
          count:
            description: Number of pending suggestions.
            type: integer
            minimum: -9007199254740991
            maximum: 9007199254740991
        required:
          - keywordId
          - count
        additionalProperties: false
    SuggestionsWithVolume:
      description: Org-wide suggestion list enriched with keyword volume data.
      type: object
      properties:
        keywords:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
              keyword:
                type: string
              volume:
                description: Mentions for this keyword in the recent window.
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
            required:
              - id
              - keyword
              - volume
            additionalProperties: false
        suggestions:
          type: array
          items:
            $ref: '#/components/schemas/EnrichedSuggestion'
        totalSuggestionCount:
          description: >-
            Total pending suggestions across all keywords (the full snapshot,
            not just the current page).
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        nextCursor:
          description: >-
            Opaque cursor for the next page of `suggestions`. Null when no more
            pages remain. `keywords` and `totalSuggestionCount` are not
            paginated. The cursor encodes a position in the current snapshot —
            if suggestions are accepted or rejected between page requests, later
            pages may skip or repeat items; restart pagination after mutations.
          anyOf:
            - type: string
            - type: 'null'
      required:
        - keywords
        - suggestions
        - totalSuggestionCount
        - nextCursor
      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).


        **Not found (404)** — generic `NOT_FOUND` plus domain-specific variants:
        `FEED_NOT_FOUND`, `KEYWORD_NOT_FOUND`, `POST_NOT_FOUND`,
        `SUMMARY_NOT_FOUND`, `SUGGESTION_NOT_FOUND`, `COMPANY_NOT_FOUND`,
        `ORG_NOT_FOUND`, `SETTINGS_NOT_FOUND`.


        **Business-rule violations (400)** — `KEYWORD_LIMIT_EXCEEDED` (plan cap
        hit), `LAST_ADMIN` (refuses to remove the only admin), `ITEM_EXISTS`
        (duplicate), `INVALID_DOMAIN`, `INVALID_TIMEZONE`.
      type: string
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - RATE_LIMITED
        - VALIDATION_ERROR
        - INTERNAL_ERROR
        - NOT_FOUND
        - FEED_NOT_FOUND
        - KEYWORD_NOT_FOUND
        - POST_NOT_FOUND
        - SUMMARY_NOT_FOUND
        - SUGGESTION_NOT_FOUND
        - COMPANY_NOT_FOUND
        - ORG_NOT_FOUND
        - SETTINGS_NOT_FOUND
        - KEYWORD_LIMIT_EXCEEDED
        - LAST_ADMIN
        - ITEM_EXISTS
        - INVALID_DOMAIN
        - INVALID_TIMEZONE
    KeywordSettingsPeek:
      description: >-
        Snapshot of the current keyword config so the UI can show a diff
        preview.
      type: object
      properties:
        excludeWords:
          description: Current comma-separated exclude words on the keyword.
          anyOf:
            - type: string
            - type: 'null'
        excludeAuthors:
          description: Current comma-separated excluded authors.
          anyOf:
            - type: string
            - type: 'null'
        additionalTerms:
          description: Current comma-separated additional terms.
          anyOf:
            - type: string
            - type: 'null'
        additionalTermsAndOr:
          description: Current AND/OR combinator for additional terms.
          type: boolean
        platforms:
          description: Current comma-separated platforms list.
          anyOf:
            - type: string
            - type: 'null'
        symbolSensitive:
          description: Current "exact match" mode.
          type: boolean
      required:
        - excludeWords
        - excludeAuthors
        - additionalTerms
        - additionalTermsAndOr
        - platforms
        - symbolSensitive
      additionalProperties: false
    KeywordSuggestion:
      type: object
      properties:
        id:
          description: Suggestion id.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        keywordId:
          description: Keyword this suggestion targets.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        organizationId:
          description: Organization id.
          type: string
        type:
          $ref: '#/components/schemas/KeywordSuggestionType'
        value:
          description: >-
            The proposed value. Shape depends on `type`: for `add_exclude_words`
            / `add_additional_terms` it's a comma-separated string; for
            `disable_source` it's a platform enum value; for `set_exact_match`
            it's `true`/`false` serialized; for `change_additional_terms_logic`
            it's `and`/`or`.
          type: string
        reason:
          description: AI-written justification shown to the user.
          anyOf:
            - type: string
            - type: 'null'
        impact:
          description: >-
            AI-written estimated impact (e.g. 'reduces noise by ~15%').
            Free-form prose.
          anyOf:
            - type: string
            - type: 'null'
        status:
          $ref: '#/components/schemas/SuggestionStatus'
        createdAt:
          type: string
          format: date-time
          pattern: >-
            ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
        respondedAt:
          description: When the user accepted / rejected. `null` while `status` is pending.
          anyOf:
            - type: string
              format: date-time
              pattern: >-
                ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
            - type: 'null'
      required:
        - id
        - keywordId
        - organizationId
        - type
        - value
        - reason
        - impact
        - status
        - createdAt
        - respondedAt
      additionalProperties: false
    EnrichedSuggestion:
      description: >-
        Suggestion augmented with keyword-level details — used when listing
        across keywords.
      type: object
      properties:
        id:
          description: Suggestion id.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        keywordId:
          description: Keyword the suggestion targets.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        keyword:
          description: Keyword text.
          type: string
        keywordVolume:
          description: Mention volume on this keyword over the recent window.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        type:
          $ref: '#/components/schemas/KeywordSuggestionType'
        value:
          description: >-
            Proposed value (shape depends on `type`, see
            `KeywordSuggestion.value`).
          type: string
        reason:
          anyOf:
            - type: string
            - type: 'null'
        impact:
          anyOf:
            - type: string
            - type: 'null'
        impactScore:
          description: >-
            Numeric impact score (0–1) used to sort suggestions. Higher = higher
            impact.
          type: number
        keywordSettings:
          $ref: '#/components/schemas/KeywordSettingsPeek'
      required:
        - id
        - keywordId
        - keyword
        - keywordVolume
        - type
        - value
        - reason
        - impact
        - impactScore
        - keywordSettings
      additionalProperties: false
    KeywordSuggestionType:
      description: >-
        What kind of change the AI is proposing. `add_*` and `change_*` modify
        existing keyword fields; `set_exact_match` toggles the symbol-sensitive
        flag; `disable_source` removes a platform from the keyword's `platforms`
        list.
      example: add_exclude_words
      type: string
      enum:
        - add_exclude_words
        - add_exclude_authors
        - add_additional_terms
        - change_additional_terms_logic
        - set_exact_match
        - disable_source
    SuggestionStatus:
      description: >-
        Lifecycle. `pending` awaits user action; `accepted` / `rejected` are
        terminal; `expired` = the underlying keyword config changed and the
        suggestion no longer applies.
      example: pending
      type: string
      enum:
        - pending
        - accepted
        - rejected
        - expired
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        Clerk API key. Create one in Settings → API Keys. Pass as
        `Authorization: Bearer <key>`.

````