openapi: 3.1.0
info:
  title: AIXBT API v3
  version: 3.0.0-draft.1
  summary: Crypto intelligence API for Projects, Topics, Intel, Reports, and Clusters.
  description: |
    The AIXBT API v3 contract. The implementation conforms to this document,
    the reference docs generate from it, and automated tests keep the served
    API and this document in sync. Interactive docs are served at `/v3/docs`;
    this document is served verbatim at `/v3/openapi.yaml`.

    ## Conventions

    - **Envelope**: every JSON success body is `{data, meta}`. There is no
      body-level `status` integer; the HTTP status code is the status.
    - **Errors**: every 4xx/5xx body is a single typed `Error` with a stable
      machine `code`. `401` means the credential is missing or invalid; `403`
      means the credential lacks an entitlement or crossed the history
      boundary.
    - **Pagination**: collections use opaque cursors: request `cursor` +
      `limit`, read `meta.nextCursor` (`null` on the last page). There are no
      page numbers and no total counts.
    - **Measurements**: v3 publishes AIXBT-owned measurements (`trend`,
      universe-scoped `rank`, attention counts) and canonical identity. It
      does not redistribute third-party market data: no prices, market caps,
      volumes, or candles anywhere in this contract. Composition happens
      through `identifiers`: stable external join keys (CoinGecko id, X
      handle; additively extensible per universe) that let a consumer fetch
      market data under their own license. The one bounded exception is the
      publication-time evidence block inside an entitled Report, which quotes
      attributed observations as part of the research artifact.
    - **Access tiers**: Current Topic list, detail, and association routes are
      free and served under unauthenticated IP rate limits. Topic measurement
      history and everything else require an API key, and Report detail
      additionally requires the `topic-reports` entitlement. Alert management
      is owner-scoped to the calling credential; destination provisioning (webhook secrets, chat
      connections) stays in product Settings and the API references
      destinations by id.
    - **Naming**: `trend` is the only public measurement name. Enumerated
      values are lowercase. Timestamps are ISO 8601 UTC and semantic
      (`detectedAt`, `reinforcedAt`, `observedAt`, never processing time).
      Intel activity keeps claimed `eventTime` separate from detection time;
      legacy `occurredAt` remains a deprecated provider-publication/detection
      fallback; it is never claimed event time.
    - **Locators**: Project path params accept the canonical id or the
      immutable public slug; responses always carry both. Merged or re-slugged
      Projects keep old locators resolving to the canonical resource.
    - **Universes**: every Project and Cluster belongs to a universe
      (`crypto` today; `tradfi` is reserved and already used by Clusters).
      Ranks and leaderboards are universe-scoped.
    - **Assets**: a Project's instrument identity is a typed `assets` array
      (`token`, `stablecoin`, `nft-collection` today). New universes add new
      asset types additively; the array shape never changes per universe.
    - **History**: measurement series endpoints serve recorded history
      through their `from`/`to` windows, subject to the credential's history
      window (`meta.history`).
    - **Subject misses**: a 404 body distinguishes `not_found` (never
      existed / bad locator) and `not_current` (exists, no longer current).
    - **Deprecation**: nothing in v3 is removed silently: retiring routes
      first serve RFC 9745 `Deprecation` and RFC 8288
      `Link: rel="successor-version"` headers and are marked
      `deprecated: true` here with an `x-successor` pointer.
    - **Caching**: every response is `Cache-Control: private, no-store`.
      Rate limits are surfaced via `X-RateLimit-*` headers; breaches return
      `429` with `Retry-After`.
  x-status: draft
servers:
  - url: https://api.aixbt.tech
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Projects
    description: Project identity, trend, attention, and relations.
  - name: Topics
    description: The evolving narrative leaderboard and Topic–Project associations.
  - name: Intel
    description: Discrete evidence-graded developments on Projects.
  - name: Reports
    description: Read-only published research artifacts.
  - name: Alerts
    description: Owner-scoped alert subscriptions over the shipped alert system.
  - name: Clusters
    description: Audience/source archetypes used as an analysis dimension.
  - name: Vocabulary
    description: Versioned value sets used by every filter.
  - name: Me
    description: Your credential's entitlements, limits, and history window.
  - name: Meta
    description: The contract itself.

