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

# Composite dashboard overview

> Volume trend, per-keyword breakdown, sentiment split, and plan usage in one response. Each section matches the corresponding focused endpoint (`/analytics/volume`, `/analytics/keywords`, `/analytics/sentiment`, `/org/usage`) exactly, but the whole dashboard costs one request and one rate-limit token.



## OpenAPI

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


    ### Authentication


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


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


    ### Rate limiting


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


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

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

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

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


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


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


    ### 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/analytics/dashboard:
    get:
      tags:
        - Analytics
      summary: Composite dashboard overview
      description: >-
        Volume trend, per-keyword breakdown, sentiment split, and plan usage in
        one response. Each section matches the corresponding focused endpoint
        (`/analytics/volume`, `/analytics/keywords`, `/analytics/sentiment`,
        `/org/usage`) exactly, but the whole dashboard costs one request and one
        rate-limit token.
      operationId: getAnalyticsDashboard
      parameters:
        - in: query
          name: keywordIds
          schema:
            description: Limit aggregation to specific keyword IDs.
            anyOf:
              - type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
              - type: array
                items:
                  type: integer
                  minimum: -9007199254740991
                  maximum: 9007199254740991
          description: Limit aggregation to specific keyword IDs.
        - in: query
          name: platforms
          schema:
            description: Filter to specific platforms.
            anyOf:
              - $ref: '#/components/schemas/Platform'
              - type: array
                items:
                  $ref: '#/components/schemas/Platform'
          description: Filter to specific platforms.
        - in: query
          name: tag
          schema:
            $ref: '#/components/schemas/MentionTag'
            description: >-
              Narrow to mentions carrying this AI-assigned tag. See `MentionTag`
              for all values.
          description: >-
            Narrow to mentions carrying this AI-assigned tag. See `MentionTag`
            for all values.
        - in: query
          name: sentiment
          schema:
            type: string
            enum:
              - POSITIVE
              - NEUTRAL
              - NEGATIVE
        - in: query
          name: relevance
          schema:
            description: >-
              Relevance scores to include. 0 = high relevance, 1 = medium, 2 =
              low. Default: [0, 1].
            anyOf:
              - type: integer
                minimum: 0
                maximum: 2
              - type: array
                items:
                  type: integer
                  minimum: 0
                  maximum: 2
          description: >-
            Relevance scores to include. 0 = high relevance, 1 = medium, 2 =
            low. Default: [0, 1].
        - in: query
          name: startDate
          schema:
            description: >-
              Inclusive start of the window. ISO 8601 datetime. If omitted
              together with endDate, defaults to the last 30 days.
            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))$
          description: >-
            Inclusive start of the window. ISO 8601 datetime. If omitted
            together with endDate, defaults to the last 30 days.
        - in: query
          name: endDate
          schema:
            description: >-
              Exclusive end of the window. ISO 8601 datetime. If omitted
              together with startDate, defaults to "now".
            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))$
          description: >-
            Exclusive end of the window. ISO 8601 datetime. If omitted together
            with startDate, defaults to "now".
        - in: query
          name: granularity
          schema:
            description: Bucket size. Default `day`.
            default: day
            type: string
            enum:
              - day
              - hour
          description: Bucket size. Default `day`.
      responses:
        '200':
          description: 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsDashboardResponse'
        '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:
    Platform:
      description: Platform a mention can originate from / a keyword can be monitored on.
      example: reddit
      type: string
      enum:
        - dev
        - github
        - hackernews
        - linkedin
        - producthunt
        - reddit
        - stackoverflow
        - twitter
        - youtube
        - tiktok
        - medium
        - reddit_comment
        - bluesky
        - newsletter
        - podcasts
        - news
        - firehose
    MentionTag:
      description: >-
        AI-assigned topic tag. Applied by the relevance worker during scoring. A
        post can carry multiple tags.
      example: competitor_mention
      type: string
      enum:
        - buy_intent
        - competitor_mention
        - customer_testimonial
        - promotional_post
        - own_brand_mention
        - industry_insights
        - hiring
        - event
        - product_question
        - pricing
        - churn_intent
        - launch_announcement
        - bug_report
        - user_feedback
        - ai_generated
    AnalyticsDashboardResponse:
      type: object
      properties:
        volume:
          $ref: '#/components/schemas/VolumeResponse'
        keywords:
          $ref: '#/components/schemas/KeywordBreakdownResponse'
        sentiment:
          $ref: '#/components/schemas/SentimentDistributionResponse'
        usage:
          $ref: '#/components/schemas/OrgUsage'
      required:
        - volume
        - keywords
        - sentiment
        - usage
      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
    VolumeResponse:
      type: object
      properties:
        granularity:
          description: Granularity that was used (echoes the request).
          type: string
          enum:
            - day
            - hour
        data:
          description: >-
            Sorted chronologically, ascending. Empty buckets are omitted (not
            zero-filled).
          type: array
          items:
            $ref: '#/components/schemas/VolumeEntry'
      required:
        - granularity
        - data
      additionalProperties: false
    KeywordBreakdownResponse:
      type: object
      properties:
        data:
          description: Sorted by `count` descending.
          type: array
          items:
            $ref: '#/components/schemas/KeywordBreakdownEntry'
      required:
        - data
      additionalProperties: false
    SentimentDistributionResponse:
      type: object
      properties:
        data:
          description: One entry per sentiment bucket that has a non-zero count.
          type: array
          items:
            $ref: '#/components/schemas/SentimentDistributionEntry'
      required:
        - data
      additionalProperties: false
    OrgUsage:
      description: Current-cycle usage counters and plan limits.
      type: object
      properties:
        plan:
          description: >-
            Current subscription plan (e.g. `Free_trial`, `Pro_v3`,
            `Enterprise`). `null` if no active plan / free trial expired. The
            `*.limit` numbers below already reflect this plan.
          example: Pro_v3
          anyOf:
            - type: string
            - type: 'null'
        mentions:
          type: object
          properties:
            count:
              description: Mentions consumed in the current billing cycle.
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            limit:
              description: >-
                Plan allowance + one-time `countExtension` + purchased add-on
                mentions (`additionalMentions`). Matches the cap shown in the
                app UI.
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            resetAt:
              description: >-
                When the mention counter resets (start of next cycle). `null`
                for plans without a reset schedule.
              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:
            - count
            - limit
            - resetAt
          additionalProperties: false
        keywords:
          type: object
          properties:
            count:
              description: Live keyword count.
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            limit:
              description: >-
                Plan allowance + purchased add-on keywords
                (`additionalKeywords`).
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
          required:
            - count
            - limit
          additionalProperties: false
        searches:
          $ref: '#/components/schemas/OrgUsageSearches'
        flex:
          $ref: '#/components/schemas/OrgUsageFlex'
      required:
        - plan
        - mentions
        - keywords
      additionalProperties: false
    ApiErrorCode:
      description: >-
        Stable, machine-readable error code. Grouped as follows:


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


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


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


        **Not found (404)** — generic `NOT_FOUND` plus domain-specific variants:
        `FEED_NOT_FOUND`, `KEYWORD_NOT_FOUND`, `KEYWORDS_NOT_FOUND`,
        `POST_NOT_FOUND`, `MENTION_NOT_FOUND`, `SUMMARY_NOT_FOUND`,
        `SEARCH_NOT_FOUND`, `SUGGESTION_NOT_FOUND`, `COMPANY_NOT_FOUND`,
        `ORG_NOT_FOUND`, `SETTINGS_NOT_FOUND`, `NOTIFICATION_NOT_FOUND`,
        `VIEW_NOT_FOUND`, `ALERT_NOT_FOUND`. 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.


        **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),
        `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).


        **Operation failures (500)** — `ENGAGE_FAILED` /
        `RELEVANCE_UPDATE_FAILED` carry a specific code so clients can
        distinguish a failed mutation from a generic `INTERNAL_ERROR`.
      type: string
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - RATE_LIMITED
        - VALIDATION_ERROR
        - INTERNAL_ERROR
        - ORGANIZATION_REQUIRED
        - FORBIDDEN_ORGANIZATION
        - MASTER_KEY_REQUIRED
        - AGENCY_INACTIVE
        - NOT_AN_AGENCY_WORKSPACE
        - AGENCY_NOT_FOUND
        - WORKSPACE_NOT_IN_AGENCY
        - ANCHOR_WORKSPACE
        - AGENCY_WORKSPACE_LIMIT
        - INVITE_FANOUT_TOO_LARGE
        - SUPERADMIN_REMOVE_FORBIDDEN
        - USER_REQUIRED
        - FLEX_REQUIRES_CAP
        - FLEX_REQUIRES_BUDGET
        - AGENCY_BILLING_NOT_CONFIGURED
        - WORKSPACE_CREATE_FAILED
        - WORKSPACE_SETUP_FAILED
        - WORKSPACE_ACCESS_FAILED
        - UPGRADE_REQUIRED
        - QUOTA_EXCEEDED
        - NOT_FOUND
        - FEED_NOT_FOUND
        - KEYWORD_NOT_FOUND
        - KEYWORDS_NOT_FOUND
        - POST_NOT_FOUND
        - MENTION_NOT_FOUND
        - SUMMARY_NOT_FOUND
        - SEARCH_NOT_FOUND
        - SUGGESTION_NOT_FOUND
        - COMPANY_NOT_FOUND
        - ORG_NOT_FOUND
        - SETTINGS_NOT_FOUND
        - NOTIFICATION_NOT_FOUND
        - VIEW_NOT_FOUND
        - ALERT_NOT_FOUND
        - INVALID_INPUT
        - KEYWORD_LIMIT_EXCEEDED
        - 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
        - ENGAGE_FAILED
        - RELEVANCE_UPDATE_FAILED
    VolumeEntry:
      type: object
      properties:
        bucket:
          description: >-
            Bucket start. `YYYY-MM-DD` for daily granularity, ISO datetime
            (`YYYY-MM-DDTHH:00:00Z`) for hourly.
          example: '2026-04-22'
          type: string
        count:
          description: Number of mentions that fell in this bucket.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
      required:
        - bucket
        - count
      additionalProperties: false
    KeywordBreakdownEntry:
      type: object
      properties:
        keywordId:
          description: Tracked keyword id.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        keyword:
          description: Keyword text.
          type: string
        count:
          description: >-
            Mentions that matched this keyword. A mention matching multiple
            keywords is counted once per match.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
      required:
        - keywordId
        - keyword
        - count
      additionalProperties: false
    SentimentDistributionEntry:
      type: object
      properties:
        sentiment:
          description: Sentiment label. `unknown` covers mentions the AI hasn't scored yet.
          type: string
          enum:
            - Positive
            - Neutral
            - Negative
            - unknown
        count:
          description: Number of mentions with this sentiment.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
      required:
        - sentiment
        - count
      additionalProperties: false
    OrgUsageSearches:
      description: >-
        Lifetime on-demand search allowance. Only present on the Agents plan
        (the only plan with a lifetime search cap). Numbers come from the
        canonical quota read — identical to the `X-Octolens-Searches-Remaining`
        header on `/api/v2/search*` responses.
      type: object
      properties:
        used:
          description: Lifetime on-demand searches consumed (never resets).
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        limit:
          description: >-
            Lifetime search allowance (plan allowance + any support-granted
            extension).
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        remaining:
          description: Searches left before the plan wall (`UPGRADE_REQUIRED`).
          type: integer
          minimum: 0
          maximum: 9007199254740991
      required:
        - used
        - limit
        - remaining
      additionalProperties: false
    OrgUsageFlex:
      description: >-
        Flex (usage-based) pricing state. Only present when flex pricing is
        enabled for the org.
      type: object
      properties:
        enabled:
          description: Flag marker so the field's presence unambiguously means flex is on.
          type: boolean
          const: true
        budgetCents:
          description: Max flex-pricing spend per cycle, in cents (e.g. 50000 = $500).
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        used:
          description: Flex mentions consumed in the current cycle.
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        resetAt:
          description: When the flex counter resets (synced with Stripe billing cycle).
          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:
        - enabled
        - budgetCents
        - used
        - resetAt
      additionalProperties: false
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        Clerk API key. Create one in Settings → API Keys. Pass as
        `Authorization: Bearer <key>`.

````