paths:
  /v3/projects:
    get:
      operationId: listProjects
      tags: [Projects]
      summary: List Projects
      description: |
        Canonical Project collection, sorted by `trend` (default),
        `createdAt`, or `reinforcedAt`, always descending. All filters come
        from the documented filter values; catalog-derived values
        (categories, chains, asset types) are discoverable via
        `GET /v3/vocabulary`.
      x-collection: true
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: sort
          in: query
          schema:
            {
              type: string,
              enum: [trend, createdAt, reinforcedAt],
              default: trend,
            }
        - name: ids
          in: query
          description: Comma-separated canonical Project ids.
          schema: { type: string }
        - name: slugs
          in: query
          description: Comma-separated Project slugs (aliases resolve).
          schema: { type: string }
        - name: names
          in: query
          description: Comma-separated display names, case-insensitive.
          schema: { type: string }
        - name: xHandles
          in: query
          description: Comma-separated X handles without the at sign.
          schema: { type: string }
        - name: tickers
          in: query
          description: Comma-separated asset tickers.
          schema: { type: string }
        - name: categories
          in: query
          description: Comma-separated canonical taxonomy slugs.
          schema: { type: string }
        - name: chain
          in: query
          description: Restrict to Projects with an asset deployment on this chain.
          schema: { type: string }
        - name: address
          in: query
          description: Asset contract address (EVM case-insensitive, other chains case-sensitive).
          schema: { type: string }
        - name: assetTypes
          in: query
          description: Comma-separated asset types (see `/v3/vocabulary`).
          schema: { type: string }
        - name: universe
          in: query
          schema: { type: string, enum: [crypto, tradfi], default: crypto }
        - name: minTrend
          in: query
          description: Minimum current trend measurement.
          schema: { type: number }
      responses:
        '200':
          description: Project page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Project' }
                  meta: { $ref: '#/components/schemas/CollectionMeta' }
              examples:
                success:
                  summary: First page of Projects ranked by trend
                  value:
                    data:
                      - id: 665f0a2b1c9d440000a1b2c3
                        slug: examplechain
                        name: ExampleChain
                        universe: crypto
                        symbol: EXC
                        description: Modular settlement layer for restaked rollups.
                        identifiers:
                          { coingecko: examplechain, x: examplechain }
                        image: https://images.aixbt.tech/projects/examplechain.png
                        trend: 87.4
                        rank: 3
                        categories: [modular-blockchains, restaking]
                        assets:
                          - {
                              type: token,
                              chain: ethereum,
                              address: '0xabc0000000000000000000000000000000000001',
                              primary: true,
                            }
                        intel:
                          - {
                              id: 6890aa000000000000000001,
                              headline: Mainnet v2 date confirmed,
                              grade: polished,
                              detectedAt: '2026-08-04T21:14:00Z',
                            }
                        createdAt: '2026-05-02T10:00:00Z'
                        updatedAt: '2026-08-05T04:58:11Z'
                        reinforcedAt: '2026-08-05T03:40:00Z'
                    meta:
                      nextCursor: b3BhcXVlLWN1cnNvcg
                      history:
                        {
                          archiveAccess: false,
                          enforced: true,
                          lookbackDays: 90,
                          earliestAvailableAt: '2026-05-07T00:00:00Z',
                          enforcementAt: '2026-08-17T00:00:00Z',
                        }
                      versions: { trendModel: trend-v3 }
                empty:
                  summary: Filter matched nothing
                  value:
                    data: []
                    meta: { nextCursor: null }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /v3/projects/{idOrSlug}:
    get:
      operationId: getProject
      tags: [Projects]
      summary: Get a Project
      description: |
        Project detail by canonical id or slug. Slug aliases (from renames or
        merges) resolve to the canonical resource; the response always carries
        the canonical `slug`. Includes a bounded `relatedProjects` co-mention
        preview derived from the same posts that support Project Intel;
        observational only, not partnership or endorsement.
      parameters:
        - $ref: '#/components/parameters/IdOrSlug'
      responses:
        '200':
          description: Project detail.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/ProjectDetail' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Detail with related Projects
                  value:
                    data:
                      id: 665f0a2b1c9d440000a1b2c3
                      slug: examplechain
                      name: ExampleChain
                      universe: crypto
                      symbol: EXC
                      description: Modular settlement layer for restaked rollups.
                      identifiers: { coingecko: examplechain, x: examplechain }
                      image: https://images.aixbt.tech/projects/examplechain.png
                      trend: 87.4
                      trendDelta: { value: 12.1, window: PT24H }
                      rank: 3
                      categories: [modular-blockchains, restaking]
                      assets:
                        - {
                            type: token,
                            chain: ethereum,
                            address: '0xabc0000000000000000000000000000000000001',
                            primary: true,
                          }
                      intel:
                        - {
                            id: 6890aa000000000000000001,
                            headline: Mainnet v2 date confirmed,
                            grade: polished,
                            detectedAt: '2026-08-04T21:14:00Z',
                          }
                      relatedProjects:
                        - project:
                            {
                              id: 665f0a2b1c9d440000a1b2c4,
                              slug: otherchain,
                              name: OtherChain,
                            }
                          kind: co_mention
                          sharedIntel: 14
                          window: P14D
                          asOf: '2026-08-05T04:45:00Z'
                      createdAt: '2026-05-02T10:00:00Z'
                      updatedAt: '2026-08-05T04:58:11Z'
                      reinforcedAt: '2026-08-05T03:40:00Z'
                    meta:
                      history:
                        {
                          archiveAccess: false,
                          enforced: true,
                          lookbackDays: 90,
                          earliestAvailableAt: '2026-05-07T00:00:00Z',
                          enforcementAt: '2026-08-17T00:00:00Z',
                        }
                      versions:
                        { trendModel: trend-v3, relations: co-mention-v1 }
                partial:
                  summary: Assetless Project with nothing tradable yet
                  value:
                    data:
                      id: 665f0a2b1c9d440000a1b2c5
                      slug: stealthlabs
                      name: StealthLabs
                      universe: crypto
                      description: Pre-token research collective.
                      trend: 41.2
                      categories: [ai-agents]
                      assets: []
                      intel: []
                      createdAt: '2026-07-29T08:00:00Z'
                      updatedAt: '2026-08-05T02:10:00Z'
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /v3/projects/{idOrSlug}/series/{measure}:
    get:
      operationId: getProjectSeries
      tags: [Projects]
      summary: Get a Project measurement series
      description: |
        One series contract for the public measures. `trend` is the hourly
        canonical score history; `rank` is the committed rank-transition
        series exactly as recorded: change points, not resampled intervals
        (`value` is `null` while unranked). `meta.basis` states which.
        Points are objects, never positional arrays.
      parameters:
        - $ref: '#/components/parameters/IdOrSlug'
        - name: measure
          in: path
          required: true
          schema: { type: string, enum: [trend, rank] }
        - name: from
          in: query
          schema: { type: string, format: date-time }
        - name: to
          in: query
          schema: { type: string, format: date-time }
        - name: include
          in: query
          description: '`clusters` adds per-point cluster attribution to `trend` series.'
          schema: { type: string, enum: [clusters] }
      responses:
        '200':
          description: Series page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    required: [project, points]
                    properties:
                      project: { $ref: '#/components/schemas/ProjectRef' }
                      points:
                        type: array
                        items: { $ref: '#/components/schemas/SeriesPoint' }
                  meta: { $ref: '#/components/schemas/SeriesMeta' }
              examples:
                success:
                  summary: Hourly trend series
                  value:
                    data:
                      project:
                        {
                          id: 665f0a2b1c9d440000a1b2c3,
                          slug: examplechain,
                          name: ExampleChain,
                        }
                      points:
                        - { at: '2026-08-05T03:00:00Z', value: 84.1 }
                        - { at: '2026-08-05T04:00:00Z', value: 87.4 }
                    meta:
                      measure: trend
                      basis: hourly
                      window:
                        {
                          from: '2026-08-04T05:00:00Z',
                          to: '2026-08-05T05:00:00Z',
                        }
                rank:
                  summary: Rank transition series over an explicit window
                  value:
                    data:
                      project:
                        {
                          id: 665f0a2b1c9d440000a1b2c3,
                          slug: examplechain,
                          name: ExampleChain,
                        }
                      points:
                        - { at: '2026-07-20T11:45:00Z', value: 7 }
                        - { at: '2026-07-20T12:00:00Z', value: null }
                    meta:
                      measure: rank
                      basis: rank_transitions
                      window:
                        {
                          from: '2026-07-20T11:00:00Z',
                          to: '2026-07-20T12:00:00Z',
                        }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /v3/topics:
    get:
      operationId: listTopics
      tags: [Topics]
      security: []
      summary: List the current Topic leaderboard
      description: |
        The current Topic leaderboard (top 25), a bounded set, so
        there is no cursor. `meta.snapshot` carries the shared evidence
        cutoff. With `projectId` (or `projectSlug`) the same authority is
        projected inversely: only Topics observationally associated with that
        Project, each item gaining an `association` block.
      parameters:
        - name: projectId
          in: query
          schema: { type: string }
        - name: projectSlug
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Leaderboard.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/TopicListItem' }
                  meta: { $ref: '#/components/schemas/TopicSnapshotMeta' }
              examples:
                success:
                  summary: Current leaderboard head
                  value:
                    data:
                      - id: topic_a1b2c3d4e5f601234567
                        rank: 1
                        name: Restaking yield compression
                        summary: Yields across restaking protocols compress as new AVS demand stalls.
                        trend: 93.2
                        postCount: 412
                        authorCount: 118
                        lastSeenAt: '2026-08-05T04:52:00Z'
                        clusters:
                          - {
                              cluster: { id: cl_defi, name: DeFi researchers },
                              share: 0.41,
                            }
                        report:
                          {
                            id: tr_9f2b6c41d8a35e07b1c4f6a2d5e8091c3b7a4f60d2e5c8b1a4f7092c5d8e3b6a,
                            title: Restaking Yield Compression,
                            publishedAt: '2026-08-04T18:00:00Z',
                          }
                    meta:
                      snapshot:
                        {
                          completedAt: '2026-08-05T04:55:00Z',
                          latestEvidenceAt: '2026-08-05T04:52:10Z',
                          windowStartedAt: '2026-08-04T04:55:00Z',
                        }
                empty:
                  summary: Project with no associated Topics
                  value:
                    data: []
                    meta:
                      snapshot:
                        {
                          completedAt: '2026-08-05T04:55:00Z',
                          latestEvidenceAt: '2026-08-05T04:52:10Z',
                          windowStartedAt: '2026-08-04T04:55:00Z',
                        }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/topics/{topicId}:
    get:
      operationId: getTopic
      tags: [Topics]
      security: []
      summary: Get a Topic
      description: |
        Topic detail. Carries the versioned `stance` series: bucketed,
        target-scoped stance of the Topic conversation itself
        (`supportive`, `skeptical`, `neutral`, `mixed`, `unknown`),
        independent from Project-targeted sentiment.
      parameters:
        - name: topicId
          in: path
          required: true
          schema: { type: string, pattern: '^topic_[a-f0-9]{20}$' }
      responses:
        '200':
          description: Topic detail.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/TopicDetail' }
                  meta: { $ref: '#/components/schemas/TopicSnapshotMeta' }
              examples:
                success:
                  summary: Detail with stance series
                  value:
                    data:
                      id: topic_a1b2c3d4e5f601234567
                      rank: 1
                      name: Restaking yield compression
                      summary: Yields across restaking protocols compress as new AVS demand stalls.
                      trend: 93.2
                      postCount: 412
                      authorCount: 118
                      lastSeenAt: '2026-08-05T04:52:00Z'
                      clusters:
                        - {
                            cluster: { id: cl_defi, name: DeFi researchers },
                            share: 0.41,
                          }
                      report:
                        {
                          id: tr_9f2b6c41d8a35e07b1c4f6a2d5e8091c3b7a4f60d2e5c8b1a4f7092c5d8e3b6a,
                          title: Restaking Yield Compression,
                          publishedAt: '2026-08-04T18:00:00Z',
                        }
                      stance:
                        buckets:
                          - {
                              from: '2026-08-05T03:00:00Z',
                              to: '2026-08-05T04:00:00Z',
                              dominant: skeptical,
                              shares:
                                {
                                  supportive: 0.18,
                                  skeptical: 0.47,
                                  neutral: 0.22,
                                  mixed: 0.08,
                                  unknown: 0.05,
                                },
                            }
                    meta:
                      snapshot:
                        {
                          completedAt: '2026-08-05T04:55:00Z',
                          latestEvidenceAt: '2026-08-05T04:52:10Z',
                          windowStartedAt: '2026-08-04T04:55:00Z',
                        }
                      versions:
                        { stanceSchema: '1', stanceClassifier: stance-v2 }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/topics/{topicId}/series/{measure}:
    get:
      operationId: getTopicSeries
      tags: [Topics]
      summary: Get a Topic measurement series
      description: |
        The same measurement-series contract used by Projects, for one known
        Topic id. `trend` keeps the newest real completed score boundary in
        each hourly bucket. `rank` is the committed boundary series reduced
        to change points; `value` is `null` when a complete leaderboard
        publishes the Topic as unranked. Point timestamps preserve the actual
        completed measurement boundary. Topic history requires an API key.
      parameters:
        - name: topicId
          in: path
          required: true
          schema: { type: string, pattern: '^topic_[a-f0-9]{20}$' }
        - name: measure
          in: path
          required: true
          schema: { type: string, enum: [trend, rank] }
        - name: from
          in: query
          schema: { type: string, format: date-time }
        - name: to
          in: query
          schema: { type: string, format: date-time }
      responses:
        '200':
          description: Series page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    required: [topic, points]
                    properties:
                      topic: { $ref: '#/components/schemas/TopicRef' }
                      points:
                        type: array
                        items: { $ref: '#/components/schemas/SeriesPoint' }
                  meta: { $ref: '#/components/schemas/SeriesMeta' }
              examples:
                success:
                  summary: Hourly Topic trend series
                  value:
                    data:
                      topic:
                        {
                          id: topic_a1b2c3d4e5f601234567,
                          name: Restaking yield compression,
                        }
                      points:
                        - { at: '2026-08-05T03:45:00Z', value: 91.8 }
                        - { at: '2026-08-05T04:45:00Z', value: 93.2 }
                    meta:
                      measure: trend
                      basis: hourly
                      window:
                        {
                          from: '2026-08-04T05:00:00Z',
                          to: '2026-08-05T05:00:00Z',
                        }
                      versions:
                        {
                          trendModel: trend-nb-predictive-v2,
                          topicAlgorithm: trend-nb-predictive-v2.topic-relevance-v3,
                        }
                      history:
                        {
                          archiveAccess: false,
                          enforced: true,
                          lookbackDays: 90,
                          earliestAvailableAt: '2026-05-07T00:00:00Z',
                          enforcementAt: '2026-08-17T00:00:00Z',
                        }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /v3/topics/{topicId}/projects:
    get:
      operationId: listTopicProjects
      tags: [Topics]
      security: []
      summary: List Projects associated with a Topic
      description: |
        The authoritative Topic→Project association collection. An
        association is observational: canonical Project-attributed posts that
        are members of the Topic at the shared cutoff. It never implies
        partnership, endorsement, taxonomy, causality, or a primary Project.
        Coverage is a statement about source completeness, separate from
        association eligibility.
      x-collection: true
      parameters:
        - name: topicId
          in: path
          required: true
          schema: { type: string, pattern: '^topic_[a-f0-9]{20}$' }
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Association page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      { $ref: '#/components/schemas/TopicProjectAssociation' }
                  meta: { $ref: '#/components/schemas/AssociationMeta' }
              examples:
                success:
                  summary: Associations at the current window
                  value:
                    data:
                      - project:
                          {
                            id: 665f0a2b1c9d440000a1b2c3,
                            slug: examplechain,
                            name: ExampleChain,
                          }
                        observedPosts: 96
                        distinctAuthors: 44
                        shareOfTopicPosts: 0.23
                        firstObservedAt: '2026-08-04T06:10:00Z'
                        lastObservedAt: '2026-08-05T04:41:00Z'
                        sentiment: { populated: 61, coverage: 0.64 }
                    meta:
                      nextCursor: null
                      window:
                        {
                          from: '2026-08-04T04:55:00Z',
                          to: '2026-08-05T04:55:00Z',
                        }
                      versions:
                        {
                          resolver: project-resolver-v4,
                          membership: topic-membership-v2,
                        }
                empty:
                  summary: No qualifying associations
                  value:
                    data: []
                    meta:
                      nextCursor: null
                      window:
                        {
                          from: '2026-08-04T04:55:00Z',
                          to: '2026-08-05T04:55:00Z',
                        }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/intel:
    get:
      operationId: listIntel
      tags: [Intel]
      summary: List Intel
      description: |
        Discrete evidence-graded developments. `grades` filters on the
        evidence ladder from `chipped` to `flawless`. Project filters combine
        with AND across kinds and OR within one comma-separated value.

        `search` is free-text search: the query parses into meaningful terms
        that must all match, against project identity (names, tickers,
        handles, addresses, chains) and intel text. Matched items carry a
        `searchMatches` array explaining each match. Search results page
        through `meta.nextCursor`. Every non-empty query shape is accepted.
        When responsive candidate or recent-result bounds may omit matches,
        `meta.searchCoverage` names the bound and `meta.executedSearch`
        discloses any normalized scope that differs from the request.
      x-collection: true
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: search
          in: query
          description: Free-text deep search; combines with every other filter.
          schema: { type: string }
        - name: sort
          in: query
          description: |
            `detectedAt` orders by first sighting; `reinforcedAt` by the
            latest confirming observation of any kind (an echoed story).
          schema:
            {
              type: string,
              enum: [reinforcedAt, detectedAt],
              default: detectedAt,
            }
        - name: projectIds
          in: query
          schema: { type: string }
        - name: projectSlugs
          in: query
          schema: { type: string }
        - name: names
          in: query
          schema: { type: string }
        - name: xHandles
          in: query
          schema: { type: string }
        - name: tickers
          in: query
          schema: { type: string }
        - name: address
          in: query
          schema: { type: string }
        - name: assetTypes
          in: query
          description: Comma-separated asset types of the parent Project.
          schema: { type: string }
        - name: projectRanking
          in: query
          description: Restrict to Projects in the named ranking cohort.
          schema: { type: string, enum: [trend] }
        - name: clusterIds
          in: query
          schema: { type: string }
        - name: categories
          in: query
          description: Comma-separated intel event categories (see `/v3/vocabulary`).
          schema: { type: string }
        - name: grades
          in: query
          description: Comma-separated evidence grades.
          schema: { type: string }
        - name: sentiments
          in: query
          description: Comma-separated sentiment values.
          schema: { type: string }
        - name: hasOfficialSource
          in: query
          schema: { type: boolean }
        - name: minClusters
          in: query
          schema: { type: integer, minimum: 1 }
        - name: detectedAfter
          in: query
          schema: { type: string, format: date-time }
        - name: detectedBefore
          in: query
          schema: { type: string, format: date-time }
        - name: reinforcedAfter
          in: query
          schema: { type: string, format: date-time }
        - name: reinforcedBefore
          in: query
          schema: { type: string, format: date-time }
        - name: activity
          in: query
          schema:
            { type: string, enum: [all, observations, none], default: all }
      responses:
        '200':
          description: Intel page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Intel' }
                  meta: { $ref: '#/components/schemas/IntelCollectionMeta' }
              examples:
                success:
                  summary: Recent graded intel
                  value:
                    data:
                      - id: 6890aa000000000000000001
                        headline: Mainnet v2 date confirmed
                        description: ExampleChain confirmed the v2 mainnet date for September 3rd with a public changelog entry.
                        grade: polished
                        project:
                          {
                            id: 665f0a2b1c9d440000a1b2c3,
                            slug: examplechain,
                            name: ExampleChain,
                          }
                        category: product-launch
                        clusters: [{ id: cl_defi, name: DeFi researchers }]
                        observationCount: 9
                        hasOfficialSource: true
                        citations: [https://blog.example.chain/v2-mainnet]
                        sentiment: { value: positive }
                        detectedAt: '2026-08-04T21:14:00Z'
                        reinforcedAt: '2026-08-05T03:40:00Z'
                    meta:
                      nextCursor: b3BhcXVlLWN1cnNvcg
                      history:
                        {
                          archiveAccess: false,
                          enforced: true,
                          lookbackDays: 90,
                          earliestAvailableAt: '2026-05-07T00:00:00Z',
                          enforcementAt: '2026-08-17T00:00:00Z',
                        }
                empty:
                  summary: No intel matched the qualifiers
                  value:
                    data: []
                    meta: { nextCursor: null }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/intel/{id}:
    get:
      operationId: getIntel
      tags: [Intel]
      summary: Get an Intel item
      description: |
        Intel detail with the activity trail and cluster composition. The
        history boundary is enforced against the intel's own recency
        (`reinforcedAt`, falling back to `detectedAt`).
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: activity
          in: query
          schema:
            { type: string, enum: [all, observations, none], default: all }
      responses:
        '200':
          description: Intel detail.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/IntelDetail' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Detail with activity and composition
                  value:
                    data:
                      id: 6890aa000000000000000001
                      headline: Mainnet v2 date confirmed
                      description: ExampleChain confirmed the v2 mainnet date for September 3rd with a public changelog entry.
                      grade: polished
                      project:
                        {
                          id: 665f0a2b1c9d440000a1b2c3,
                          slug: examplechain,
                          name: ExampleChain,
                        }
                      category: product-launch
                      clusters: [{ id: cl_defi, name: DeFi researchers }]
                      observationCount: 9
                      hasOfficialSource: true
                      citations: [https://blog.example.chain/v2-mainnet]
                      sentiment: { value: positive }
                      detectedAt: '2026-08-04T21:14:00Z'
                      reinforcedAt: '2026-08-05T03:40:00Z'
                      activity:
                        - action: observation
                          detectedAt: '2026-08-04T21:14:00Z'
                          occurredAt: '2026-08-04T21:14:00Z'
                          eventTime:
                            {
                              status: instant,
                              at: '2026-09-03T00:00:00Z',
                              precision: day,
                              relationToSource: future,
                            }
                          headline: First sighting
                          source:
                            {
                              url: 'https://x.com/someone/status/1',
                              official: false,
                            }
                          sentiment: { value: positive }
                        - action: official_statement
                          detectedAt: '2026-08-04T22:03:00Z'
                          occurredAt: '2026-08-04T22:03:00Z'
                          eventTime: { status: unknown, reason: ambiguous }
                          source:
                            {
                              url: 'https://blog.example.chain/v2-mainnet',
                              official: true,
                            }
                      clusterComposition:
                        - {
                            cluster: { id: cl_defi, name: DeFi researchers },
                            percentage: 62.5,
                          }
                        - {
                            cluster: { id: cl_other, name: Other },
                            percentage: 37.5,
                          }
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/reports:
    get:
      operationId: listReports
      tags: [Reports]
      summary: List published Reports
      description: |
        Top-level read-only collection of published research artifacts, newest
        first, with relationship filters. List items are bounded previews;
        the full body requires the `topic-reports` entitlement on the detail
        route.
      x-collection: true
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: projectId
          in: query
          schema: { type: string }
        - name: projectSlug
          in: query
          schema: { type: string }
        - name: topicId
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Report page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/ReportListItem' }
                  meta: { $ref: '#/components/schemas/CollectionMeta' }
              examples:
                success:
                  summary: Latest reports
                  value:
                    data:
                      - id: tr_9f2b6c41d8a35e07b1c4f6a2d5e8091c3b7a4f60d2e5c8b1a4f7092c5d8e3b6a
                        title: Restaking Yield Compression
                        publishedAt: '2026-08-04T18:00:00Z'
                        revisedAt: null
                        primarySubject:
                          {
                            type: topic,
                            topic:
                              {
                                id: topic_a1b2c3d4e5f601234567,
                                name: Restaking yield compression,
                              },
                          }
                        relatedSubjects:
                          - {
                              type: project,
                              project:
                                {
                                  id: 665f0a2b1c9d440000a1b2c3,
                                  slug: examplechain,
                                  name: ExampleChain,
                                },
                            }
                        preview: Yields across restaking protocols compress as new AVS demand stalls...
                    meta: { nextCursor: null }
                empty:
                  summary: No reports for the filter
                  value:
                    data: []
                    meta: { nextCursor: null }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/reports/{id}:
    get:
      operationId: getReport
      tags: [Reports]
      summary: Get a published Report
      description: |
        Full report artifact: body, typed sources, and the publication-time
        evidence block. Requires the `topic-reports` entitlement. The
        evidence block quotes market figures with source attribution as
        part of the published research.
      x-required-entitlements: [topic-reports]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Report detail.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Report' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Full report
                  value:
                    data:
                      id: tr_9f2b6c41d8a35e07b1c4f6a2d5e8091c3b7a4f60d2e5c8b1a4f7092c5d8e3b6a
                      title: Restaking Yield Compression
                      publishedAt: '2026-08-04T18:00:00Z'
                      revisedAt: null
                      primarySubject:
                        {
                          type: topic,
                          topic:
                            {
                              id: topic_a1b2c3d4e5f601234567,
                              name: Restaking yield compression,
                            },
                        }
                      relatedSubjects:
                        - {
                            type: project,
                            project:
                              {
                                id: 665f0a2b1c9d440000a1b2c3,
                                slug: examplechain,
                                name: ExampleChain,
                              },
                          }
                      summary: Yields across restaking protocols compress as new AVS demand stalls.
                      body: "## Thesis\n\nYields compress because..."
                      sources:
                        - {
                            title: AVS demand dashboard,
                            url: 'https://dune.com/example/avs',
                            publishedAt: '2026-08-03T00:00:00Z',
                          }
                      evidence:
                        snapshotAt: '2026-08-04T18:00:00Z'
                        entities:
                          - project:
                              {
                                id: 665f0a2b1c9d440000a1b2c3,
                                slug: examplechain,
                                name: ExampleChain,
                              }
                            observations:
                              - {
                                  key: avs_count,
                                  value: 41,
                                  unit: count,
                                  provider: aixbt,
                                  observedAt: '2026-08-04T17:58:00Z',
                                  freshness: fresh,
                                }
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/alerts:
    get:
      operationId: listAlerts
      tags: [Alerts]
      summary: List my alert subscriptions
      description: |
        Alert subscriptions owned by the calling credential. One owner may
        hold at most 50 subscriptions, paused rows included. Lists the
        API-manageable kinds (`intelligence.material`, `rank.entered`);
        preset subscriptions managed in product Settings do not appear
        here.
      x-collection: true
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Alert page.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Alert' }
                  meta: { $ref: '#/components/schemas/CollectionMeta' }
              examples:
                success:
                  summary: Owner subscriptions
                  value:
                    data:
                      - id: 66a10a2b1c9d440000d1e2f3
                        status: active
                        trigger: intelligence.material
                        selector:
                          followedProjects: true
                          qualifiers:
                            {
                              grades: [polished, flawless],
                              categories: [product-launch],
                            }
                        cadence: hourly
                        destinationIds: [66a10a2b1c9d440000aa0001]
                        createdAt: '2026-08-01T10:00:00Z'
                        updatedAt: '2026-08-05T04:00:00Z'
                    meta: { nextCursor: null }
                empty:
                  summary: No subscriptions yet
                  value:
                    data: []
                    meta: { nextCursor: null }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
    post:
      operationId: createAlert
      tags: [Alerts]
      summary: Create an alert subscription
      description: |
        Creates an owner-scoped subscription: selector + one trigger +
        delivery cadence + explicit destination ids. Destinations are
        provisioned in product Settings and referenced here by id. Chat
        cadence defaults to `hourly`; `instant` is subject to a per-day
        platform safety ceiling. Webhook destinations receive every admitted
        event regardless of cadence. The 50-subscription owner cap and
        selector list bounds (20 values per list) are enforced. An account
        holds at most one `rank.entered` subscription; creating a second
        while one exists fails with `invalid_request`.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AlertCreate' }
            examples:
              materialIntel:
                summary: Material intel on followed Projects
                value:
                  trigger: intelligence.material
                  selector:
                    followedProjects: true
                    qualifiers: { grades: [polished, flawless] }
                  cadence: hourly
                  destinationIds: [66a10a2b1c9d440000aa0001]
              rankEntry:
                summary: Rank entry into the top 10
                value:
                  trigger: rank.entered
                  rankThreshold: 10
                  selector:
                    qualifiers: { categories: [modular-blockchains] }
                  cadence: instant
                  destinationIds: [66a10a2b1c9d440000aa0002]
      responses:
        '201':
          description: Created subscription.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Alert' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Created
                  value:
                    data:
                      id: 66a10a2b1c9d440000d1e2f4
                      status: active
                      trigger: rank.entered
                      rankThreshold: 10
                      selector:
                        { qualifiers: { categories: [modular-blockchains] } }
                      cadence: instant
                      destinationIds: [66a10a2b1c9d440000aa0002]
                      createdAt: '2026-08-05T05:00:00Z'
                      updatedAt: '2026-08-05T05:00:00Z'
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/alerts/{id}:
    get:
      operationId: getAlert
      tags: [Alerts]
      summary: Get an alert subscription
      description: Owner-scoped read; another owner's id behaves as `not_found`.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Subscription.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Alert' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: One subscription
                  value:
                    data:
                      id: 66a10a2b1c9d440000d1e2f3
                      status: active
                      trigger: intelligence.material
                      selector:
                        followedProjects: true
                      cadence: hourly
                      destinationIds: [66a10a2b1c9d440000aa0001]
                      createdAt: '2026-08-01T10:00:00Z'
                      updatedAt: '2026-08-05T04:00:00Z'
                    meta: {}
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
    patch:
      operationId: updateAlert
      tags: [Alerts]
      summary: Update an alert subscription
      description: |
        Partial update: pause/resume, rename, change cadence, selector,
        threshold, or destinations. Semantics are identical to creation.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AlertUpdate' }
            examples:
              pause:
                summary: Pause delivery
                value: { status: paused }
      responses:
        '200':
          description: Updated subscription.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Alert' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Paused
                  value:
                    data:
                      id: 66a10a2b1c9d440000d1e2f3
                      status: paused
                      trigger: intelligence.material
                      selector:
                        followedProjects: true
                      cadence: hourly
                      destinationIds: [66a10a2b1c9d440000aa0001]
                      createdAt: '2026-08-01T10:00:00Z'
                      updatedAt: '2026-08-05T05:10:00Z'
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
    delete:
      operationId: deleteAlert
      tags: [Alerts]
      summary: Delete an alert subscription
      description: Removes the subscription; delivery history retention is unaffected.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '204':
          description: Deleted.
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/SubjectMiss' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/alerts/destinations:
    get:
      operationId: listAlertDestinations
      tags: [Alerts]
      summary: List my delivery destinations
      description: |
        Read-only list of destinations connected in product Settings
        (Telegram, Discord, signed webhooks). Creation, secret rotation,
        and repair stay in Settings; subscriptions reference these ids.
      responses:
        '200':
          description: Destinations.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/AlertDestination' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Connected destinations
                  value:
                    data:
                      - {
                          id: 66a10a2b1c9d440000aa0001,
                          kind: telegram,
                          label: 'Team channel',
                          status: active,
                        }
                      - {
                          id: 66a10a2b1c9d440000aa0002,
                          kind: webhook,
                          label: 'prod ingest',
                          status: active,
                        }
                    meta: {}
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/clusters:
    get:
      operationId: listClusters
      tags: [Clusters]
      summary: List Clusters
      description: Audience/source archetypes used as an analysis dimension across Topics and Intel.
      parameters:
        - name: universe
          in: query
          schema: { type: string, enum: [crypto, tradfi], default: crypto }
      responses:
        '200':
          description: Clusters.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Cluster' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Crypto universe clusters
                  value:
                    data:
                      - {
                          id: cl_defi,
                          name: DeFi researchers,
                          description: Protocol analysts and yield researchers.,
                          universe: crypto,
                        }
                    meta: {}
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/vocabulary:
    get:
      operationId: getVocabulary
      tags: [Vocabulary]
      summary: Get the filter vocabularies
      description: |
        One discovery surface for every value a v3 filter accepts. Closed
        vocabularies (grades, sentiments, stances, sort keys, activity modes,
        rankings, universes, asset types, series measures) are versioned with
        the contract; catalog-derived sets (project categories, intel
        categories, chains) are composed from their owning catalogs at read
        time and carry their own versions.
      responses:
        '200':
          description: Vocabularies.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Vocabulary' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: Current vocabularies
                  value:
                    data:
                      grades: [chipped, flawed, cut, polished, flawless]
                      sentiments: [positive, neutral, negative]
                      stances: [supportive, skeptical, neutral, mixed, unknown]
                      assetTypes: [token, stablecoin, nft-collection]
                      projectSort: [trend, createdAt, reinforcedAt]
                      intelSort: [reinforcedAt, detectedAt]
                      activityModes: [all, observations, none]
                      projectRankings: [trend]
                      universes: [crypto, tradfi]
                      seriesMeasures: [trend, rank]
                      alertTriggers: [intelligence.material, rank.entered]
                      alertCadences: [instant, hourly, every_12h, daily]
                      projectCategories:
                        [
                          {
                            slug: modular-blockchains,
                            name: Modular blockchains,
                          },
                        ]
                      intelCategories:
                        [{ slug: product-launch, name: Product launch }]
                      chains: [ethereum, solana, base]
                    meta:
                      versions:
                        { vocabulary: '3.0.0-draft.1', taxonomy: taxonomy-v2 }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/me:
    get:
      operationId: getMe
      tags: [Me]
      summary: Get the calling credential's capabilities
      description: |
        Everything an integration needs to plan requests: credential
        metadata, entitlements, effective rate limits, and the history
        window. Replaces `/v2/api-keys/info`.
      responses:
        '200':
          description: Credential capabilities.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/Me' }
                  meta: { $ref: '#/components/schemas/Meta' }
              examples:
                success:
                  summary: A full-tier key
                  value:
                    data:
                      credential:
                        {
                          id: key_01,
                          name: prod-bot,
                          type: full,
                          expiresAt: null,
                        }
                      entitlements: [api, topic-reports]
                      rateLimit: { perMinute: 30, perDay: 10000 }
                      history:
                        {
                          archiveAccess: false,
                          enforced: true,
                          lookbackDays: 90,
                          earliestAvailableAt: '2026-05-07T00:00:00Z',
                          enforcementAt: '2026-08-17T00:00:00Z',
                        }
                    meta: {}
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }

  /v3/openapi.yaml:
    get:
      operationId: getOpenApiDocument
      tags: [Meta]
      summary: Get this contract
      description: The canonical v3 OpenAPI document, served verbatim. No credential required.
      security: []
      responses:
        '200':
          description: This document.
          content:
            application/yaml:
              schema: { type: string }
        '500': { $ref: '#/components/responses/Internal' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: |
        API key. The `Authorization` header is accepted as an equivalent
        carrier. Responses echo the credential class in `X-API-Key-Type`.

  parameters:
    Cursor:
      name: cursor
      in: query
      description: Opaque cursor from `meta.nextCursor`.
      schema: { type: string }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 50, default: 50 }
    IdOrSlug:
      name: idOrSlug
      in: path
      required: true
      description: Canonical Project id or public slug (aliases resolve).
      schema: { type: string }

  schemas:
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum:
            - invalid_request
            - credential_required
            - invalid_credential
            - entitlement_required
            - history_window_exceeded
            - not_found
            - not_current
            - rate_limited
            - internal_error
            - service_unavailable
        message: { type: string }
        details:
          type: object
          additionalProperties: true
          description: Optional structured context (field errors, subject locators, limits).

    Meta:
      type: object
      description: Response metadata. Empty when nothing applies.
      properties:
        history: { $ref: '#/components/schemas/HistoryMeta' }
        versions:
          type: object
          additionalProperties: { type: string }
          description: Named component versions that shaped this response.

    CollectionMeta:
      allOf:
        - $ref: '#/components/schemas/Meta'
        - type: object
          required: [nextCursor]
          properties:
            nextCursor:
              oneOf: [{ type: string }, { type: 'null' }]
              description: Cursor for the next page; `null` on the last page.

    IntelCollectionMeta:
      allOf:
        - $ref: '#/components/schemas/CollectionMeta'
        - type: object
          properties:
            searchCoverage:
              type: string
              enum: [bounded_candidates, bounded_recent, bounded_candidates_recent]
              description: Responsive search bounds that may have omitted matching Intel.
            searchQuery:
              type: string
              description: Normalized search requested by the caller.
            executedSearch:
              type: string
              description: Exact normalized term scope executed by bounded search.

    HistoryMeta:
      type: object
      required: [archiveAccess, enforced, lookbackDays, enforcementAt]
      properties:
        archiveAccess: { type: boolean }
        enforced: { type: boolean }
        lookbackDays: { type: integer }
        earliestAvailableAt: { type: string, format: date-time }
        enforcementAt: { type: string, format: date-time }

    ProjectRef:
      type: object
      required: [id, slug, name]
      properties:
        id: { type: string }
        slug:
          { type: string, pattern: '^[a-z0-9]+(-[a-z0-9]+)*$', maxLength: 64 }
        name: { type: string }

    TopicRef:
      type: object
      required: [id, name]
      properties:
        id: { type: string }
        name: { type: string }

    ClusterRef:
      type: object
      required: [id, name]
      properties:
        id: { type: string }
        name: { type: string }

    ReportRef:
      type: object
      required: [id, title, publishedAt]
      properties:
        id: { type: string }
        title: { type: string }
        publishedAt: { type: string, format: date-time }

    IntelRef:
      type: object
      required: [id, grade, detectedAt]
      properties:
        id: { type: string }
        headline: { type: string }
        grade: { $ref: '#/components/schemas/Grade' }
        detectedAt: { type: string, format: date-time }

    Grade:
      type: string
      enum: [chipped, flawed, cut, polished, flawless]
      description: |
        Evidence grade of an Intel item, lowest to highest. Grades describe
        the shape of the evidence, never the truth of the claim.

    Sentiment:
      type: object
      required: [value]
      properties:
        value:
          type: string
          enum: [positive, neutral, negative]

    Identifiers:
      type: object
      description: |
        Stable external join keys for composing AIXBT data with sources the
        consumer licenses directly. Keys are additive per universe (tradfi
        adds e.g. exchange listing identifiers); values are identifiers
        only; v3 never attaches the external source's data to them.
      properties:
        coingecko:
          type: string
          description: CoinGecko API id of the primary asset.
        x:
          type: string
          description: X handle without the at sign.

    Asset:
      type: object
      required: [type, primary]
      description: |
        Canonical instrument identity owned by the Asset registry. Identity
        only; v3 never attaches third-party market values to an asset.
        Universe-specific fields are additive per type (crypto types carry
        `chain` and `address`).
      properties:
        type:
          type: string
          enum: [token, stablecoin, nft-collection]
        chain: { type: string }
        address: { type: string }
        symbol: { type: string }
        primary:
          type: boolean
          description: The Project's primary instrument.

    Project:
      type: object
      required:
        [
          id,
          slug,
          name,
          universe,
          trend,
          categories,
          assets,
          createdAt,
          updatedAt,
        ]
      properties:
        id: { type: string }
        slug: { type: string }
        name: { type: string }
        universe:
          type: string
          enum: [crypto, tradfi]
          description: Entity universe; shares the Cluster enum. Ranks are universe-scoped.
        symbol: { type: string }
        description: { type: string }
        identifiers: { $ref: '#/components/schemas/Identifiers' }
        image: { type: string, format: uri }
        trend:
          type: number
          description: |
            Canonical trend measurement from the shared trend model.
            Comparable only within the same `meta.versions.trendModel`.
        trendDelta:
          type: object
          required: [value, window]
          description: |
            Series-derived change over the stated window (PT24H), computed
            from the hourly trend history. Emitted on Project detail; absent
            when history is too shallow to state one.
          properties:
            value: { type: number }
            window: { type: string, enum: [PT24H] }
        rank:
          type: integer
          description: Committed leaderboard rank within the Project's universe; absent while unranked.
        categories:
          type: array
          items: { type: string }
          description: Canonical taxonomy slugs.
        assets:
          type: array
          items: { $ref: '#/components/schemas/Asset' }
        intel:
          type: array
          items: { $ref: '#/components/schemas/IntelRef' }
          description: Bounded preview of current Intel, newest first.
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
        reinforcedAt: { type: string, format: date-time }

    ProjectDetail:
      allOf:
        - $ref: '#/components/schemas/Project'
        - type: object
          properties:
            relatedProjects:
              type: array
              items: { $ref: '#/components/schemas/RelatedProject' }

    RelatedProject:
      type: object
      required: [project, kind, sharedIntel, window, asOf]
      properties:
        project: { $ref: '#/components/schemas/ProjectRef' }
        kind:
          type: string
          enum: [co_mention]
          description: Observational relation kind; never partnership or endorsement.
        sharedIntel:
          type: integer
          description: Distinct co-mentioning Intel items in the window.
        window:
          {
            type: string,
            description: ISO 8601 duration of the observation window.,
          }
        asOf: { type: string, format: date-time }

    SeriesPoint:
      type: object
      required: [at, value]
      properties:
        at: { type: string, format: date-time }
        value:
          oneOf: [{ type: number }, { type: 'null' }]
          description: '`null` marks a boundary where the subject was unranked or unmeasured.'
        clusters:
          type: array
          items:
            type: object
            required: [id, count]
            properties:
              id: { type: string }
              name: { type: string }
              count: { type: integer }

    SeriesMeta:
      allOf:
        - $ref: '#/components/schemas/Meta'
        - type: object
          required: [measure, basis, window]
          properties:
            measure: { type: string, enum: [trend, rank] }
            basis:
              type: string
              enum: [hourly, rank_transitions]
              description: How points are spaced, either the latest real measurement per hourly bucket or rank change points.
            window:
              type: object
              required: [from, to]
              properties:
                from: { type: string, format: date-time }
                to: { type: string, format: date-time }

    TopicListItem:
      type: object
      required:
        [id, rank, name, summary, trend, postCount, authorCount, lastSeenAt]
      properties:
        id: { type: string }
        rank: { type: integer }
        name: { type: string }
        summary: { type: string }
        trend: { type: number }
        postCount: { type: integer }
        authorCount: { type: integer }
        lastSeenAt: { type: string, format: date-time }
        clusters:
          type: array
          items:
            type: object
            required: [cluster, share]
            properties:
              cluster: { $ref: '#/components/schemas/ClusterRef' }
              share: { type: number, minimum: 0, maximum: 1 }
        report:
          oneOf:
            - $ref: '#/components/schemas/ReportRef'
            - type: 'null'
        association:
          $ref: '#/components/schemas/TopicAssociationFacts'

    TopicDetail:
      allOf:
        - $ref: '#/components/schemas/TopicListItem'
        - type: object
          properties:
            stance: { $ref: '#/components/schemas/StanceSeries' }

    StanceSeries:
      type: object
      required: [buckets]
      description: |
        Bucketed stance of the Topic conversation itself, independent from
        Project-targeted sentiment. Versions travel in `meta.versions`.
      properties:
        buckets:
          type: array
          items:
            type: object
            required: [from, to, dominant, shares]
            properties:
              from: { type: string, format: date-time }
              to: { type: string, format: date-time }
              dominant: { $ref: '#/components/schemas/Stance' }
              shares:
                type: object
                additionalProperties: { type: number }
                description: Share per stance value, summing to 1.

    Stance:
      type: string
      enum: [supportive, skeptical, neutral, mixed, unknown]

    TopicSnapshotMeta:
      allOf:
        - $ref: '#/components/schemas/Meta'
        - type: object
          required: [snapshot]
          properties:
            snapshot:
              type: object
              required: [completedAt, windowStartedAt]
              properties:
                completedAt: { type: string, format: date-time }
                latestEvidenceAt: { type: string, format: date-time }
                windowStartedAt: { type: string, format: date-time }

    TopicAssociationFacts:
      type: object
      required:
        [
          observedPosts,
          distinctAuthors,
          shareOfTopicPosts,
          firstObservedAt,
          lastObservedAt,
        ]
      properties:
        observedPosts: { type: integer }
        distinctAuthors: { type: integer }
        shareOfTopicPosts: { type: number, minimum: 0, maximum: 1 }
        firstObservedAt: { type: string, format: date-time }
        lastObservedAt: { type: string, format: date-time }
        sentiment:
          type: object
          properties:
            populated: { type: integer }
            coverage: { type: number, minimum: 0, maximum: 1 }

    TopicProjectAssociation:
      allOf:
        - type: object
          required: [project]
          properties:
            project: { $ref: '#/components/schemas/ProjectRef' }
        - $ref: '#/components/schemas/TopicAssociationFacts'

    AssociationMeta:
      allOf:
        - $ref: '#/components/schemas/CollectionMeta'
        - type: object
          required: [window]
          properties:
            window:
              type: object
              required: [from, to]
              properties:
                from: { type: string, format: date-time }
                to: { type: string, format: date-time }

    Intel:
      type: object
      required:
        [
          id,
          description,
          grade,
          project,
          category,
          observationCount,
          hasOfficialSource,
          citations,
          detectedAt,
          reinforcedAt,
        ]
      properties:
        id: { type: string }
        headline: { type: string }
        description: { type: string }
        grade: { $ref: '#/components/schemas/Grade' }
        project: { $ref: '#/components/schemas/ProjectRef' }
        category:
          type: string
          description: Intel event category (a vocabulary distinct from Project taxonomy).
        clusters:
          type: array
          items: { $ref: '#/components/schemas/ClusterRef' }
        observationCount: { type: integer }
        hasOfficialSource: { type: boolean }
        officialSourceUrl:
          type: string
          format: uri
          description: Canonical official X provenance for the intel, including research-only observations omitted from activity.
        citations:
          type: array
          items: { type: string, format: uri }
          description: Vetted non-X citations supporting this intel.
        sentiment: { $ref: '#/components/schemas/Sentiment' }
        detectedAt: { type: string, format: date-time }
        reinforcedAt:
          type: string
          format: date-time
          nullable: true
          description: Null until the first attention-contributing source activates the gem.
        activity:
          type: array
          items: { $ref: '#/components/schemas/IntelActivity' }
        searchMatches:
          type: array
          description: |
            Present only on an active `search`: why this item matched,
            derived from stored data.
          items:
            type: object
            required: [kind, term]
            properties:
              kind:
                type: string
                enum:
                  [
                    contract_address,
                    canonical_chain,
                    current_official_x_handle,
                    project_identity,
                    asset_profile,
                    current_official_x_profile,
                    project_profile,
                    intel_text,
                  ]
              term: { type: string }
              excerpt:
                type: string
                description: Short public text containing the match, when it is not already visible.

    IntelActivity:
      type: object
      required: [action, detectedAt, occurredAt]
      description: |
        One entry in the intel evidence trail. Detection time records when the
        source was observed; event time records when the source claims the
        event happened.
      properties:
        action:
          type: string
          enum: [observation, official_statement, correction, merge]
        detectedAt:
          type: string
          format: date-time
          description: Source observation timestamp.
        occurredAt:
          type: string
          format: date-time
          deprecated: true
          x-successor: '#/components/schemas/IntelActivity/properties/detectedAt'
          description: >-
            Deprecated legacy timestamp: provider publication when known,
            otherwise detectedAt. Use eventTime for claimed event time.
        eventTime: { $ref: '#/components/schemas/IntelEventTime' }
        headline: { type: string }
        source:
          type: object
          required: [url, official]
          properties:
            url: { type: string, format: uri }
            official: { type: boolean }
        sentiment: { $ref: '#/components/schemas/Sentiment' }
        clusters:
          type: array
          items: { $ref: '#/components/schemas/ClusterRef' }
    IntelEventTime:
      description: Claimed event time, sanitized from internal source evidence.
      oneOf:
        - type: object
          additionalProperties: false
          required: [status, at, precision, relationToSource]
          properties:
            status: { type: string, const: instant }
            at: { type: string, format: date-time }
            precision: { type: string, enum: [instant, day, week, month] }
            relationToSource:
              { type: string, enum: [current, historical, future] }
        - type: object
          additionalProperties: false
          required: [status, startAt, endAt, precision, relationToSource]
          properties:
            status: { type: string, const: interval }
            startAt: { type: string, format: date-time }
            endAt:
              type: string
              format: date-time
              description: Exclusive boundary after the final supported period.
            precision: { type: string, enum: [day, week, month] }
            relationToSource:
              { type: string, enum: [current, historical, future] }
        - type: object
          additionalProperties: false
          required: [status, relationToSource]
          properties:
            status: { type: string, const: relative_unresolved }
            relationToSource:
              { type: string, enum: [before_post, after_post, around_post] }
        - type: object
          additionalProperties: false
          required: [status, reason]
          properties:
            status: { type: string, const: unknown }
            reason: { type: string, enum: [not_stated, ambiguous, ongoing] }

    IntelDetail:
      allOf:
        - $ref: '#/components/schemas/Intel'
        - type: object
          properties:
            clusterComposition:
              type: array
              description: Top clusters by share, with an `Other` bucket.
              items:
                type: object
                required: [cluster, percentage]
                properties:
                  cluster: { $ref: '#/components/schemas/ClusterRef' }
                  percentage: { type: number, minimum: 0, maximum: 100 }

    ReportSubject:
      type: object
      required: [type]
      properties:
        type: { type: string, enum: [project, topic] }
        project: { $ref: '#/components/schemas/ProjectRef' }
        topic: { $ref: '#/components/schemas/TopicRef' }

    ReportListItem:
      type: object
      required: [id, title, publishedAt, primarySubject, preview]
      properties:
        id: { type: string }
        title: { type: string }
        publishedAt: { type: string, format: date-time }
        revisedAt:
          oneOf: [{ type: string, format: date-time }, { type: 'null' }]
        primarySubject: { $ref: '#/components/schemas/ReportSubject' }
        relatedSubjects:
          type: array
          items: { $ref: '#/components/schemas/ReportSubject' }
          description: Ordered, deduplicated; never inferred from prose.
        preview: { type: string }

    Report:
      allOf:
        - $ref: '#/components/schemas/ReportListItem'
        - type: object
          required: [summary, body, sources]
          properties:
            summary: { type: string }
            body:
              {
                type: string,
                description: Markdown body of the published artifact without internal source-id markers.,
              }
            sources:
              type: array
              items:
                type: object
                required: [title, url]
                properties:
                  title: { type: string }
                  url: { type: string, format: uri }
                  publishedAt: { type: string, format: date-time }
            evidence:
              type: object
              description: |
                Publication-time evidence quoted by the artifact, with source
                attribution and observation timestamps. Immutable once
                published.
              properties:
                snapshotAt: { type: string, format: date-time }
                entities:
                  type: array
                  items:
                    type: object
                    required: [project, observations]
                    properties:
                      project: { $ref: '#/components/schemas/ProjectRef' }
                      observations:
                        type: array
                        items:
                          type: object
                          required:
                            [key, value, unit, provider, observedAt, freshness]
                          properties:
                            key: { type: string }
                            value: { type: number }
                            unit: { type: string }
                            provider: { type: string }
                            observedAt:
                              description: |
                                Source-side observation timestamp. Null when
                                the provider publishes the figure without
                                one.
                              oneOf:
                                - { type: string, format: date-time }
                                - { type: 'null' }
                            freshness: { type: string }
                            disagreement: { type: number }

    AlertTrigger:
      type: string
      enum: [intelligence.material, rank.entered]
      description: The set of launch triggers.

    AlertCadence:
      type: string
      enum: [instant, hourly, every_12h, daily]
      description: |
        Chat delivery cadence. First event in a window delivers immediately;
        the window bundles the rest. `instant` is subject to a per-day
        platform safety ceiling. Webhooks ignore cadence and receive every
        admitted event.

    AlertQualifiers:
      type: object
      description: |
        Filter qualifiers; each list holds at most 20 values.
        `grades`, `sentiments`, `clusterIds`, `maxRank`, and
        `hasOfficialSource` qualify `intelligence.material` subscriptions;
        `chains` qualifies `rank.entered`; `categories` and `assetTypes`
        qualify both.
      properties:
        categories:
          type: array
          items: { type: string }
        grades:
          type: array
          items: { $ref: '#/components/schemas/Grade' }
        sentiments:
          type: array
          items: { type: string }
        assetTypes:
          type: array
          items: { type: string }
        clusterIds:
          type: array
          items: { type: string }
        chains:
          type: array
          items: { type: string }
        maxRank:
          type: integer
          minimum: 1
          maximum: 100
          description: Only intel on Projects at or above this trend rank.
        hasOfficialSource: { type: boolean }

    AlertSelector:
      type: object
      description: |
        What the subscription watches: the owner's followed Projects, the
        currently ranked cohort, a normalized keyword, or a qualifier-only cohort.
        Explicit per-Project and per-Topic id lists are not yet supported
        and will join additively.
      properties:
        followedProjects:
          type: boolean
          description: Only the owner's followed Projects. Excludes `trendingProjects`.
        trendingProjects:
          type: boolean
          description: Only currently ranked Projects. Excludes `followedProjects`.
        keyword:
          type: string
          minLength: 2
          maxLength: 64
          description: Normalized once at save time; matched case-insensitively against source-derived text.
        qualifiers: { $ref: '#/components/schemas/AlertQualifiers' }

    Alert:
      type: object
      required:
        [
          id,
          status,
          trigger,
          selector,
          cadence,
          destinationIds,
          createdAt,
          updatedAt,
        ]
      properties:
        id: { type: string }
        status:
          type: string
          enum: [active, paused]
        trigger: { $ref: '#/components/schemas/AlertTrigger' }
        rankThreshold:
          type: integer
          minimum: 1
          maximum: 100
          description: Top-N boundary; present only with `rank.entered`.
        selector: { $ref: '#/components/schemas/AlertSelector' }
        cadence: { $ref: '#/components/schemas/AlertCadence' }
        destinationIds:
          type: array
          items: { type: string }
          minItems: 1
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    AlertCreate:
      type: object
      required: [trigger, selector, destinationIds]
      properties:
        trigger: { $ref: '#/components/schemas/AlertTrigger' }
        rankThreshold:
          type: integer
          minimum: 1
          maximum: 100
          description: Required when `trigger` is `rank.entered`.
        selector: { $ref: '#/components/schemas/AlertSelector' }
        cadence:
          allOf: [{ $ref: '#/components/schemas/AlertCadence' }]
          default: hourly
        destinationIds:
          type: array
          items: { type: string }
          minItems: 1

    AlertUpdate:
      type: object
      description: Partial update; omitted fields are unchanged.
      properties:
        status:
          type: string
          enum: [active, paused]
        rankThreshold: { type: integer, minimum: 1, maximum: 100 }
        selector: { $ref: '#/components/schemas/AlertSelector' }
        cadence: { $ref: '#/components/schemas/AlertCadence' }
        destinationIds:
          type: array
          items: { type: string }
          minItems: 1

    AlertDestination:
      type: object
      required: [id, kind, label, status]
      properties:
        id: { type: string }
        kind:
          type: string
          enum: [telegram, discord, webhook]
        label: { type: string }
        status:
          type: string
          enum: [pending, active, failing, disabled]

    Cluster:
      type: object
      required: [id, name, description]
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string }
        universe: { type: string, enum: [crypto, tradfi] }

    Vocabulary:
      type: object
      required:
        [
          grades,
          sentiments,
          stances,
          assetTypes,
          projectSort,
          intelSort,
          activityModes,
          projectRankings,
          universes,
          seriesMeasures,
          alertTriggers,
          alertCadences,
          projectCategories,
          intelCategories,
          chains,
        ]
      properties:
        grades:
          type: array
          items: { $ref: '#/components/schemas/Grade' }
        sentiments:
          type: array
          items: { type: string }
        stances:
          type: array
          items: { $ref: '#/components/schemas/Stance' }
        assetTypes:
          type: array
          items: { type: string }
        projectSort:
          type: array
          items: { type: string }
        intelSort:
          type: array
          items: { type: string }
        activityModes:
          type: array
          items: { type: string }
        projectRankings:
          type: array
          items: { type: string }
        universes:
          type: array
          items: { type: string }
        seriesMeasures:
          type: array
          items: { type: string }
        alertTriggers:
          type: array
          items: { $ref: '#/components/schemas/AlertTrigger' }
        alertCadences:
          type: array
          items: { $ref: '#/components/schemas/AlertCadence' }
        projectCategories:
          type: array
          items:
            type: object
            required: [slug, name]
            properties:
              slug: { type: string }
              name: { type: string }
        intelCategories:
          type: array
          items:
            type: object
            required: [slug, name]
            properties:
              slug: { type: string }
              name: { type: string }
        chains:
          type: array
          items: { type: string }

    Me:
      type: object
      required: [credential, entitlements, rateLimit, history]
      properties:
        credential:
          type: object
          required: [id, name, type, expiresAt]
          properties:
            id: { type: string }
            name: { type: string }
            type: { type: string, enum: [full, oauth, x402] }
            expiresAt:
              oneOf: [{ type: string, format: date-time }, { type: 'null' }]
        entitlements:
          type: array
          items: { type: string }
        rateLimit:
          type: object
          required: [perMinute, perDay]
          properties:
            perMinute: { type: integer }
            perDay: { type: integer }
        history: { $ref: '#/components/schemas/HistoryMeta' }

  responses:
    BadRequest:
      description: The request is invalid.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            invalidRequest:
              value:
                {
                  code: invalid_request,
                  message: 'minTrend must be numeric',
                  details: { fields: [minTrend] },
                }
    Unauthorized:
      description: The credential is missing or invalid.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            credentialRequired:
              value:
                {
                  code: credential_required,
                  message: 'Provide an API key via the x-api-key header.',
                }
            invalidCredential:
              value:
                {
                  code: invalid_credential,
                  message: 'The API key is expired or unknown.',
                }
    Forbidden:
      description: The credential lacks an entitlement or crossed the history boundary.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            entitlementRequired:
              value:
                {
                  code: entitlement_required,
                  message: 'This resource requires the topic-reports entitlement.',
                  details: { entitlement: topic-reports },
                }
            historyWindow:
              value:
                {
                  code: history_window_exceeded,
                  message: 'Requested instant is outside the 90-day history window.',
                  details: { earliestAvailableAt: '2026-05-07T00:00:00Z' },
                }
    SubjectMiss:
      description: The subject was not found or is not current.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            notFound:
              value:
                {
                  code: not_found,
                  message: 'No Project matches that locator.',
                  details:
                    { subject: { type: project, locator: examplechain } },
                }
            notCurrent:
              value:
                {
                  code: not_current,
                  message: 'The Topic exists but is no longer current.',
                  details:
                    {
                      subject:
                        { type: topic, locator: topic_a1b2c3d4e5f601234567 },
                    },
                }
    RateLimited:
      description: Rate limit exceeded; retry after the indicated delay.
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            rateLimited:
              value:
                {
                  code: rate_limited,
                  message: 'Minute limit reached.',
                  details: { limit: 30, window: minute },
                }
    Internal:
      description: Unexpected server error.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            internal:
              value: { code: internal_error, message: 'Unexpected error.' }
    Unavailable:
      description: A required backing store is unavailable.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            unavailable:
              value:
                {
                  code: service_unavailable,
                  message: 'Measurement history is temporarily unavailable.',
                }
