openapi: 3.1.0
info:
  title: Y2 Intelligence Platform API
  version: 1.0.0
  description: |
    Y2 provides AI-powered intelligence reports, real-time news aggregation,
    and webhook delivery for automated workflows.

    ## Authentication

    Authenticated operations accept a scoped API key as a Bearer token:
    ```
    Authorization: Bearer y2_your_api_key_here
    ```

    API keys can be created in the Y2 dashboard under Developers > API Keys.
    Each key has specific scopes that determine which endpoints it can access.

    Operations that declare x402 support can instead be called through their
    pay-per-request flow. Agent Y2 is API-key only, and the sanitized x402
    receipt lookup is public. Check each operation's `security`,
    `x-required-scopes`, and `x-x402` fields instead of assuming one auth mode
    for the entire document.

    ## Rate Limiting

    New API-key access requires a **Pro or Elite** workspace plan. Lite retains
    in-app features and webhook delivery. Existing Lite API users keep their
    current keys at historical limits, but Lite cannot create additional keys.
    x402 pay-per-request access is unchanged.

    Rate limits are enforced per API key **and** per account (workspace when scoped,
    otherwise user; aggregate across all keys):

    | Plan | Per-Minute | Per-Day | Max Keys |
    |------|-----------|---------|----------|
    | Free | — | — | 0 |
    | Lite grace* | 10 | 500 | 0 new |
    | Pro | 30 | 5,000 | 5 |
    | Elite | 120 | 50,000 | 25 |

    *Only keys already belonging to Lite API users are grandfathered.

    Per-key limits can be self-restricted below plan defaults.
    Rate limit headers are included in all responses.

    ## Response Headers

    All responses include:
    - `X-Y2-API-Version` — `1.0` on `/api/v1/*` endpoints, `2.0` on `/api/v2/*` endpoints

    ## Scopes

    | Scope | Description |
    |-------|-------------|
    | `reports:read` | Read intelligence reports |
    | `reports:audio` | Access report audio files |
    | `profiles:read` | List subscribed profiles |
    | `profiles:write` | Create, edit, and delete owned profiles |
    | `news:read` | Read Y2 News Terminal data |
    | `osint:read` | Read OSINT intelligence data |
    | `intel:finint` | Read dedicated financial intelligence endpoints |
    | `intel:cyber` | Read dedicated cyber intelligence endpoints |
    | `intel:explorer` | Read entity explorer and graph endpoints |
    | `intel:knowledge` | Retrieve authorized Y2 Global Knowledge context |
    | `projects:read` | Read owned Projects in the API key workspace |
    | `projects:write` | Create and update owned Projects |
    | `automations:read` | Read owned Automation definitions and run history |
    | `automations:write` | Create, update, and run owned Automations |
    | `agent:y2` | Use the preconfigured Y2 Agent through the API |
    | `webhooks:manage` | Manage webhook configurations |

  license:
    name: Proprietary
    url: https://y2.dev/terms
  contact:
    name: Y2 Support
    url: https://y2.dev/docs/community/contributing

servers:
  - url: https://api.y2.dev/api/v1
    description: Y2 Production API (v1)
  - url: https://data.y2.dev/api/v1
    description: Y2 Convex Data API (v1)
  - url: https://api.y2.dev
    description: Y2 Production API (v2 root — Intel surface)

security:
  - bearerAuth: []

tags:
  - name: Reports
    description: Report retrieval, text, audio, signals, and ontology graphs
  - name: Profiles
    description: Profile subscriptions, ownership, and configuration
  - name: Projects
    description: Owner-private intelligence workspaces in the API key workspace
  - name: Automations
    description: Durable Agent Y2 Automation definitions and run history
  - name: Webhooks
    description: Webhook configuration for paid workspaces
  - name: Subscriptions
    description: Subscription delivery management
  - name: News
    description: News Terminal items, feeds, and AI recaps
  - name: OSINT
    x-displayName: OSINT
    description: Situation Room events, feeds, country data, and source health
  - name: Intel
    description: |
      Ontology-backed v2 incidents, entities, markets, financial indicators,
      emergent signals, and cyber graphs. Served from `https://api.y2.dev/api/v2/*`.
      Operations accept Bearer API keys or x402 pay-per-request. Required scopes vary
      by resource: `intel:finint`, `intel:cyber`, or `intel:explorer`.

      The in-app Cyber dashboard is available on all plans. New API-key access to FININT,
      Cyber, and Explorer requires Pro or Elite; existing Lite API keys are grandfathered.
  - name: Global Knowledge
    description: |
      Bounded, provider-neutral semantic and lexical retrieval across authorized Y2 reports
      and signal assessments. This surface is API-key-only and does not support x402.
  - name: Payments
    description: x402 receipt lookup
  - name: Agent Y2
    description: API-key streams for the preconfigured Y2 Agent and compatibility endpoint

paths:
  /agent-y2/chat/stream:
    post:
      operationId: streamAgentY2Chat
      summary: Stream a Y2 Agent response
      description: |
        Streams the preconfigured Y2 Agent with the same tools, entitlements,
        chat-credit budget, and API-key rate limits as the Y2 app copilot.

        This is not a general multi-model API. v1 callers cannot choose models,
        use onboarding mode, or attach files. Transcript history supplies
        user/assistant context but cannot override the fixed system instructions.

        v1 requires an API key and does not support x402. Both Agent Y2 routes share
        a throttle of 5 requests/minute and 100/day per key, plus 10 requests/minute
        and 250/day per user or workspace. A `402` indicates subscription,
        chat-credit, or upstream-provider credit exhaustion, not an x402 challenge.
      tags: [Agent Y2]
      security:
        - bearerAuth: []
      x-required-scopes: [agent:y2]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentY2StreamRequest"
            example:
              messages:
                - role: user
                  parts:
                    - type: text
                      text: What changed in cyber risk this week?
              metadata:
                source: customer_crm
                externalUserId: analyst-42
      responses:
        "200":
          description: Vercel AI SDK UI message stream
          headers:
            X-Thread-Id:
              description: Y2 chat thread ID for continuing the conversation
              schema:
                type: string
            X-Y2-Agent:
              description: Agent identifier
              schema:
                type: string
                example: y2
            X-Y2-Agent-Mode:
              description: Agent mode
              schema:
                type: string
                example: copilot
            X-Y2-Agent-RateLimit-Limit-Minute:
              description: Agent Y2 requests allowed per minute for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Remaining-Minute:
              description: Agent Y2 requests remaining this minute for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Limit-Day:
              description: Agent Y2 requests allowed per day for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Remaining-Day:
              description: Agent Y2 requests remaining today for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Limit-Minute:
              description: Agent Y2 requests allowed per minute for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Remaining-Minute:
              description: Agent Y2 requests remaining this minute for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Limit-Day:
              description: Agent Y2 requests allowed per day for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Remaining-Day:
              description: Agent Y2 requests remaining today for the user or workspace
              schema:
                type: integer
          content:
            text/event-stream:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/AgentY2PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /chat/completions:
    post:
      operationId: createY2OpenAIChatCompletion
      summary: OpenAI-compatible Agent Y2 chat stream
      description: |
        Routes OpenAI-style chat completions requests to Agent Y2 and streams text
        responses to compatible clients.

        v1 supports `messages`, `stream: true`, optional `model` set to `y2-agent`
        or `agent-y2`, optional `threadId`, and optional bounded `metadata`.
        Arbitrary models are unsupported. System and developer messages cannot
        override Agent Y2's fixed instructions.

        v1 requires an API key and does not support x402. Both Agent Y2 routes share
        a throttle of 5 requests/minute and 100/day per key, plus 10 requests/minute
        and 250/day per user or workspace. A `402` indicates subscription,
        chat-credit, or upstream-provider credit exhaustion, not an x402 challenge.
      tags: [Agent Y2]
      security:
        - bearerAuth: []
      x-required-scopes: [agent:y2]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OpenAICompatibleChatCompletionRequest"
            example:
              model: y2-agent
              stream: true
              messages:
                - role: user
                  content: Summarize the latest Y2 platform capabilities.
      responses:
        "200":
          description: OpenAI-style streaming chat completion chunks
          headers:
            X-Thread-Id:
              description: Y2 chat thread ID for continuing the conversation
              schema:
                type: string
            X-Y2-Agent-RateLimit-Limit-Minute:
              description: Agent Y2 requests allowed per minute for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Remaining-Minute:
              description: Agent Y2 requests remaining this minute for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Limit-Day:
              description: Agent Y2 requests allowed per day for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Remaining-Day:
              description: Agent Y2 requests remaining today for this API key
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Limit-Minute:
              description: Agent Y2 requests allowed per minute for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Remaining-Minute:
              description: Agent Y2 requests remaining this minute for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Limit-Day:
              description: Agent Y2 requests allowed per day for the user or workspace
              schema:
                type: integer
            X-Y2-Agent-RateLimit-Account-Remaining-Day:
              description: Agent Y2 requests remaining today for the user or workspace
              schema:
                type: integer
          content:
            text/event-stream:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/AgentY2PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports:
    get:
      operationId: listReports
      summary: List intelligence reports
      description: |
        Lists reports for the user's subscribed profiles by generation date,
        newest first.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T4_PREMIUM_LIST
        price: "$0.05"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
        - name: profileId
          in: query
          description: Filter by stable public profile ID (`prf_...`).
          schema:
            type: string
            pattern: "^prf_[a-f0-9]{24}$"
            example: "prf_0123456789abcdef01234567"
        - name: limit
          in: query
          description: Maximum number of reports to return for this page.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: List of reports
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports/{reportId}:
    get:
      operationId: getReport
      summary: Get a report
      description: |
        Returns a compact report by default. Use bounded `include` values or
        `view=agent`; request `text/markdown` for the canonical Markdown representation.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T4_PREMIUM_SINGLE
        price: "$0.25"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: include
          in: query
          description: Comma-separated `content,sources,signals,graph,audio` expansions.
          schema:
            type: string
        - name: view
          in: query
          description: Compact projection optimized for grounded agent context.
          schema:
            type: string
            enum: [agent]
        - name: format
          in: query
          description: Explicit representation override.
          schema:
            type: string
            enum: [markdown]
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
            example: "rpt_0123456789abcdef01234567"
      responses:
        "200":
          description: Report details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportDetailResponse"
            text/markdown:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports/{reportId}/signals:
    get:
      operationId: getReportSignals
      summary: Get report emergent signals
      description: |
        Returns structured emergent SIGINT signals inferred from a report. Reports
        created before signal extraction return an empty `signals` array.
        Requires the `reports:read` scope.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
      responses:
        "200":
          description: Structured report emergent signals
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportSignalsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports/{reportId}/graph:
    get:
      operationId: getReportGraph
      summary: Get report ontology graph
      description: |
        Returns the report's bounded ontology graph: report-local nodes, edges,
        incident anchors, citations, and counts. Reports created before graph
        extraction return `graph: null` with zero counts.
        Requires the `reports:read` scope.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
      responses:
        "200":
          description: Report ontology graph snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportGraphResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports/{reportId}/audio:
    get:
      operationId: getReportAudio
      summary: Get report audio
      description: |
        Returns audio file metadata or redirects to the CDN URL.
        Requires the `reports:audio` scope.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:audio]
      x-x402:
        enabled: true
        tier: T4_PREMIUM_AUDIO
        price: "$0.50"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
        - name: redirect
          in: query
          description: When true, redirects with `302` to the audio CDN URL
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Audio metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudioResponse"
        "302":
          description: Redirect to audio CDN URL
          headers:
            Location:
              description: CDN URL for audio file
              schema:
                type: string
                format: uri
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Report not found or no audio available
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /reports/{reportId}/text:
    get:
      operationId: getReportText
      summary: Get report as plain text
      description: |
        Returns the report content stripped of all HTML tags as plain text,
        along with word count and estimated reading time.
        Requires the `reports:read` scope.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T4_PREMIUM_SINGLE
        price: "$0.25"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
      responses:
        "200":
          description: Plain text report content
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportTextResponse"
            text/plain:
              schema:
                type: string
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Report not found
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /reports/{reportId}/audio-text:
    get:
      operationId: getReportAudioText
      summary: Get TTS-preprocessed report text
      description: |
        Returns the report content preprocessed for text-to-speech synthesis.
        Applies pronunciation normalization for acronyms, currency, and percentages;
        strips HTML and Markdown; and adds branding intro/outro text. Cartesia TTS
        generation uses this pipeline.
        Requires the `reports:read` scope.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [Reports]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [reports:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: reportId
          in: path
          required: true
          description: Stable Y2 public report ID (`rpt_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
      responses:
        "200":
          description: TTS-preprocessed report text
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportAudioTextResponse"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Report not found
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /profiles:
    get:
      operationId: listProfiles
      summary: List subscribed profiles
      description: |
        Lists the user's subscribed profiles with subscription status and
        delivery preferences.
      tags: [Profiles]
      security:
        - bearerAuth: []
      x-required-scopes: [profiles:read]
      responses:
        "200":
          description: List of subscribed profiles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfileListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"

    post:
      operationId: createProfile
      summary: Create an intelligence profile
      description: |
        Creates an `active` intelligence profile owned by the authenticated user
        with the supplied configuration.
      tags: [Profiles]
      security:
        - bearerAuth: []
      x-required-scopes: [profiles:write]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileCreateRequest"
      responses:
        "201":
          description: Profile created
          headers:
            Location:
              description: Canonical URL of the new profile.
              schema: { type: string }
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
            Idempotency-Key:
              description: Echo of the supplied retry key.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfileCreateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /profiles/{profileId}:
    put:
      operationId: updateProfile
      summary: Update a profile (full replacement)
      description: |
        Replaces every mutable field on a profile owned by the authenticated user.
      tags: [Profiles]
      security:
        - bearerAuth: []
      x-required-scopes: [profiles:write]
      parameters:
        - name: profileId
          in: path
          required: true
          description: Stable public profile ID. Legacy IDs remain accepted during the migration window.
          schema:
            type: string
            pattern: "^prf_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileReplaceRequest"
      responses:
        "200":
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfileUpdateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

    patch:
      operationId: patchProfile
      summary: Update a profile (partial)
      description: |
        Updates supplied mutable fields on a profile owned by the authenticated
        user. Omitted fields remain unchanged.
      tags: [Profiles]
      security:
        - bearerAuth: []
      x-required-scopes: [profiles:write]
      parameters:
        - name: profileId
          in: path
          required: true
          description: Stable public profile ID. Legacy IDs remain accepted during the migration window.
          schema:
            type: string
            pattern: "^prf_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileUpdateRequest"
      responses:
        "200":
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfileUpdateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteProfile
      summary: Delete a profile
      description: |
        Permanently deletes a profile owned by the authenticated user and all its
        subscriptions. This action cannot be undone.
      tags: [Profiles]
      security:
        - bearerAuth: []
      x-required-scopes: [profiles:write]
      parameters:
        - name: profileId
          in: path
          required: true
          description: Stable public profile ID. Legacy IDs remain accepted during the migration window.
          schema:
            type: string
            pattern: "^prf_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: Profile deleted. The response has no body.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # =========================================================================
  # Projects
  # =========================================================================
  /projects:
    get:
      operationId: listProjects
      summary: List owned Projects
      description: |
        Lists owner-private Projects in the API key workspace. Results are bounded,
        ordered by recent activity, and traversable with an opaque cursor.
      tags: [Projects]
      security:
        - bearerAuth: []
      x-required-scopes: [projects:read]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ProjectStatus"
            default: active
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
      responses:
        "200":
          description: Bounded Project list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createProject
      summary: Create a Project
      description: Creates an active owner-private Project in the API key workspace.
      tags: [Projects]
      security:
        - bearerAuth: []
      x-required-scopes: [projects:write]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectCreateRequest"
      responses:
        "201":
          description: Project created
          headers:
            Location:
              description: Canonical URL of the new Project.
              schema: { type: string }
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
            Idempotency-Key:
              description: Echo of the supplied retry key.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectMutationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /projects/{projectId}:
    get:
      operationId: getProject
      summary: Get a Project
      tags: [Projects]
      security:
        - bearerAuth: []
      x-required-scopes: [projects:read]
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
            pattern: "^prj_[a-f0-9]{24}$"
      responses:
        "200":
          description: Project details
          headers:
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      operationId: patchProject
      summary: Update, archive, or restore a Project
      description: |
        Applies a partial Project update. Set `status` to `archived` or `active`
        for the reversible lifecycle transition. Archiving pauses active bound Automations.
      tags: [Projects]
      security:
        - bearerAuth: []
      x-required-scopes: [projects:write]
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
            pattern: "^prj_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectPatchRequest"
      responses:
        "200":
          description: Project updated
          headers:
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectMutationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # =========================================================================
  # Automations
  # =========================================================================
  /automations:
    get:
      operationId: listAutomations
      summary: List Automation definitions
      description: |
        Lists owner-private Automation definitions in the API key workspace.
        Traverse the bounded result set with the returned opaque cursor.
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:read]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - name: projectId
          in: query
          schema:
            type: string
            pattern: "^prj_[a-f0-9]{24}$"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/AutomationStatus"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 30
      responses:
        "200":
          description: Bounded Automation list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createAutomation
      summary: Create an Automation
      description: Creates a draft, active, or paused Agent Y2 Automation.
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:write]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomationCreateRequest"
      responses:
        "201":
          description: Automation created
          headers:
            Location:
              description: Canonical URL of the new Automation.
              schema: { type: string }
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
            Idempotency-Key:
              description: Echo of the supplied retry key.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationMutationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /automations/{automationId}:
    get:
      operationId: getAutomation
      summary: Get an Automation definition
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:read]
      parameters:
        - name: automationId
          in: path
          required: true
          schema:
            type: string
            pattern: "^atm_[a-f0-9]{24}$"
      responses:
        "200":
          description: Automation details
          headers:
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      operationId: patchAutomation
      summary: Update or archive an Automation
      description: |
        Applies a partial definition update or a status-only lifecycle change.
        Archived Automations cannot be resumed or edited.
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:write]
      parameters:
        - name: automationId
          in: path
          required: true
          schema:
            type: string
            pattern: "^atm_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomationPatchRequest"
      responses:
        "200":
          description: Automation updated
          headers:
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationMutationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /automations/{automationId}/runs:
    get:
      operationId: listAutomationRuns
      summary: List Automation run history
      description: Traverse bounded Automation run history with the returned opaque cursor.
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:read]
      parameters:
        - name: automationId
          in: path
          required: true
          schema:
            type: string
            pattern: "^atm_[a-f0-9]{24}$"
        - $ref: "#/components/parameters/Cursor"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 30
      responses:
        "200":
          description: Bounded run history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationRunListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: runAutomation
      summary: Request a manual Automation run
      description: |
        Queues an idempotent manual run request for an active Automation. The
        `Idempotency-Key` header is required and identifies duplicate requests.
      tags: [Automations]
      security:
        - bearerAuth: []
      x-required-scopes: [automations:write]
      parameters:
        - name: automationId
          in: path
          required: true
          schema:
            type: string
            pattern: "^atm_[a-f0-9]{24}$"
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 200
            pattern: "^[A-Za-z0-9._:-]+$"
      responses:
        "202":
          description: Run request accepted
          headers:
            Idempotency-Key:
              description: Echo of the supplied retry key.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationRunRequestResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # =========================================================================
  # Webhooks
  # =========================================================================
  /webhooks:
    get:
      operationId: listWebhooks
      summary: List webhook configurations
      description: |
        Lists the authenticated user's webhook configurations. Masks secrets.
      tags: [Webhooks]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      responses:
        "200":
          description: Webhook configurations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createWebhook
      summary: Create a webhook configuration
      description: |
        Creates a webhook configuration. Requires a paid workspace plan with webhook
        access. The URL must use HTTPS and pass SSRF validation.
      tags: [Webhooks]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookCreateRequest"
      responses:
        "201":
          description: Webhook configuration created
          headers:
            Location:
              description: Canonical URL of the new webhook configuration.
              schema: { type: string }
            ETag:
              description: Strong validator for the returned representation.
              schema: { type: string }
            Idempotency-Key:
              description: Echo of the supplied retry key.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookCreateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /webhooks/{webhookId}:
    put:
      operationId: updateWebhook
      summary: Update a webhook configuration
      description: |
        Replaces every mutable webhook configuration field. `name` and `url` are required;
        omitted optional fields are reset to their defaults.
      tags: [Webhooks]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            pattern: "^whk_[a-f0-9]{24}$"
          description: Stable public webhook configuration ID.
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookReplaceRequest"
      responses:
        "200":
          description: Webhook configuration updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookUpdateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteWebhook
      summary: Delete a webhook configuration
      description: |
        Deletes a webhook configuration. Returns `409` if any subscription uses it.
      tags: [Webhooks]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            pattern: "^whk_[a-f0-9]{24}$"
          description: Stable public webhook configuration ID.
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: Webhook configuration deleted. The response has no body.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /webhooks/{webhookId}/test:
    post:
      operationId: testWebhook
      summary: Test a webhook endpoint
      description: |
        Sends a test payload to the webhook URL. Returns `422` if the endpoint
        responds with an error.
      tags: [Webhooks]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            pattern: "^whk_[a-f0-9]{24}$"
          description: Stable public webhook configuration ID.
      responses:
        "200":
          description: Webhook test successful
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTestResponse"
        "400":
          description: Webhook configuration is inactive
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: Webhook test failed (endpoint returned error)
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # =========================================================================
  # Subscriptions
  # =========================================================================
  /subscriptions/{subscriptionId}/delivery:
    patch:
      operationId: updateDelivery
      summary: Update subscription delivery method
      description: |
        Changes a subscription's delivery method. Setting `webhook` requires an
        active `webhookConfigId`. On plans with member seats, set `emailAudience`
        to `workspace` to email active workspace members.
      tags: [Subscriptions]
      security:
        - bearerAuth: []
      x-required-scopes: [webhooks:manage]
      parameters:
        - name: subscriptionId
          in: path
          required: true
          schema:
            type: string
            pattern: "^sub_[a-f0-9]{24}$"
          description: Stable public subscription ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeliveryUpdateRequest"
      responses:
        "200":
          description: Delivery method updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeliveryUpdateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /news:
    get:
      operationId: listNews
      summary: List news items
      description: |
        Lists cached Y2 News Terminal items with topic filters and pagination.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [News]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [news:read]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: topics
          in: query
          description: |
            Comma-separated list of topics to filter by.
            Use `GET /news/feeds` to discover the current topic catalog.
            Default: crypto, geopolitics, macro, equities, ai, energy
          schema:
            type: string
            example: "crypto,ai_agents,bitcoin"
        - name: limit
          in: query
          description: Maximum number of items to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: countryCode
          in: query
          description: |
            Filter by canonical ISO 3166-1 alpha-2 country code. When supplied without
            `topics`, the query searches every News Terminal topic.
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: ES
        - $ref: "#/components/parameters/Cursor"
        - name: format
          in: query
          description: Use `ndjson` for row-oriented streaming output.
          schema:
            type: string
            enum: [json, ndjson]
      responses:
        "200":
          description: List of news items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsListResponse"
            application/x-ndjson:
              schema:
                type: string
        "400":
          description: Invalid topics
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          description: News cache is initializing
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /news/recaps:
    get:
      operationId: listRecaps
      summary: List AI-generated recaps
      description: |
        Lists AI-generated recaps for selected topics and timeframe.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [News]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [news:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: topics
          in: query
          description: |
            Comma-separated list of topics.
            Use `GET /news/feeds` to discover the current topic catalog.
          schema:
            type: string
        - name: timeframe
          in: query
          description: Time period for recaps
          schema:
            $ref: "#/components/schemas/TimeframeEnum"
      responses:
        "200":
          description: Topic recaps
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecapResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /news/feeds:
    get:
      operationId: listFeeds
      summary: List available news feeds
      description: |
        Lists news feed topics and descriptions.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [News]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [news:read]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      responses:
        "200":
          description: List of available feeds
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedListResponse"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ===========================================================================
  # OSINT Endpoints
  # ===========================================================================
  /osint/events:
    get:
      operationId: listOsintEvents
      summary: List threat events
      description: |
        Lists Situation Room threat events. Supports category and severity filters.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
        - name: category
          in: query
          description: Filter by event category
          schema:
            $ref: "#/components/schemas/OsintEventCategoryEnum"
        - name: severity
          in: query
          description: Filter by severity level
          schema:
            $ref: "#/components/schemas/OsintSeverityEnum"
        - name: limit
          in: query
          description: Maximum number of events to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: List of OSINT threat events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OsintEventListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/map:
    get:
      operationId: listOsintMapEvents
      summary: List map events with coordinates
      description: |
        Lists geolocated OSINT events for map display. Excludes events without
        coordinates.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: region
          in: query
          description: Filter by geographic region
          schema:
            $ref: "#/components/schemas/OsintRegionEnum"
        - name: limit
          in: query
          description: Maximum number of events to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 200
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of map events with coordinates
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OsintMapEventListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/cii:
    get:
      operationId: listCII
      summary: Conflict Indicators Index
      description: |
        Lists Conflict Indicators Index (CII) values with 0–100 scores and
        recent-change deltas. Supports region and category filters.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: region
          in: query
          description: Filter by geographic region
          schema:
            $ref: "#/components/schemas/OsintRegionEnum"
        - name: category
          in: query
          description: Filter by event category
          schema:
            $ref: "#/components/schemas/OsintEventCategoryEnum"
        - name: limit
          in: query
          description: Maximum number of items to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
      responses:
        "200":
          description: List of CII items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CIIListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/countries/{countryCode}/brief:
    get:
      operationId: getCountryBrief
      summary: AI-generated intelligence brief
      description: |
        Returns a periodically generated, cached intelligence brief for a country.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: path
          required: true
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: "US"
      responses:
        "200":
          description: Intelligence brief for the country
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntelBriefResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Brief not available for this country
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/countries/{countryCode}/markets:
    get:
      operationId: getCountryStockIndex
      summary: Stock market index
      description: |
        Returns a country's primary stock index, weekly change, and currency.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: path
          required: true
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: "US"
      responses:
        "200":
          description: Stock market index for the country
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StockIndexResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Stock-index data is not available for this country
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/countries/{countryCode}/predictions:
    get:
      operationId: getCountryPredictions
      summary: Prediction markets
      description: |
        Returns prediction-market probabilities and trading volumes for a country.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: path
          required: true
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: "US"
        - name: limit
          in: query
          description: Maximum number of predictions to return
          schema:
            type: integer
            minimum: 1
            maximum: 10
            default: 3
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
      responses:
        "200":
          description: Prediction markets for the country
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountryPredictionMarketListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/countries/{countryCode}/news:
    get:
      operationId: getCountryNews
      summary: Country-specific news
      description: |
        Returns recent country news from the OSINT event pipeline.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: path
          required: true
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: "US"
        - name: limit
          in: query
          description: Maximum number of news items to return
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 8
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: Country-specific news items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountryNewsListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/countries/{countryCode}/cii:
    get:
      operationId: getCountryCII
      summary: Country instability index
      description: |
        Returns a country's Conflict Indicators Index (CII) score, baseline,
        delta, and components.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: path
          required: true
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
            example: "UA"
      responses:
        "200":
          description: Country instability index
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountryCIIResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conflict-indicator data is not available for this country
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/military-posture:
    get:
      operationId: listMilitaryPosture
      summary: Military posture by theater
      description: |
        Lists theater posture assessments based on Wingbits ADS-B military aircraft
        activity. Each includes a `normal`, `elevated`, or `critical` posture and
        aircraft counts by type.

        > **Status:** Aircraft ingestion has been disabled since May 8, 2026, to
        > reserve the shared Wingbits quota for GPS interference detection. This
        > endpoint may return empty or stale results. `/osint/gps-jamming` is unaffected.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      x-status: disabled
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-y2-empty-policy: feed
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: limit
          in: query
          description: Maximum number of items to return
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: Military posture assessments by theater
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MilitaryPostureListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/aircraft:
    get:
      operationId: listAircraft
      summary: Tracked military aircraft
      description: |
        Lists Wingbits ADS-B military aircraft positions, classified by type such
        as tanker, AWACS, or fighter.

        > **Status:** Aircraft ingestion has been disabled since May 8, 2026, to
        > reserve the shared Wingbits quota for GPS interference detection. This
        > endpoint may return empty or stale results. `/osint/gps-jamming` is unaffected.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      x-status: disabled
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-y2-empty-policy: feed
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: theater
          in: query
          description: Filter by theater ID (e.g. "iran", "taiwan", "blacksea", "scs")
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of aircraft to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of tracked military aircraft
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AircraftListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/vessels:
    get:
      operationId: listVessels
      summary: Naval vessel positions
      description: |
        Lists USNI fleet-tracker positions for carrier strike groups and warships.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-y2-empty-policy: feed
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: region
          in: query
          description: Filter by region name
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of vessels to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of naval vessel positions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VesselListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/gps-jamming:
    get:
      operationId: listGPSJamming
      summary: GPS interference zones
      description: |
        Lists GPS interference zones inferred from ADS-B navigation-accuracy
        degradation and aggregated into H3 cells.

        Coverage spans 22 theaters on a tiered cadence within the shared Wingbits quota:

        | Tier      | Cadence   | Theaters                                                                                          |
        | --------- | --------- | ------------------------------------------------------------------------------------------------- |
        | Hot       | Hourly    | iran, blacksea, israelgaza, redsea, taiwan, scs                                                   |
        | Watch     | Every 3h  | emed, korea, caucasus, kaliningrad-tight, finland-russia, us-south, bashi-luzon, east-china-sea   |
        | Perimeter | Every 6h  | us-pacom-west, us-northeast, aleutian-bering, baltic-south, giuk-greenland                        |
        | Daily     | Every 24h | baltic-north, us-north, arctic-greenland-pass                                                     |

        Records expire 30 minutes after fetch. Align polling with the theater's tier.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-y2-empty-policy: feed
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: severity
          in: query
          description: Filter by interference severity
          schema:
            type: string
            enum: [low, moderate, severe, critical]
        - name: limit
          in: query
          description: Maximum number of zones to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of GPS interference zones
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GPSJammingListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/sources/status:
    get:
      operationId: listSourceStatus
      summary: Data source health
      description: |
        Lists OSINT source health, circuit-breaker state, and failure counts.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      responses:
        "200":
          description: Data source health statuses
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceStatusListResponse"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/cyber-threats:
    get:
      operationId: listCyberThreats
      summary: List cyber threat indicators
      description: |
        Lists URLhaus malware URLs, Feodo Tracker C2 servers, and RSS cyber-feed
        indicators with malware family, tags, and host metadata.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: severity
          in: query
          description: Filter by severity level
          schema:
            $ref: "#/components/schemas/OsintSeverityEnum"
        - name: limit
          in: query
          description: Maximum number of threats to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
      responses:
        "200":
          description: List of cyber threat indicators
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CyberThreatListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/prediction-markets:
    get:
      operationId: listPredictionMarkets
      summary: List prediction markets
      description: |
        Lists Polymarket and Kalshi markets by descending volume with probabilities,
        volume, and outcomes.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: countryCode
          in: query
          description: Filter by ISO alpha-2 country code (e.g., US, CN, RU)
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
        - name: limit
          in: query
          description: Maximum number of markets to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
      responses:
        "200":
          description: List of prediction markets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PredictionMarketListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/y2-events:
    get:
      operationId: listY2Events
      summary: Y2 report-extracted events
      description: |
        Lists Y2SOURCE events extracted from intelligence reports with classification,
        confidence, entities, and originating-report provenance.

        Returns only `sourceType: "y2_report"` events. Supports category, severity,
        and country-code filters.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: category
          in: query
          description: Filter by event category
          schema:
            $ref: "#/components/schemas/OsintEventCategoryEnum"
        - name: severity
          in: query
          description: Filter by severity level
          schema:
            $ref: "#/components/schemas/OsintSeverityEnum"
        - name: countryCode
          in: query
          description: Filter by ISO alpha-2 country code (e.g., US, CN, RU)
          schema:
            type: string
            pattern: "^[A-Z]{2}$"
        - name: limit
          in: query
          description: Maximum number of events to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of Y2 report-extracted events with provenance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Y2EventListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/regional:
    get:
      operationId: listOsintRegionalEvents
      summary: Search regional OSINT events
      description: |
        Searches OSINT events across RSS, USGS, GDACS, ACLED, direct News Terminal
        items, other threat feeds, and Y2 report extractions. Filters by region,
        country code, bounding box, radius, source, category, severity, time window,
        or title text. Text search falls back to description substrings.

        All filters compose with AND, including coordinate and region filters.
        Events without coordinates remain unless `requireCoordinates=true` or a
        bounding-box or radius filter is supplied.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: q
          in: query
          description: Full-text search on title (also substring-matches description)
          schema:
            type: string
            maxLength: 200
        - name: region
          in: query
          description: Filter by geographic region
          schema:
            $ref: "#/components/schemas/OsintRegionEnum"
        - name: countryCode
          in: query
          description: Filter by ISO 3166-1 alpha-2 country code
          schema:
            type: string
            pattern: "^[A-Za-z]{2}$"
        - name: sourceType
          in: query
          description: Filter by one data source; omit to include all
          schema:
            type: string
            enum:
              - usgs
              - rss
              - acled
              - gdelt
              - gdacs
              - eonet
              - firms
              - opensky
              - wingbits
              - usni
              - polymarket
              - kalshi
              - urlhaus
              - feodo
              - y2_report
              - manual
              - fred
              - yfinance
              - eia
              - nvd
              - cisa_kev
              - intel_expansion
              - intel_discovery
              - news_terminal
        - name: category
          in: query
          description: Filter by event category
          schema:
            $ref: "#/components/schemas/OsintEventCategoryEnum"
        - name: severity
          in: query
          description: Filter by severity level
          schema:
            $ref: "#/components/schemas/OsintSeverityEnum"
        - name: bbox
          in: query
          description: |
            WGS 84 bounding box as `west,south,east,north` (longitude/latitude order).
            Exactly four values are required and antimeridian-crossing boxes are not accepted.
          schema:
            type: string
            pattern: "^-?[0-9]+(?:\\.[0-9]+)?,-?[0-9]+(?:\\.[0-9]+)?,-?[0-9]+(?:\\.[0-9]+)?,-?[0-9]+(?:\\.[0-9]+)?$"
        - name: nearLat
          in: query
          description: Center latitude for radius filter (requires nearLon + radiusKm)
          schema:
            type: number
            minimum: -90
            maximum: 90
        - name: nearLon
          in: query
          description: Center longitude for radius filter (requires nearLat + radiusKm)
          schema:
            type: number
            minimum: -180
            maximum: 180
        - name: radiusKm
          in: query
          description: Search radius in kilometers (requires nearLat + nearLon)
          schema:
            type: number
            exclusiveMinimum: 0
            maximum: 20000
        - name: requireCoordinates
          in: query
          description: When true, returns only rows with latitude and longitude
          schema:
            type: boolean
        - name: since
          in: query
          description: Inclusive lower bound on eventTime (epoch milliseconds)
          schema:
            type: integer
            minimum: 0
          deprecated: true
        - name: until
          in: query
          description: Inclusive upper bound on eventTime (epoch milliseconds)
          schema:
            type: integer
            minimum: 0
          deprecated: true
        - name: datetime
          in: query
          description: |
            OGC-style RFC 3339 instant or inclusive `start/end` interval. Use `..`
            for an open boundary. Cannot be combined with legacy `since` or `until`.
          schema:
            type: string
            examples:
              - "2026-07-21T00:00:00Z"
              - "2026-07-01T00:00:00Z/2026-07-21T23:59:59Z"
              - "../2026-07-21T23:59:59Z"
        - name: limit
          in: query
          description: Maximum number of events to return
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/SpatialFormat"
      responses:
        "200":
          description: List of regional events across all sources
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OsintEventListResponse"
            application/geo+json:
              schema:
                $ref: "#/components/schemas/GeoJSONFeatureCollection"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /osint/finint:
    get:
      operationId: listFinintIndicators
      summary: Financial intelligence indicators
      description: |
        Lists financial intelligence (FININT) indicators from FRED macroeconomic
        series, Yahoo Finance markets, and EIA energy data. Each includes current
        and previous values, percentage change, sparkline data, and active alerts.

        ### Aggregate and dimension rows

        Most indicators are US aggregates: one row per `indicatorId` (for example,
        `EIA:WTI`, `FRED:DGS10`). EIA also publishes per-dimension rows tagged
        with `dimension`, `dimensionType`, `lat`, and `lon`:

        - `dimensionType=facility`: reactor outages, such as `EIA:NUKE_OUTAGE:vogtle`
        - `dimensionType=rto`: hourly US balancing-authority demand, such as `EIA:RTO_LOAD:PJM`
        - `dimensionType=port`: monthly port-level crude imports, such as `EIA:CRUDE_IMPORT:houston`

        Filter by `dimensionType` for dimension rows only. Aggregate rows set
        `dimension`, `dimensionType`, `lat`, and `lon` to null.

        Nuclear-outage rows with `outagePct > 0` publish `economic` map events to
        the OSINT cache. RTO and crude-import rows do not publish map events.

        Supports x402 pay-per-request. Requests with a valid Bearer token use
        API-key authentication. Without a Bearer API key, start the x402 flow from
        the `402 Payment Required` response and `PAYMENT-REQUIRED` header; retry
        with `PAYMENT-SIGNATURE`.
      tags: [OSINT]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: category
          in: query
          description: Filter by indicator category
          schema:
            type: string
            enum: [rates, credit, inflation, labor, gdp, business, consumer, housing, money_supply, volatility, commodities, fx, equities, energy, crypto]
        - name: source
          in: query
          description: Filter by data source
          schema:
            type: string
            enum: [fred, yfinance, eia]
        - name: dimensionType
          in: query
          description: |
            Filter to per-dimension rows only (nuclear facilities, RTO balancing
            authorities, ports of entry, or US states). When provided, aggregate
            rows are excluded from the response.
          schema:
            type: string
            enum: [facility, rto, port, state]
        - name: limit
          in: query
          description: Maximum number of indicators to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/RowFormat"
      responses:
        "200":
          description: List of financial intelligence indicators
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinintIndicatorListResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # ==========================================================================
  # INTEL API (v2) — Ontology-backed intel sections
  #
  # v2 endpoints carry the literal `/api/v2/...` path prefix and are served
  # from `https://api.y2.dev` (no `/api/v1` segment). Each operation declares
  # an operation-level `servers:` override so OpenAPI tooling, SDK codegen,
  # and the in-app Workbench compose `https://api.y2.dev` + `/api/v2/...`
  # = the correct production URL.
  # ==========================================================================

  /api/v2/changes:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listChangesV2
      summary: Read the public intelligence change feed
      description: |
        Returns immutable public-resource lifecycle events in global order. An
        authenticated API key receives globally visible intelligence changes
        plus changes owned by its user or workspace tenant. Anonymous x402
        requests receive globally visible intelligence changes only; tenant
        records are never exposed across scopes.

        The `watermark` is an opaque exclusive checkpoint: persist the returned
        value and send it on the next request. Replaying a checkpoint never
        skips later events and may safely be retried after a consumer failure.

        Deleted resources are represented as tombstones. Events are append-only
        in schema version `1.0`; no retention expiry is currently applied.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [osint:read]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/RowFormat"
        - name: watermark
          in: query
          description: Opaque exclusive checkpoint returned by an earlier response.
          schema:
            type: string
            pattern: "^y2w1_[A-Za-z0-9_-]+$"
        - name: limit
          in: query
          description: Maximum number of change events to return.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        "200":
          description: Ordered public resource changes after the supplied watermark.
          headers:
            X-Y2-Watermark:
              description: Exclusive checkpoint to persist after processing this page.
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChangeFeedResponse"
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/intel/knowledge/retrieve:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    post:
      operationId: retrieveGlobalKnowledgeV2
      summary: Retrieve Y2 Global Knowledge
      description: |
        Runs bounded hybrid retrieval over shared Y2 knowledge and, when requested, the API
        key's bound workspace corpus. Access identifiers and provider filters are derived by
        Y2; the request cannot supply a user or workspace ID. Results are discovery evidence,
        include source IDs and citations, and omit provider and access-control attributes.

        This operation requires a workspace-bound API key with `intel:knowledge`. It does not
        support x402. Agent Y2 keys continue to use `agent:y2`; the agent may invoke this
        retrieval tool internally when the workspace is entitled to Chat.
      tags: [Global Knowledge]
      security:
        - bearerAuth: []
      x-required-scopes: [intel:knowledge]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GlobalKnowledgeRetrievalRequest"
            example:
              query: What changed in semiconductor supply-chain risk?
              knowledgeMode: combined
              domains: [supply_chain, geopolitical]
              generatedAfter: 1782864000000
              limit: 8
      responses:
        "200":
          description: Authorized, current-manifest Global Knowledge results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GlobalKnowledgeRetrievalResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /api/v2/incidents:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listIncidentsV2
      summary: List intel incidents
      description: |
        Lists deduplicated, scored ontology incidents linked from the OSINT cache.
        Filters by category, severity, status, or last-observed lower bound.

        Required scopes vary by `category` filter:
        - `category=cyber` → `intel:cyber` or `intel:explorer`
        - any other category → `intel:finint` or `intel:explorer`
        - no `category` filter → `intel:explorer`

        Supports x402 pay-per-request. Requests without `Authorization` return
        `402 Payment Required` with payment requirements; retry with a signed
        `PAYMENT-SIGNATURE` header.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer, intel:finint, intel:cyber]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsIncidents"
        - name: category
          in: query
          description: Filter incidents by category
          schema:
            type: string
            enum: [seismic, conflict, political, economic, weather, health, cyber, maritime, fire, aviation, other]
        - name: severity
          in: query
          description: Filter by severity
          schema:
            type: string
            enum: [low, medium, high, critical]
        - name: status
          in: query
          description: Filter by lifecycle status
          schema:
            type: string
            enum: [active, resolved, forecast]
        - name: sinceMs
          in: query
          description: |
            Lower bound on `lastObservedAt` as Unix milliseconds. Useful for
            polling — pass the largest `lastObservedAt` you've seen.
          schema:
            type: integer
            format: int64
        - name: limit
          in: query
          description: Maximum number of incidents to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of intel incidents
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IncidentListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/incidents/{incidentId}:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: getIncidentV2
      summary: Get an intel incident
      description: |
        Returns an incident with supporting observations, linked markets and
        entities, primary place, and related incidents.

        The server resolves the category before checking scope: `intel:cyber` for
        cyber, `intel:finint` for other categories, or `intel:explorer` for any.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer, intel:finint, intel:cyber]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/IncidentIncludes"
        - $ref: "#/components/parameters/FieldsIncidents"
        - $ref: "#/components/parameters/FieldsObservations"
        - $ref: "#/components/parameters/FieldsMarkets"
        - $ref: "#/components/parameters/FieldsEntities"
        - name: incidentId
          in: path
          required: true
          description: Stable Y2 public incident ID (`inc_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
      responses:
        "200":
          description: Incident drill — incident, observations, markets, entities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IncidentDetailResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/entities:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listEntitiesV2
      summary: List intel entities
      description: |
        Lists ontology entities, including people, organizations, countries, CVEs,
        threat actors, vessels, aircraft, facilities, vendors, software, and protocols.
        `q` searches `canonicalName`; `kind` filters by exact kind.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsEntities"
        - name: kind
          in: query
          description: Filter by entity kind
          schema:
            type: string
            enum:
              - person
              - organization
              - country
              - region
              - vessel
              - aircraft
              - facility
              - asset
              - indicator
              - cve
              - malware_family
              - threat_actor
              - vendor
              - software
              - ai_model
              - api_service
              - protocol
        - name: q
          in: query
          description: Full-text search across `canonicalName` (filtered by `kind` when present)
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of entities to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of intel entities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EntityListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/entities/{entityId}:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: getEntityV2
      summary: Get an intel entity
      description: |
        Returns an entity with inbound and outbound relations, related entities,
        participating incidents, subject markets, and recent observations.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer]
      x-x402:
        enabled: true
        tier: T2_STANDARD
        price: "$0.002"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/EntityIncludes"
        - $ref: "#/components/parameters/FieldsEntities"
        - $ref: "#/components/parameters/FieldsIncidents"
        - $ref: "#/components/parameters/FieldsMarkets"
        - $ref: "#/components/parameters/FieldsObservations"
        - name: entityId
          in: path
          required: true
          description: Stable Y2 public entity ID (`ent_...`). Legacy IDs are accepted during migration.
          schema:
            type: string
      responses:
        "200":
          description: Entity drill — entity, relations, incidents, markets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EntityDetailResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/entities/{entityId}/graph:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: getEntityGraphV2
      summary: Traverse an entity relationship graph
      description: |
        Traverses the ontology graph from `entityId` with breadth-first search.
        Returns nodes and edges to the requested depth, capped at 200 nodes.
        `relationKinds` limits traversed edge kinds.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: entityId
          in: path
          required: true
          description: Root entity ID for the traversal
          schema:
            type: string
        - name: depth
          in: query
          description: Maximum BFS depth (0–3)
          schema:
            type: integer
            minimum: 0
            maximum: 3
            default: 2
        - name: relationKinds
          in: query
          description: |
            Comma-separated list of relation kinds to traverse. Defaults to all
            kinds. Allowed values: `part_of`, `operates`, `located_in`,
            `ally_of`, `adversary_of`, `subsidiary_of`, `supplies`,
            `references`, `affects`, `targets`, `uses`.
          schema:
            type: string
            example: "affects,targets,uses"
      responses:
        "200":
          description: Entity graph (nodes + edges)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EntityGraphResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/markets:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listMarketsV2
      summary: List intel markets
      description: |
        Lists Y2-derived, Polymarket, Kalshi, and Manifold ontology markets keyed
        by `marketKey`. Filters by source and lifecycle status. Each market links
        to subject entities and may link to an underlying incident.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:finint]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsMarkets"
        - name: source
          in: query
          description: Filter by market source
          schema:
            type: string
            enum: [y2, polymarket, kalshi, manifold, derived]
        - name: status
          in: query
          description: Filter by lifecycle status
          schema:
            type: string
            enum: [open, resolved, cancelled]
        - name: limit
          in: query
          description: Maximum number of markets to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of markets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/finint:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listFinintIntelV2
      summary: List FININT indicators
      description: |
        Lists dimension-filtered FININT indicators from the same data as
        `GET /api/v1/osint/finint`, using the `intel:finint` scope and
        ontology-aware filters.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:finint]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsFinint"
        - name: dimensionType
          in: query
          description: Restrict to per-dimension rows (e.g. `facility`, `rto`, `port`, `state`)
          schema:
            type: string
            enum: [facility, rto, port, state]
        - name: dimension
          in: query
          description: Exact dimension key (e.g. `PJM`, `vogtle`, `houston`)
          schema:
            type: string
        - name: indicatorId
          in: query
          description: Exact indicator key (e.g. `EIA:WTI`, `FRED:DGS10`)
          schema:
            type: string
        - name: category
          in: query
          description: Filter by indicator category
          schema:
            type: string
            enum: [rates, credit, inflation, labor, gdp, business, consumer, housing, money_supply, volatility, commodities, fx, equities, energy, crypto]
        - name: source
          in: query
          description: Filter by data source
          schema:
            type: string
            enum: [fred, yfinance, eia]
        - name: limit
          in: query
          description: Maximum number of indicators to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        "200":
          description: List of FinInt indicators
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinintIntelListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/signals:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listSignalsV2
      summary: List tagged emergent signals
      description: |
        Lists cached Y2 emergent SIGINT signals with ontology-aligned subjects,
        normalized subject keys, tags, source-report metadata, timestamps, and
        available geographic, observation, or entity links.

        Required scopes vary by `domain` filter:
        - `domain=cyber` or `domain=technology` -> `intel:cyber` or `intel:explorer`
        - `domain=markets` or `domain=supply_chain` -> `intel:finint` or `intel:explorer`
        - other domain filters -> `intel:explorer`
        - no `domain` filter -> any of `intel:cyber`, `intel:finint`, or `intel:explorer`

        Public x402 callers receive global and community signals. Authenticated
        API-key callers also receive authorized workspace and private rows.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:explorer, intel:finint, intel:cyber]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsSignals"
        - name: domain
          in: query
          description: Filter by SIGINT domain
          schema:
            type: string
            enum: [cyber, markets, geopolitical, operational, supply_chain, policy, military, technology, other]
        - name: actionType
          in: query
          description: Filter by candidate action type
          schema:
            type: string
            enum: [invest, patch, upgrade, strategy, hedge, monitor, mitigate, escalate, defer, allocate]
        - name: priority
          in: query
          description: Filter by priority
          schema:
            type: string
            enum: [low, medium, high, critical]
        - name: subjectKey
          in: query
          description: Filter by normalized subject key returned in `subjectKeys`
          schema:
            type: string
            example: "organization:example-ai-vendor"
        - name: subjectKind
          in: query
          description: Filter by ontology subject kind
          schema:
            type: string
            enum:
              - person
              - organization
              - country
              - region
              - vessel
              - aircraft
              - facility
              - asset
              - indicator
              - cve
              - malware_family
              - threat_actor
              - vendor
              - software
              - ai_model
              - api_service
              - protocol
        - name: entityId
          in: query
          description: Filter by linked ontology entity public ID
          schema:
            type: string
            pattern: "^ent_[a-f0-9]{24}$"
        - name: profileId
          in: query
          description: Filter by source profile public ID
          schema:
            type: string
            pattern: "^prf_[a-f0-9]{24}$"
        - name: reportId
          in: query
          description: Filter by source report public ID
          schema:
            type: string
            pattern: "^rpt_[a-f0-9]{24}$"
        - name: countryCode
          in: query
          description: Filter by ISO-like country code when geo-linked
          schema:
            type: string
            example: US
        - name: region
          in: query
          description: Filter by region label when geo-linked
          schema:
            type: string
        - name: visibility
          in: query
          description: Filter by signal visibility
          schema:
            type: string
            enum: [private, workspace, community, global]
        - name: q
          in: query
          description: Text search across title, signal, decision, justification, tags, and subjects
          schema:
            type: string
        - name: sinceMs
          in: query
          description: Lower bound on signal extraction timestamp as Unix milliseconds
          schema:
            type: integer
            format: int64
        - name: untilMs
          in: query
          description: Upper bound on signal extraction timestamp as Unix milliseconds
          schema:
            type: integer
            format: int64
        - name: limit
          in: query
          description: Maximum number of signals to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of tagged emergent signals
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignalListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/cyber/graph:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: getCyberGraphV2
      summary: Cyber entity graph
      description: |
        Returns a graph rooted at a CVE, threat actor, or malware family. Requires
        `rootCveId`, `rootActorId`, or `rootMalwareFamilyId`. Breadth-first search
        depth is capped at 2.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:cyber]
      x-y2-required-query-one-of: [rootCveId, rootActorId, rootMalwareFamilyId]
      x-x402:
        enabled: true
        tier: T3_ENRICHED
        price: "$0.01"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - name: rootCveId
          in: query
          description: Root entity ID (kind=cve) for the traversal
          schema:
            type: string
        - name: rootActorId
          in: query
          description: Root entity ID (kind=threat_actor) for the traversal
          schema:
            type: string
        - name: rootMalwareFamilyId
          in: query
          description: Root entity ID (kind=malware_family) for the traversal
          schema:
            type: string
        - name: depth
          in: query
          description: Maximum BFS depth (1–2)
          schema:
            type: integer
            minimum: 1
            maximum: 2
            default: 2
      responses:
        "200":
          description: Cyber graph (nodes + edges)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CyberGraphResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/cyber/cves:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listCVEsV2
      summary: List CVE entities
      description: |
        Lists `kind=cve` ontology entities. `q` searches CVE identifiers. Equivalent to
        `GET /api/v2/entities?kind=cve&q=...`.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:cyber]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsEntities"
        - name: q
          in: query
          description: Full-text search on `canonicalName` (CVE-YYYY-NNNN form)
          schema:
            type: string
            example: "CVE-2024-3094"
        - name: limit
          in: query
          description: Maximum number of CVEs to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of CVE entities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CVEListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v2/cyber/actors:
    servers:
      - url: https://api.y2.dev
        description: Y2 Intel v2 root
    get:
      operationId: listThreatActorsV2
      summary: List threat actor entities
      description: |
        Lists `kind=threat_actor` ontology entities. Equivalent to
        `GET /api/v2/entities?kind=threat_actor&q=...`.
      tags: [Intel]
      security:
        - bearerAuth: []
        - {}
      x-required-scopes: [intel:cyber]
      x-x402:
        enabled: true
        tier: T1_HEALTH
        price: "$0.001"
        network:
          production: eip155:8453
          development: eip155:84532
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/FieldsEntities"
        - name: q
          in: query
          description: Full-text search on `canonicalName`
          schema:
            type: string
            example: "Lazarus"
        - name: limit
          in: query
          description: Maximum number of threat actors to return
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
      responses:
        "200":
          description: List of threat actor entities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ThreatActorListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /x402/receipts/{nonce}:
    get:
      operationId: getX402Receipt
      summary: x402 receipt lookup
      description: |
        Returns sanitized x402 payment status by nonce. Public: anyone with the
        nonce can read the receipt status.
      tags: [Payments]
      security: []
      parameters:
        - name: nonce
          in: path
          required: true
          description: Payment authorization nonce.
          schema:
            type: string
      responses:
        "200":
          description: Sanitized x402 payment receipt
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/X402ReceiptResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Y2 API Key
      description: |
        API keys in format `y2_{64_hex_chars}`.
        Create keys in the Y2 dashboard.

  parameters:
    Cursor:
      name: cursor
      in: query
      description: Opaque continuation token from the previous response. Bound to the original filters and ordering.
      schema:
        type: string
    IfMatch:
      name: If-Match
      in: header
      required: false
      description: Strong ETag from the latest representation. A mismatch returns 412.
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Client-generated retry key retained for 24 hours. Repeating the same key
        and canonical JSON body returns the original resource; changing the body
        returns `409 IDEMPOTENCY_CONFLICT`.
      schema:
        type: string
        minLength: 8
        maxLength: 200
        pattern: "^[A-Za-z0-9._:-]+$"
    RowFormat:
      name: format
      in: query
      description: "`json` uses the resource envelope; `ndjson` streams one canonical row per line."
      schema:
        type: string
        enum: [json, ndjson]
    SpatialFormat:
      name: format
      in: query
      description: Select the JSON resource envelope, row-oriented NDJSON, or an RFC 7946 FeatureCollection.
      schema:
        type: string
        enum: [json, ndjson, geojson]
    FieldsIncidents:
      name: fields[incidents]
      in: query
      description: Comma-separated allowlist of incident fields. Identity and links remain present.
      schema:
        type: string
    FieldsEntities:
      name: fields[entities]
      in: query
      description: Comma-separated allowlist of entity fields. Identity and links remain present.
      schema:
        type: string
    FieldsMarkets:
      name: fields[markets]
      in: query
      description: Comma-separated allowlist of market fields. Identity remains present.
      schema:
        type: string
    FieldsObservations:
      name: fields[observations]
      in: query
      description: Comma-separated allowlist of observation fields. Identity and source links remain present.
      schema:
        type: string
    FieldsFinint:
      name: fields[finint]
      in: query
      description: Comma-separated allowlist of financial observation fields. Identity remains present.
      schema:
        type: string
    FieldsSignals:
      name: fields[signals]
      in: query
      description: Comma-separated allowlist of signal fields. Identity remains present.
      schema:
        type: string
    IncidentIncludes:
      name: include
      in: query
      description: Bounded related resources to embed.
      schema:
        type: string
        example: observations,entities
    EntityIncludes:
      name: include
      in: query
      description: Bounded related resources to embed.
      schema:
        type: string
        example: relations,incidents

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            type: "https://api.y2.dev/problems/unauthorized"
            title: "Unauthorized"
            status: 401
            detail: "Missing Authorization header"
            instance: "urn:y2:request:req_019d0f205ea37f248c3f2f65ddce8b61"
            code: "UNAUTHORIZED"
            requestId: "req_019d0f205ea37f248c3f2f65ddce8b61"
            error:
              message: "Missing Authorization header"
              code: "UNAUTHORIZED"
    Forbidden:
      description: Insufficient permissions or access denied
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resource not found
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: Rate limit exceeded (per-key or per-account aggregate)
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
        X-RateLimit-Limit-Minute:
          description: Per-key requests allowed per minute
          schema:
            type: integer
        X-RateLimit-Remaining-Minute:
          description: Per-key requests remaining this minute
          schema:
            type: integer
        X-RateLimit-Limit-Day:
          description: Per-key requests allowed per day
          schema:
            type: integer
        X-RateLimit-Remaining-Day:
          description: Per-key requests remaining today
          schema:
            type: integer
        X-RateLimit-Limit-User-Minute:
          description: Per-account aggregate requests allowed per minute (workspace when scoped, otherwise user; legacy header name retained)
          schema:
            type: integer
        X-RateLimit-Remaining-User-Minute:
          description: Per-account aggregate requests remaining this minute (workspace when scoped, otherwise user; legacy header name retained)
          schema:
            type: integer
        X-RateLimit-Limit-User-Day:
          description: Per-account aggregate requests allowed per day (workspace when scoped, otherwise user; legacy header name retained)
          schema:
            type: integer
        X-RateLimit-Remaining-User-Day:
          description: Per-account aggregate requests remaining today (workspace when scoped, otherwise user; legacy header name retained)
          schema:
            type: integer
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    PaymentRequired:
      description: x402 payment required
      headers:
        PAYMENT-REQUIRED:
          description: Base64-encoded JSON payment requirements.
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/X402PaymentRequiredResponse"
    AgentY2PaymentRequired:
      description: Agent Y2 subscription, chat-credit, or provider credit exhaustion
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    InternalError:
      description: Internal server error
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServiceUnavailable:
      description: Y2 Global Knowledge is disabled or its retrieval provider is unavailable.
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Resource conflict (e.g., webhook in use)
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    PreconditionFailed:
      description: The supplied If-Match validator does not match the current representation.
      headers:
        ETag:
          description: Strong validator for the current representation.
          schema: { type: string }
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    GlobalKnowledgeRetrievalRequest:
      type: object
      required: [query]
      additionalProperties: false
      properties:
        query:
          type: string
          minLength: 1
          maxLength: 1000
        knowledgeMode:
          type: string
          enum: [shared, workspace, combined]
          default: combined
          description: Narrows retrieval to shared, key-workspace, or both authorized corpora.
        documentKinds:
          type: array
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            enum: [report_chunk, signal_assessment, report_graph_component]
        entityIds:
          type: array
          maxItems: 20
          uniqueItems: true
          items: { type: string, maxLength: 128 }
        incidentIds:
          type: array
          maxItems: 20
          uniqueItems: true
          items: { type: string, maxLength: 128 }
        subjectKeys:
          type: array
          maxItems: 20
          uniqueItems: true
          items: { type: string, maxLength: 128 }
        domains:
          type: array
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            enum: [cyber, markets, geopolitical, operational, supply_chain, policy, military, technology, other]
        priorities:
          type: array
          maxItems: 4
          uniqueItems: true
          items:
            type: string
            enum: [low, medium, high, critical]
        generatedAfter:
          type: integer
          minimum: 0
          description: Inclusive lower bound as Unix milliseconds.
        generatedBefore:
          type: integer
          minimum: 0
          description: Inclusive upper bound as Unix milliseconds.
        limit:
          type: integer
          minimum: 1
          maximum: 20
          default: 10

    GlobalKnowledgeCanonicalReference:
      type: object
      required: [type, id, revision]
      additionalProperties: false
      properties:
        type:
          type: string
          enum: [entity, incident, place, observation, relation]
        id: { type: string }
        revision: { type: integer, minimum: 1 }

    GlobalKnowledgeResult:
      type: object
      required: [documentId, rank, documentKind, title, excerpt, source, ontology, canonicalRefs, episode, inferenceKind, generatedAt, citationUrls]
      additionalProperties: false
      properties:
        documentId: { type: string }
        rank: { type: integer, minimum: 1 }
        documentKind:
          type: string
          enum: [report_chunk, signal_assessment, report_graph_component]
        title: { type: string, maxLength: 300 }
        excerpt: { type: string, maxLength: 1200 }
        sectionPath: { type: string, maxLength: 300 }
        source:
          type: object
          required: [type, id]
          additionalProperties: false
          properties:
            type: { type: string, enum: [report, signal_assessment] }
            id: { type: string }
            reportId: { type: string }
            profileId: { type: string }
        ontology:
          type: object
          required: [entityIds, incidentIds, placeIds, subjectKeys]
          additionalProperties: false
          properties:
            entityIds: { type: array, items: { type: string } }
            incidentIds: { type: array, items: { type: string } }
            placeIds: { type: array, items: { type: string } }
            subjectKeys: { type: array, items: { type: string } }
        canonicalRefs:
          type: array
          maxItems: 32
          items:
            $ref: "#/components/schemas/GlobalKnowledgeCanonicalReference"
        episode:
          type: object
          required: [id, recordedAt]
          additionalProperties: false
          properties:
            id: { type: string }
            recordedAt: { type: integer, minimum: 0 }
            validFrom: { type: integer, minimum: 0 }
            validTo: { type: integer, minimum: 0 }
        inferenceKind: { type: string }
        generatedAt: { type: integer, minimum: 0 }
        validFrom: { type: integer, minimum: 0 }
        validTo: { type: integer, minimum: 0 }
        citationUrls:
          type: array
          maxItems: 32
          items: { type: string, format: uri }

    GlobalKnowledgeRetrievalResponse:
      type: object
      required: [data, meta]
      additionalProperties: false
      properties:
        data:
          type: array
          maxItems: 20
          items:
            $ref: "#/components/schemas/GlobalKnowledgeResult"
        meta:
          type: object
          required: [count, limit, knowledgeMode]
          additionalProperties: false
          properties:
            count: { type: integer, minimum: 0, maximum: 20 }
            limit: { type: integer, minimum: 1, maximum: 20 }
            knowledgeMode: { type: string, enum: [shared, workspace, combined] }

    AgentY2TextPart:
      type: object
      required: [type, text]
      properties:
        type:
          type: string
          const: text
        text:
          type: string

    AgentY2Message:
      type: object
      required: [role]
      properties:
        role:
          type: string
          enum: [user, assistant]
        content:
          type: [string, "null"]
        parts:
          type: array
          items:
            $ref: "#/components/schemas/AgentY2TextPart"

    AgentY2RequestMetadata:
      type: object
      additionalProperties: false
      properties:
        source:
          type: string
          maxLength: 128
        externalUserId:
          type: string
          maxLength: 256
        externalThreadId:
          type: string
          maxLength: 256

    AgentY2StreamRequest:
      type: object
      required: [messages]
      properties:
        messages:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/AgentY2Message"
        threadId:
          type: string
          description: Existing Y2 chat thread ID returned in `X-Thread-Id`
        metadata:
          $ref: "#/components/schemas/AgentY2RequestMetadata"

    OpenAICompatibleChatMessage:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, developer, user, assistant, tool]
          description: System, developer, and tool messages are accepted for SDK compatibility but are not used to override Agent Y2's fixed system instructions.
        content:
          oneOf:
            - type: string
            - type: array
              items:
                $ref: "#/components/schemas/AgentY2TextPart"
            - type: "null"

    OpenAICompatibleChatCompletionRequest:
      type: object
      required: [messages, stream]
      properties:
        model:
          type: string
          enum: [y2-agent, agent-y2]
          description: Optional Y2 Agent model alias. Arbitrary model selection is not supported.
        messages:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/OpenAICompatibleChatMessage"
        stream:
          type: boolean
          const: true
        threadId:
          type: string
          description: Optional Y2 thread ID. The `X-Thread-Id` request header is also accepted.
        metadata:
          $ref: "#/components/schemas/AgentY2RequestMetadata"

    # =========================================================================
    # Error Schemas
    # =========================================================================
    ErrorResponse:
      type: object
      description: Canonical RFC 9457 Problem Details response.
      required: [type, title, status, detail, instance, code, requestId]
      properties:
        type:
          type: string
          format: uri
          description: Stable problem type URI.
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri
          description: URI identifying this request occurrence.
        code:
          type: string
          description: Stable machine-readable Y2 error code.
          enum:
            - UNAUTHORIZED
            - INVALID_API_KEY
            - API_KEY_EXPIRED
            - API_KEY_REVOKED
            - FORBIDDEN
            - INSUFFICIENT_SCOPE
            - IP_NOT_ALLOWED
            - SUBSCRIPTION_REQUIRED
            - FEATURE_REQUIRES_PRO
            - BAD_REQUEST
            - INVALID_ID_FORMAT
            - INVALID_TOPICS
            - INVALID_TIMEFRAME
            - VALIDATION_ERROR
            - QUERY_TOO_BROAD
            - NOT_FOUND
            - NO_AUDIO
            - PROFILE_NOT_FOUND
            - REPORT_NOT_FOUND
            - WEBHOOK_NOT_FOUND
            - SUBSCRIPTION_NOT_FOUND
            - DATA_NOT_AVAILABLE
            - PRO_REQUIRED
            - INVALID_DELIVERY_METHOD
            - WEBHOOK_INACTIVE
            - WEBHOOK_CONFIG_REQUIRED
            - WEBHOOK_TEST_FAILED
            - WEBHOOK_IN_USE
            - IDEMPOTENCY_CONFLICT
            - CONFLICT
            - AUTOMATION_NOT_ACTIVE
            - PRECONDITION_FAILED
            - RATE_LIMIT_EXCEEDED
            - MINUTE_LIMIT_EXCEEDED
            - DAILY_LIMIT_EXCEEDED
            - INTERNAL_ERROR
            - SERVICE_UNAVAILABLE
            - CACHE_NOT_READY
            - INSUFFICIENT_CREDITS
            - CREDITS_INSUFFICIENT
            - X402_DISABLED
            - X402_INVALID_SIGNATURE
            - X402_NONCE_REPLAY
            - X402_VERIFY_FAILED
            - X402_SETTLE_FAILED
            - X402_FACILITATOR_UNAVAILABLE
            - X402_WALLET_LIMIT
            - X402_ENDPOINT_CEILING
            - X402_HANDLER_FAILED_AFTER_SETTLE
            - X402_MISSING_CONFIG
        requestId:
          type: string
          pattern: "^req_[a-f0-9]{32}$"
        errors:
          type: array
          description: Optional field-level validation failures using JSON Pointer paths.
          items:
            type: object
            required: [path, code, message]
            properties:
              path:
                type: string
              code:
                type: string
              message:
                type: string

    # =========================================================================
    # x402 Payment Schemas
    # =========================================================================
    X402PaymentOption:
      type: object
      description: |
        One row in the `accepts` array of an x402 v2 PaymentRequired body.
        Mirrors `@x402/core` `PaymentRequirements`.
      required: [scheme, network, asset, amount, payTo, maxTimeoutSeconds]
      properties:
        scheme:
          type: string
          enum: [exact, upto]
        network:
          type: string
          description: CAIP-2 network identifier.
          example: eip155:8453
        asset:
          type: string
          description: ERC-20 token contract (USDC) for the chosen network.
          example: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
        amount:
          type: string
          description: |
            Price in atomic token units (USDC has 6 decimals).
            Example: `"2000"` = $0.002.
          example: "2000"
        payTo:
          type: string
          description: Receiver wallet address.
        maxTimeoutSeconds:
          type: integer
          description: Max settlement timeout the seller will wait for.
          example: 60
        extra:
          type: object
          additionalProperties: true
          description: |
            Scheme-specific extras. For `exact` EVM payments against a
            default EIP-3009 stablecoin (e.g. USDC), Y2 emits the EIP-712
            domain values the buyer must sign `TransferWithAuthorization`
            against. Values come from the token contract's `eip712Domain()`
            view and vary by network/contract — Base mainnet USDC is
            `name: "USD Coin"`, Base Sepolia USDC is `name: "USDC"`, both
            `version: "2"`.
          example:
            name: USD Coin
            version: "2"

    X402Resource:
      type: object
      description: Canonical resource the buyer is paying to access.
      required: [url]
      properties:
        url:
          type: string
          description: Full request URL.
          example: https://api.y2.dev/api/v1/osint/events
        description:
          type: string
        mimeType:
          type: string
          example: application/json

    X402PaymentRequiredResponse:
      type: object
      description: |
        Body returned with HTTP 402 responses. Same shape as the
        base64-encoded `PAYMENT-REQUIRED` header, plus an optional `error`
        message. Conforms to x402 v2 (`@x402/core` `PaymentRequired`).
      required: [x402Version, accepts, resource]
      properties:
        x402Version:
          type: integer
          description: x402 protocol version. Y2 advertises v2.
          example: 2
        error:
          type: string
          description: Optional human-readable reason (e.g. "Payment required").
          example: Payment required
        resource:
          $ref: "#/components/schemas/X402Resource"
        accepts:
          type: array
          items:
            $ref: "#/components/schemas/X402PaymentOption"
        extensions:
          type: object
          additionalProperties: true
          description: |
            Facilitator-level extensions. Y2 emits `extensions.bazaar` with
            CDP Bazaar discovery metadata (`category`, `tags`,
            `discoverable`).

    X402PaymentSignaturePayload:
      type: object
      description: |
        Decoded shape of the base64 `PAYMENT-SIGNATURE` request header. Mirrors
        `@x402/core` `PaymentPayload`. The `payload` field is opaque to the
        protocol — for the `exact` EVM scheme it is an EIP-3009
        `TransferWithAuthorization` signature.
      required: [x402Version, accepted, payload]
      properties:
        x402Version:
          type: integer
          example: 2
        resource:
          $ref: "#/components/schemas/X402Resource"
        accepted:
          $ref: "#/components/schemas/X402PaymentOption"
        payload:
          type: object
          description: |
            Scheme-specific payment material. For `exact` EVM:
            `{ signature, authorization: { from, to, value, validAfter, validBefore, nonce } }`.
          additionalProperties: true
        extensions:
          type: object
          additionalProperties: true

    X402ReceiptResponse:
      type: object
      required: [nonce, payerAddress, endpoint, amountUsd, network, status]
      properties:
        nonce:
          type: string
        payerAddress:
          type: string
        endpoint:
          type: string
          example: /api/v1/osint/sources/status
        amountUsd:
          type: number
          format: double
          example: 0.002
        network:
          type: string
          example: eip155:8453
        status:
          type: string
          enum: [verify_failed, rate_limited, replay_blocked, settle_failed, settled, handler_failed]
        txHash:
          type: string
        settledAt:
          type: number
          description: Epoch milliseconds.
        error:
          type: [string, "null"]

    # =========================================================================
    # Report Schemas
    # =========================================================================
    ReportListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/PublicReport"
            unevaluatedProperties: false
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    ReportDetailResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/PublicReportDetail"

    PublicReportIntelligenceSummary:
      type: object
      required: [signalCount, graph]
      additionalProperties: false
      properties:
        signalCount:
          type: integer
          minimum: 0
        graph:
          type: [object, "null"]
          required: [nodeCount, edgeCount, incidentCount]
          additionalProperties: false
          properties:
            nodeCount:
              type: integer
              minimum: 0
            edgeCount:
              type: integer
              minimum: 0
            incidentCount:
              type: integer
              minimum: 0

    PublicReportAudioSummary:
      type: object
      required: [status, durationSeconds, mediaType]
      additionalProperties: false
      properties:
        status:
          type: string
          enum: [available, unavailable]
        durationSeconds:
          type: [number, "null"]
        mediaType:
          type: [string, "null"]

    PublicReport:
      type: object
      required: [id, type, profileId, topic, summary, status, generatedAt, publishedAt, language, intelligence, audio, links]
      properties:
        id:
          type: string
          pattern: "^rpt_[a-f0-9]{24}$"
        type:
          type: string
          const: report
        profileId:
          type: string
          pattern: "^prf_[a-f0-9]{24}$"
        topic:
          type: [string, "null"]
        summary:
          type: [string, "null"]
        status:
          type: string
          const: published
        generatedAt:
          type: string
          format: date-time
        publishedAt:
          type: string
          format: date-time
        language:
          type: string
        intelligence:
          $ref: "#/components/schemas/PublicReportIntelligenceSummary"
        audio:
          $ref: "#/components/schemas/PublicReportAudioSummary"
        links:
          $ref: "#/components/schemas/PublicResourceLinks"

    PublicSource:
      type: object
      required: [id, url, title, publisher, publishedAt, retrievedAt, sourceType, language]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^src_[a-f0-9]{24}$"
        url:
          type: string
          format: uri
        title:
          type: [string, "null"]
        publisher:
          type: [string, "null"]
        publishedAt:
          type: [string, "null"]
          format: date-time
        retrievedAt:
          type: string
          format: date-time
        sourceType:
          type: string
        language:
          type: [string, "null"]

    PublicSourceLink:
      type: object
      required: [id, url]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^src_[a-f0-9]{24}$"
        url:
          type: string
          format: uri

    PublicReportSignal:
      type: object
      required: [title, signal, domain, actionType, decision, justification, priority, confidence, inferenceType, timeHorizon, subjects, tags, sources]
      additionalProperties: false
      properties:
        title:
          type: string
        signal:
          type: string
        domain:
          type: string
        actionType:
          type: string
        decision:
          type: string
        justification:
          type: string
        priority:
          type: string
        confidence:
          type: number
          minimum: 0
          maximum: 1
        inferenceType:
          type: string
        timeHorizon:
          type: string
        subjects:
          type: array
          items:
            type: object
            required: [kind, label, key, entityId]
            additionalProperties: false
            properties:
              kind:
                type: string
              label:
                type: string
              key:
                type: string
              entityId:
                type: [string, "null"]
        tags:
          type: array
          items:
            type: string
        sources:
          type: array
          items:
            type: object
            required: [id, url]
            additionalProperties: false
            properties:
              id:
                type: string
                pattern: "^src_[a-f0-9]{24}$"
              url:
                type: string
                format: uri

    PublicReportGraph:
      type: object
      required: [generatedAt, summary, nodes, edges, incidents, sources]
      additionalProperties: false
      properties:
        generatedAt:
          type: string
          format: date-time
        summary:
          type: string
        nodes:
          type: array
          items:
            type: object
            required: [id, type, kind, label, summary, sources]
            additionalProperties: false
            properties:
              id:
                type: string
              type:
                type: string
                const: entity
              kind:
                type: string
              label:
                type: string
              summary:
                type: [string, "null"]
              sources:
                type: array
                items:
                  $ref: "#/components/schemas/PublicSourceLink"
        edges:
          type: array
          items:
            type: object
            required: [id, type, from, to, confidence, source]
            additionalProperties: false
            properties:
              id:
                type: string
              type:
                type: string
              from:
                type: string
              to:
                type: string
              confidence:
                type: number
              source:
                oneOf:
                  - $ref: "#/components/schemas/PublicSourceLink"
                  - type: "null"
        incidents:
          type: array
          items:
            type: object
            required: [id, type, title, category, severity, occurredAt, entityIds, sources]
            additionalProperties: false
            properties:
              id:
                type: string
              type:
                type: string
                const: incident
              title:
                type: string
              category:
                type: string
              severity:
                type: string
              occurredAt:
                type: string
                format: date-time
              entityIds:
                type: array
                items:
                  type: string
              sources:
                type: array
                items:
                  $ref: "#/components/schemas/PublicSourceLink"
        sources:
          type: array
          items:
            $ref: "#/components/schemas/PublicSourceLink"

    PublicReportDetail:
      allOf:
        - $ref: "#/components/schemas/PublicReport"
        - type: object
          properties:
            profile:
              type: object
              required: [id, name, topic]
              properties:
                id:
                  type: string
                name:
                  type: [string, "null"]
                topic:
                  type: [string, "null"]
            content:
              type: object
              required: [markdown, mediaType]
              properties:
                markdown:
                  type: string
                mediaType:
                  type: string
                  const: text/markdown
            sources:
              type: array
              items:
                $ref: "#/components/schemas/PublicSource"
            signals:
              type: array
              items:
                $ref: "#/components/schemas/PublicReportSignal"
            graph:
              $ref: "#/components/schemas/PublicReportGraph"
            audioRepresentation:
              $ref: "#/components/schemas/AudioMetadata"
      unevaluatedProperties: false

    ReportSignalsResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [reportId, profileId, reportGeneratedAt, reportGeneratedAtISO, signals]
          properties:
            reportId:
              type: string
              pattern: "^rpt_[a-f0-9]{24}$"
            profileId:
              type: string
              pattern: "^prf_[a-f0-9]{24}$"
            reportGeneratedAt:
              type: integer
              format: int64
              deprecated: true
            reportGeneratedAtISO:
              type: string
              format: date-time
            topic:
              type: [string, "null"]
            summary:
              type: [string, "null"]
            generatedAt:
              type: [integer, "null"]
              format: int64
              deprecated: true
            generatedAtISO:
              type: [string, "null"]
              format: date-time
            signals:
              type: array
              items:
                $ref: "#/components/schemas/PublicReportSignal"
        meta:
          type: object
          required: [count]
          properties:
            count:
              type: integer

    ReportGraphResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [reportId, profileId, reportGeneratedAt, reportGeneratedAtISO, graph]
          properties:
            reportId:
              type: string
              pattern: "^rpt_[a-f0-9]{24}$"
            profileId:
              type: string
              pattern: "^prf_[a-f0-9]{24}$"
            reportGeneratedAt:
              type: integer
              format: int64
              deprecated: true
            reportGeneratedAtISO:
              type: string
              format: date-time
            topic:
              type: [string, "null"]
            graph:
              oneOf:
                - $ref: "#/components/schemas/PublicReportGraph"
                - type: "null"
        meta:
          type: object
          required: [nodeCount, edgeCount, incidentCount]
          properties:
            nodeCount:
              type: integer
            edgeCount:
              type: integer
            incidentCount:
              type: integer

    AudioMetadata:
      type: ["object", "null"]
      properties:
        url:
          type: string
          format: uri
          description: CDN URL for audio file
        duration:
          type: number
          deprecated: true
          description: Duration in seconds
        durationSeconds:
          type: [number, "null"]
          description: Canonical duration in seconds.
        durationFormatted:
          type: string
          description: Duration as HH:MM:SS
          example: "00:05:32"
        format:
          type: string
          enum: [mp3]
        mimeType:
          type: string
          enum: [audio/mpeg]
        fileSizeBytes:
          type: [integer, "null"]
          description: File size in bytes.

    AudioResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/AudioMetadata"

    ReportTextResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [text, wordCount, readingTimeMinutes]
          properties:
            text:
              type: string
              description: Report content as plain text (HTML stripped)
            wordCount:
              type: integer
              description: Total word count
              example: 1250
            readingTimeMinutes:
              type: integer
              description: Estimated reading time in minutes (~200 wpm)
              example: 7

    ReportAudioTextResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [text, charCount, estimatedAudioDuration]
          properties:
            text:
              type: string
              description: TTS-preprocessed text with pronunciation normalization and branding
            charCount:
              type: integer
              description: Character count of the preprocessed text
              example: 8500
            estimatedAudioDuration:
              type: integer
              description: Estimated audio duration in seconds (~750 chars/min)
              example: 420
            profileName:
              type: string
              description: Profile name used in branding intro/outro

    RecursionMetadata:
      type: object
      description: Metadata about recursive research execution
      properties:
        depth:
          type: integer
          description: Recursion depth achieved (0 = standard report)
        strategy:
          type: string
          enum: [breadth-first, depth-first, hybrid]
        layersProcessed:
          type: integer
        subtopicsGenerated:
          type: array
          items:
            type: string
        totalTimeMs:
          type: integer
        totalSourcesCollected:
          type: integer
        uniqueSourcesAggregated:
          type: integer
        fallbackReason:
          type: string
          description: Reason if fallback to standard generation occurred

    FreshnessMetadata:
      type: object
      description: Source freshness validation results
      properties:
        averageAgeMs:
          type: integer
          description: Average source age in milliseconds
        freshnessScore:
          type: number
          minimum: 0
          maximum: 1
          description: Overall freshness score (higher = fresher)
        staleSourcesCount:
          type: integer
        accessibleLinks:
          type: integer
        totalLinks:
          type: integer
        validatedAt:
          type: integer
          format: int64

    # =========================================================================
    # Profile Schemas
    # =========================================================================
    ProfileListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ProfileSubscription"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    ProfileSubscription:
      type: object
      required: [subscription, profile]
      properties:
        subscription:
          $ref: "#/components/schemas/PublicSubscription"
        profile:
          oneOf:
            - $ref: "#/components/schemas/PublicProfile"
            - type: "null"

    PublicSubscription:
      type: object
      required: [id, type, profileId, active, subscribedAt, delivery, links]
      properties:
        id:
          type: string
          pattern: "^sub_[a-f0-9]{24}$"
        type:
          type: string
          const: subscription
        profileId:
          type: string
          pattern: "^prf_[a-f0-9]{24}$"
        active:
          type: boolean
        subscribedAt:
          type: string
          format: date-time
        delivery:
          type: object
          required: [method, emailAudience, webhookId]
          properties:
            method:
              $ref: "#/components/schemas/DeliveryMethodEnum"
            emailAudience:
              $ref: "#/components/schemas/EmailAudienceEnum"
            webhookId:
              type: [string, "null"]
              pattern: "^whk_[a-f0-9]{24}$"
        links:
          type: object
          required: [delivery]
          properties:
            delivery: { type: string }

    PublicProfile:
      type: object
      required: [id, type, name, topic, status, frequency, schedule, visibility, tags, customInstructions, reportStructure, configuration, lastDeliveredAt, createdAt, links]
      properties:
        id:
          type: string
          pattern: "^prf_[a-f0-9]{24}$"
        type:
          type: string
          const: profile
        name: { type: string }
        topic: { type: string }
        status:
          $ref: "#/components/schemas/ProfileStatusEnum"
        frequency:
          oneOf:
            - $ref: "#/components/schemas/FrequencyEnum"
            - type: "null"
        schedule:
          type: object
          required: [cron, timezone]
          properties:
            cron: { type: string }
            timezone: { type: string, const: UTC }
        visibility:
          type: object
          required: [community, global]
          properties:
            community: { type: boolean }
            global: { type: boolean }
        tags:
          type: array
          items: { type: string }
        customInstructions: { type: [string, "null"] }
        reportStructure: { type: [string, "null"] }
        configuration:
          $ref: "#/components/schemas/PublicProfileConfiguration"
        lastDeliveredAt: { type: [string, "null"], format: date-time }
        createdAt: { type: string, format: date-time }
        links:
          type: object
          required: [self, reports]
          properties:
            self: { type: string }
            reports: { type: string }

    PublicProfileConfiguration:
      type: object
      required: [search, model, budget, recursion, freshness, audio, tools]
      properties:
        search: { type: [object, "null"] }
        model: { type: [object, "null"] }
        budget: { type: [object, "null"] }
        recursion: { type: [object, "null"] }
        freshness: { type: [object, "null"] }
        audio:
          type: object
          required: [enabled, speed, instructions]
          properties:
            enabled: { type: boolean }
            speed: { type: [string, "null"], enum: [slow, normal, fast, null] }
            instructions: { type: [string, "null"] }
        tools: { type: [object, "null"] }

    FrequencyEnum:
      type: string
      description: Report generation frequency
      enum:
        - daily
        - weekly
        - biweekly
        - monthly

    ProfileStatusEnum:
      type: string
      description: Profile status
      enum:
        - active
        - paused
        - cancelled

    RecursionConfigInput:
      type: object
      required: [enabled, maxDepth, strategy]
      properties:
        enabled:
          type: boolean
          description: When false, runs root-topic research without child subtopics.
        maxDepth:
          type: integer
          description: |
            Requested child-layer depth. The current runtime defaults an enabled value of
            `0` to `1` and caps values above `1` at one child layer. This field is ignored
            when `enabled` is false.
        strategy:
          type: string
          enum: [breadth-first, depth-first, hybrid]
          description: |
            Stored strategy preference. The current report engine executes its implemented
            breadth-first child-search path for every value.

    ProfileCreateRequest:
      type: object
      required: [name, topic, frequency, scheduleTimeOfDay]
      properties:
        name:
          type: string
          description: Profile display name
          example: "Cybersecurity Weekly"
        topic:
          type: string
          description: Topic description for research
          example: "Cybersecurity threats, vulnerabilities, and defense strategies"
        frequency:
          $ref: "#/components/schemas/FrequencyEnum"
        scheduleTimeOfDay:
          type: string
          pattern: '^\d{2}:\d{2}$'
          description: Time of day for report generation (HH:mm, UTC)
          example: "09:00"
        scheduleDayOfWeek:
          type: string
          description: Day of week for weekly/biweekly profiles
          example: "monday"
        scheduleDayOfMonth:
          type: string
          description: Day of month for monthly profiles
          example: "1"
        customPrompt:
          type: string
          description: Custom system prompt for the AI analyst
        tags:
          type: array
          items:
            type: string
          description: Tags for categorization
        isCommunity:
          type: boolean
          description: Whether this is a community (public) profile
          default: false
        blufStructure:
          type: string
          description: Custom BLUF report structure template
        recursionConfig:
          $ref: "#/components/schemas/RecursionConfigInput"
        searchConfig:
          type: object
          description: Web search configuration
          properties:
            searchDepth:
              type: string
              enum: [basic, advanced]
            maxResults:
              type: integer
            timeRange:
              type: string
            topic:
              type: string
            includeDomains:
              type: array
              items:
                type: string
            excludeDomains:
              type: array
              items:
                type: string
        modelConfig:
          type: object
          description: AI model configuration
          properties:
            modelId:
              type: string
            temperature:
              type: number
            maxOutputTokens:
              type: integer
        budgetConfig:
          type: object
          description: Cost budget configuration
          properties:
            maxCostPerReport:
              type: number
            alertThreshold:
              type: number
        freshnessConfig:
          type: object
          description: Source freshness configuration
          properties:
            enabled:
              type: boolean
            maxAgeMs:
              type: integer
            preferRecentSources:
              type: boolean
            recencyWeight:
              type: number
            validateLinks:
              type: boolean
        audioConfig:
          type: object
          description: Audio generation configuration
          properties:
            enabled:
              type: boolean
            voiceId:
              type: string
            speed:
              type: number
        toolConfig:
          type: object
          description: Tool configuration for report generation
        brandingTemplateId:
          type: string
          description: Branding template ID (paid workspace feature)

    ProfileUpdateRequest:
      type: object
      description: All fields are optional; only supplied fields change.
      properties:
        name:
          type: string
        topic:
          type: string
        status:
          $ref: "#/components/schemas/ProfileStatusEnum"
        frequency:
          $ref: "#/components/schemas/FrequencyEnum"
        scheduleTimeOfDay:
          type: string
          pattern: '^\d{2}:\d{2}$'
        scheduleDayOfWeek:
          type: string
        scheduleDayOfMonth:
          type: string
        customPrompt:
          type: string
        tags:
          type: array
          items:
            type: string
        isCommunity:
          type: boolean
        blufStructure:
          type: string
        searchConfig:
          type: object
          properties:
            searchDepth:
              type: string
              enum: [basic, advanced]
            maxResults:
              type: integer
            timeRange:
              type: string
            topic:
              type: string
            includeDomains:
              type: array
              items:
                type: string
            excludeDomains:
              type: array
              items:
                type: string
        modelConfig:
          type: object
          properties:
            modelId:
              type: string
            temperature:
              type: number
            maxOutputTokens:
              type: integer
        budgetConfig:
          type: object
          properties:
            maxCostPerReport:
              type: number
            alertThreshold:
              type: number
        recursionConfig:
          $ref: "#/components/schemas/RecursionConfigInput"
        freshnessConfig:
          type: object
          properties:
            enabled:
              type: boolean
            maxAgeMs:
              type: integer
            preferRecentSources:
              type: boolean
            recencyWeight:
              type: number
            validateLinks:
              type: boolean
        audioConfig:
          type: object
          properties:
            enabled:
              type: boolean
            voiceId:
              type: string
            speed:
              type: number
        brandingTemplateId:
          type: string
          description: Branding template ID (paid workspace feature)

    ProfileReplaceRequest:
      allOf:
        - $ref: "#/components/schemas/ProfileCreateRequest"
        - $ref: "#/components/schemas/ProfileUpdateRequest"
      description: Full mutable profile state. Omitted optional fields reset to defaults.

    ProfileCreateResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/PublicProfile"
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Profile created"

    ProfileUpdateResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/PublicProfile"
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Profile updated"

    ProfileDeleteResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [profileId, deleted]
          properties:
            profileId:
              type: string
            deleted:
              type: boolean
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Profile deleted"

    # =========================================================================
    # Projects and Automations Schemas
    # =========================================================================
    ProjectStatus:
      type: string
      enum: [active, archived]

    Project:
      type: object
      required:
        - id
        - type
        - name
        - objective
        - instructions
        - status
        - contextPolicy
        - icon
        - color
        - revision
        - defaultView
        - defaultTimeWindow
        - pinned
        - lastActivityAt
        - createdAt
        - updatedAt
        - links
      properties:
        id:
          type: string
          pattern: "^prj_[a-f0-9]{24}$"
        type:
          type: string
          const: project
        name:
          type: string
          maxLength: 80
        objective:
          type: [string, "null"]
          maxLength: 1000
        instructions:
          type: [string, "null"]
          maxLength: 8000
        status:
          $ref: "#/components/schemas/ProjectStatus"
        contextPolicy:
          type: string
          const: project_only
        icon:
          type: [string, "null"]
          maxLength: 64
        color:
          type: [string, "null"]
          maxLength: 32
        revision:
          type: integer
          minimum: 1
        defaultView:
          type: string
          enum: [graph, timeline, situation, reports, evidence, knowledge]
        defaultTimeWindow:
          type: string
          enum: [24h, 7d, 30d, custom]
        pinned:
          type: boolean
        lastActivityAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        links:
          type: object
          required: [self, automations]
          properties:
            self: { type: string }
            automations: { type: string }

    ProjectCreateRequest:
      type: object
      required: [name]
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 80
        objective:
          type: string
          maxLength: 1000
        instructions:
          type: string
          maxLength: 8000
        icon:
          type: string
          maxLength: 64
        color:
          type: string
          maxLength: 32

    ProjectPatchRequest:
      type: object
      minProperties: 1
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 80
        objective:
          type: [string, "null"]
          maxLength: 1000
        instructions:
          type: [string, "null"]
          maxLength: 8000
        icon:
          type: [string, "null"]
          maxLength: 64
        color:
          type: [string, "null"]
          maxLength: 32
        defaultView:
          type: string
          enum: [graph, timeline, situation, reports, evidence, knowledge]
        defaultTimeWindow:
          type: string
          enum: [24h, 7d, 30d, custom]
        status:
          $ref: "#/components/schemas/ProjectStatus"
        pinned:
          type: boolean

    ProjectResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Project"

    ProjectMutationResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Project"
        meta:
          type: object
          required: [message]
          properties:
            message: { type: string }

    ProjectListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Project"
        meta:
          allOf:
            - $ref: "#/components/schemas/PublicListMeta"
            - type: object
              required: [status]
              properties:
                status:
                  $ref: "#/components/schemas/ProjectStatus"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    AutomationStatus:
      type: string
      enum: [draft, active, paused, archived]

    AutomationScheduleTrigger:
      type: object
      required: [kind, frequency, timeOfDay, timezone, dayOfWeek, dayOfMonth]
      additionalProperties: false
      properties:
        kind:
          type: string
          const: schedule
        frequency:
          type: string
          enum: [daily, weekly, monthly]
        timeOfDay:
          type: string
          pattern: '^\d{2}:\d{2}$'
        timezone:
          type: string
        dayOfWeek:
          type: [integer, "null"]
          minimum: 0
          maximum: 6
        dayOfMonth:
          type: [integer, "null"]
          minimum: 1
          maximum: 31

    AutomationProfileTrigger:
      type: object
      required: [kind, profileId]
      additionalProperties: false
      properties:
        kind:
          type: string
          const: profile_report_completed
        profileId:
          type: string
          pattern: "^prf_[a-f0-9]{24}$"

    AutomationTrigger:
      oneOf:
        - $ref: "#/components/schemas/AutomationScheduleTrigger"
        - $ref: "#/components/schemas/AutomationProfileTrigger"

    AutomationSchedule:
      type: object
      required: [status, nextRunAt, lastScheduledAt, lastFiredAt]
      properties:
        status:
          type: string
          enum: [scheduled, paused, stale]
        nextRunAt:
          type: string
          format: date-time
        lastScheduledAt:
          type: string
          format: date-time
        lastFiredAt:
          type: [string, "null"]
          format: date-time

    AutomationRun:
      type: object
      required:
        - id
        - type
        - automationId
        - projectId
        - reportId
        - triggerKind
        - status
        - definitionRevision
        - resultSummary
        - model
        - usage
        - skipReason
        - error
        - scheduledFor
        - createdAt
        - startedAt
        - completedAt
        - links
      properties:
        id:
          type: string
          pattern: "^arn_[a-f0-9]{24}$"
        type:
          type: string
          const: automation_run
        automationId:
          type: string
          pattern: "^atm_[a-f0-9]{24}$"
        projectId:
          type: [string, "null"]
          pattern: "^prj_[a-f0-9]{24}$"
        reportId:
          type: [string, "null"]
          pattern: "^rpt_[a-f0-9]{24}$"
        triggerKind:
          type: string
          enum: [manual, schedule, profile_report_completed]
        status:
          type: string
          enum: [queued, running, succeeded, failed, skipped, canceled]
        definitionRevision:
          type: integer
          minimum: 1
        resultSummary:
          type: [string, "null"]
        model:
          type: [string, "null"]
        usage:
          type: object
          required: [inputTokens, outputTokens, toolCounts, actualCostUsd, billingState]
          properties:
            inputTokens: { type: [integer, "null"] }
            outputTokens: { type: [integer, "null"] }
            toolCounts:
              type: object
              additionalProperties: { type: number }
            actualCostUsd: { type: [number, "null"] }
            billingState:
              type: string
              enum: [reserved, settled, refunded, none]
        skipReason:
          type: [string, "null"]
        error:
          oneOf:
            - type: "null"
            - type: object
              required: [code, message]
              properties:
                code: { type: [string, "null"] }
                message: { type: [string, "null"] }
        scheduledFor:
          type: [string, "null"]
          format: date-time
        createdAt:
          type: string
          format: date-time
        startedAt:
          type: [string, "null"]
          format: date-time
        completedAt:
          type: [string, "null"]
          format: date-time
        links:
          type: object
          required: [automation]
          properties:
            automation: { type: string }

    Automation:
      type: object
      required:
        - id
        - type
        - name
        - instructions
        - operation
        - projectId
        - trigger
        - notificationPolicy
        - overlapPolicy
        - status
        - definitionRevision
        - schedule
        - lastRun
        - createdAt
        - updatedAt
        - links
      properties:
        id:
          type: string
          pattern: "^atm_[a-f0-9]{24}$"
        type:
          type: string
          const: automation
        name:
          type: string
          maxLength: 100
        instructions:
          type: string
          maxLength: 8000
        operation:
          type: string
          const: agent_y2
        projectId:
          type: [string, "null"]
          pattern: "^prj_[a-f0-9]{24}$"
        trigger:
          $ref: "#/components/schemas/AutomationTrigger"
        notificationPolicy:
          type: string
          enum: [all_runs, failures_only, none]
        overlapPolicy:
          type: string
          const: skip_if_running
        status:
          $ref: "#/components/schemas/AutomationStatus"
        definitionRevision:
          type: integer
          minimum: 1
        schedule:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/AutomationSchedule"
        lastRun:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/AutomationRun"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        links:
          type: object
          required: [self, runs]
          properties:
            self: { type: string }
            runs: { type: string }

    AutomationCreateRequest:
      type: object
      required: [name, instructions, trigger, notificationPolicy]
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        instructions:
          type: string
          minLength: 1
          maxLength: 8000
        projectId:
          type: [string, "null"]
          pattern: "^prj_[a-f0-9]{24}$"
        trigger:
          $ref: "#/components/schemas/AutomationTrigger"
        notificationPolicy:
          type: string
          enum: [all_runs, failures_only, none]
        status:
          type: string
          enum: [draft, active, paused]
          default: draft

    AutomationPatchRequest:
      type: object
      minProperties: 1
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        instructions:
          type: string
          minLength: 1
          maxLength: 8000
        projectId:
          type: [string, "null"]
          pattern: "^prj_[a-f0-9]{24}$"
        trigger:
          $ref: "#/components/schemas/AutomationTrigger"
        notificationPolicy:
          type: string
          enum: [all_runs, failures_only, none]
        status:
          $ref: "#/components/schemas/AutomationStatus"

    AutomationResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Automation"

    AutomationMutationResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Automation"
        meta:
          type: object
          required: [message]
          properties:
            message: { type: string }

    AutomationListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Automation"
        meta:
          allOf:
            - $ref: "#/components/schemas/PublicListMeta"
            - type: object
              required: [status]
              properties:
                status:
                  oneOf:
                    - type: "null"
                    - $ref: "#/components/schemas/AutomationStatus"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    AutomationRunListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AutomationRun"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    AutomationRunRequestResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [type, status, automationId]
          properties:
            type:
              type: string
              const: automation_run_request
            status:
              type: string
              const: accepted
            automationId:
              type: string
              pattern: "^atm_[a-f0-9]{24}$"
        meta:
          type: object
          required: [message]
          properties:
            message: { type: string }

    # =========================================================================
    # News Schemas
    # =========================================================================
    NewsListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/NewsItem"
        meta:
          allOf:
            - $ref: "#/components/schemas/PublicListMeta"
            - type: object
              required: [topics]
              properties:
                topics:
                  type: array
                  items:
                    $ref: "#/components/schemas/TopicEnum"
                countryCode:
                  type: [string, "null"]
                  pattern: "^[A-Z]{2}$"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    NewsItem:
      type: object
      required: [id, type, title, summary, content, publishedAt, topics, sentiment, author, sources, links, geography]
      properties:
        id:
          type: string
          pattern: "^obs_[a-f0-9]{24}$"
        type:
          type: string
          const: news
        title:
          type: string
        summary:
          type: string
        content:
          type: string
        publishedAt:
          type: string
          format: date-time
        topics:
          type: array
          items: { type: string }
        sentiment:
          type: object
          required: [label, value]
          properties:
            label:
              oneOf:
                - $ref: "#/components/schemas/SentimentEnum"
                - type: "null"
            value: { type: number }
        sources:
          type: array
          items:
            $ref: "#/components/schemas/NewsSource"
        author:
          type: [string, "null"]
        links:
          type: object
          required: [canonical]
          properties:
            canonical: { type: [string, "null"], format: uri }
        geography:
          type: ["object", "null"]
          description: Normalized country, region, location, coordinates, and resolver provenance.
          properties:
            countryCode:
              type: string
              pattern: "^[A-Z]{2}$"
            region:
              type: string
            locationName:
              type: string
            lat:
              type: number
            lon:
              type: number
            provenance:
              type: object
              additionalProperties: true

    NewsSource:
      type: object
      required: [id, url, title, publisher, publishedAt, retrievedAt, sourceType, language]
      properties:
        id: { type: string, pattern: "^src_[a-f0-9]{24}$" }
        url: { type: [string, "null"], format: uri }
        title: { type: [string, "null"] }
        publisher: { type: [string, "null"] }
        publishedAt: { type: string, format: date-time }
        retrievedAt: { type: [string, "null"], format: date-time }
        sourceType: { type: string }
        language: { type: [string, "null"] }

    RecapResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          additionalProperties:
            type: object
            description: Topic recap data
        meta:
          type: object
          properties:
            timeframe:
              $ref: "#/components/schemas/TimeframeEnum"
            topics:
              type: array
              items:
                $ref: "#/components/schemas/TopicEnum"

    FeedListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Feed"
        meta:
          type: object
          properties:
            count:
              type: integer
            defaultTopics:
              type: array
              items:
                $ref: "#/components/schemas/TopicEnum"

    Feed:
      type: object
      required:
        - id
        - name
        - shortLabel
        - description
        - group
        - groupLabel
        - color
        - ingestOntology
      properties:
        id:
          $ref: "#/components/schemas/TopicEnum"
        name:
          type: string
          description: Human-readable name
          example: "Crypto"
        shortLabel:
          type: string
          description: Compact display name
        description:
          type: string
          description: Feed description
        group:
          type: string
          description: Machine-readable topic group ID
        groupLabel:
          type: string
          description: Human-readable topic group name
        color:
          type: string
          description: UI gradient classes associated with the feed
        ingestOntology:
          type: boolean
          description: Whether eligible signals can enter the OSINT ontology pipeline

    TopicEnum:
      type: string
      description: Available Y2 News Terminal feed topics
      enum:
        - ai
        - ai_agents
        - base
        - bitcoin
        - crypto
        - dats
        - defi
        - ethereum
        - hyperliquid
        - machine_learning
        - macro
        - on_chain_whale
        - perps
        - ripple
        - rwa
        - solana
        - tech
        - token_listings
        - virtuals
        - geopolitics
        - politics_us
        - defense
        - sanctions_trade
        - equities
        - rates_fx
        - commodities
        - banking_reg
        - energy
        - cyber
        - semiconductors
        - biotech
        - logistics
        - critical_minerals
        - telecom
        - region_mena
        - region_europe
        - region_asiapac
        - region_latam
        - region_africa
        - region_namerica

    SentimentEnum:
      type: ["string", "null"]
      description: Sentiment classification for news items
      enum:
        - bullish
        - bearish
        - neutral
        - null

    TimeframeEnum:
      type: string
      description: Time period for recap data
      default: 12h
      enum:
        - 12h
        - 24h
        - 3d
        - 7d

    # =========================================================================
    # OSINT Schemas
    # =========================================================================
    OsintEventCategoryEnum:
      type: string
      description: OSINT event category classification
      enum:
        - seismic
        - conflict
        - political
        - economic
        - weather
        - health
        - cyber
        - maritime
        - fire
        - aviation
        - other

    OsintSeverityEnum:
      type: string
      description: Event severity level
      enum:
        - low
        - medium
        - high
        - critical

    OsintRegionEnum:
      type: string
      description: Geographic region identifier
      enum:
        - mena
        - africa
        - latam
        - asiapac
        - europe
        - namerica

    OsintEvent:
      type: object
      required: [id, sourceType, sourceId, title, category, severity, eventTime]
      properties:
        id:
          type: string
          description: Event ID
        sourceType:
          type: string
          description: Data source type
        sourceId:
          type: string
          description: Source-specific event identifier
        title:
          type: string
          description: Event title/headline
        description:
          type: string
          description: Detailed event description
        category:
          $ref: "#/components/schemas/OsintEventCategoryEnum"
        severity:
          $ref: "#/components/schemas/OsintSeverityEnum"
        coordinates:
          type: ["object", "null"]
          description: Geographic coordinates (null if unavailable)
          properties:
            lat:
              type: number
              format: double
              description: Latitude
            lon:
              type: number
              format: double
              description: Longitude
        locationName:
          type: string
          description: Human-readable location name
        region:
          $ref: "#/components/schemas/OsintRegionEnum"
        countryCode:
          type: string
          description: ISO alpha-2 country code
          pattern: "^[A-Z]{2}$"
        eventTime:
          type: integer
          format: int64
          description: Event time as Unix timestamp (milliseconds)
        eventTimeISO:
          type: string
          format: date-time
          description: Event time as ISO 8601 string
        fetchedAt:
          type: ["integer", "null"]
          format: int64
          description: Data fetch time as Unix timestamp (milliseconds), null if unavailable
        fetchedAtISO:
          type: ["string", "null"]
          format: date-time
          description: Data fetch time as ISO 8601 string, null if unavailable
        url:
          type: string
          format: uri
          description: Source URL for the event
        provenance:
          type: ["object", "null"]
          description: |
            Extraction provenance metadata. Populated only when
            `sourceType === "y2_report"`; null for all other sources.
            Returned by `/osint/regional`.
          allOf:
            - $ref: "#/components/schemas/Y2EventProvenance"

    OsintMapEvent:
      type: object
      required: [id, category, severity, coordinates, title, eventTime]
      properties:
        id:
          type: string
          description: Event ID
        category:
          $ref: "#/components/schemas/OsintEventCategoryEnum"
        severity:
          $ref: "#/components/schemas/OsintSeverityEnum"
        coordinates:
          type: object
          required: [lat, lon]
          description: Geographic coordinates (always present — events without coordinates are excluded)
          properties:
            lat:
              type: number
              format: double
              description: Latitude
            lon:
              type: number
              format: double
              description: Longitude
        title:
          type: string
          description: Event title/headline
        eventTime:
          type: integer
          format: int64
          description: Event time as Unix timestamp (milliseconds)
        eventTimeISO:
          type: string
          format: date-time
          description: Event time as ISO 8601 string
        sourceType:
          type: string
          description: Data source type
        description:
          type: string
          description: Detailed event description
        url:
          type: string
          format: uri
          description: Source URL for the event
        locationName:
          type: string
          description: Human-readable location name
        region:
          $ref: "#/components/schemas/OsintRegionEnum"
        countryCode:
          type: string
          description: ISO alpha-2 country code
          pattern: "^[A-Z]{2}$"

    CIIItem:
      type: object
      required: [id, category, value, delta, computedAt]
      properties:
        id:
          type: string
          description: CII item ID
        category:
          type: string
          description: Conflict indicator category
        region:
          $ref: "#/components/schemas/OsintRegionEnum"
        value:
          type: number
          minimum: 0
          maximum: 100
          description: Conflict indicator value (0-100)
        delta:
          type: number
          description: Recent change in value
        components:
          type: object
          additionalProperties: true
          description: Breakdown of indicator components
        computedAt:
          type: integer
          format: int64
          description: Computation time as Unix timestamp (milliseconds)
        computedAtISO:
          type: string
          format: date-time
          description: Computation time as ISO 8601 string

    IntelBrief:
      type: object
      required: [briefText, generatedAt, generatedAtISO, publishedAt]
      properties:
        briefText:
          type: string
          description: AI-generated intelligence brief text
        generatedAt:
          type: integer
          format: int64
          description: Generation time as Unix timestamp (milliseconds)
        generatedAtISO:
          type: string
          format: date-time
        publishedAt:
          type: string
          format: date-time

    StockIndex:
      type: object
      required: [ticker, indexName, weeklyChange, currency, fetchedAt]
      properties:
        ticker:
          type: string
          description: Stock index ticker symbol
          example: "^GSPC"
        indexName:
          type: string
          description: Human-readable index name
          example: "S&P 500"
        currentPrice:
          type: number
          format: float
          description: Current index price/value
        weeklyChange:
          type: number
          format: float
          description: Weekly change percentage
        currency:
          type: string
          description: Currency code
          example: "USD"
        fetchedAt:
          type: integer
          format: int64
          description: Data fetch time as Unix timestamp (milliseconds)
        fetchedAtISO:
          type: string
          format: date-time
          description: Data fetch time as ISO 8601 string

    CountryPredictionMarket:
      type: object
      required: [id, type, marketId, title, probability, outcomes, probabilityBasis]
      description: |
        Polymarket-only prediction market shape returned by
        `/osint/countries/{countryCode}/predictions`. The unified
        Polymarket+Kalshi shape returned by `/osint/prediction-markets`
        is documented separately as `PredictionMarket`.
      properties:
        id:
          type: string
          pattern: "^mkt_[a-f0-9]{24}$"
        type:
          type: string
          const: prediction_market
        marketId:
          type: string
          description: Prediction market identifier
        title:
          type: string
          description: Market question/title
        probability:
          type: number
          minimum: 0
          maximum: 1
          description: Current probability (0-1)
        volume:
          type: number
          description: Trading volume
        liquidity:
          type: ["number", "null"]
          description: Market liquidity (null if unavailable)
        outcomes:
          type: array
          items:
            $ref: "#/components/schemas/PredictionOutcome"
          description: Typed outcome labels with normalized fractional probabilities.
        probabilityBasis:
          type: string
          const: fraction_0_to_1
        volumeMeasurement:
          $ref: "#/components/schemas/ProviderReportedMeasurement"
        liquidityMeasurement:
          $ref: "#/components/schemas/ProviderReportedMeasurement"
        slug:
          type: ["string", "null"]
          description: URL-friendly market slug (null if unavailable)
        polymarketUrl:
          type: string
          format: uri
          description: Polymarket URL for this market
        endDate:
          type: string
          format: date-time
          description: Market resolution date

    CountryNewsItem:
      type: object
      required: [id, title, severity, category, eventTime, sourceType, countryCode]
      properties:
        id:
          type: string
          description: News item ID
        title:
          type: string
          description: News headline
        description:
          type: string
          description: News description/summary
        severity:
          $ref: "#/components/schemas/OsintSeverityEnum"
        category:
          $ref: "#/components/schemas/OsintEventCategoryEnum"
        eventTime:
          type: integer
          format: int64
          description: Event time as Unix timestamp (milliseconds)
        eventTimeISO:
          type: string
          format: date-time
        occurredAt:
          type: string
          format: date-time
        observedAt:
          type: ["string", "null"]
          format: date-time
        fetchedAt:
          type: ["integer", "null"]
          format: int64
        fetchedAtISO:
          type: ["string", "null"]
          format: date-time
        url:
          type: string
          format: uri
          description: Source URL
        sourceType:
          type: string
          description: Data source type
        countryCode:
          type: string
          pattern: "^[A-Z]{2}$"
          description: Canonical ISO 3166-1 alpha-2 country code used by the indexed query.
        region:
          type: ["string", "null"]
          description: Normalized OSINT region, when known.
        locationName:
          type: ["string", "null"]
          description: Most specific resolved sovereign location, when known.
        coordinates:
          type: ["object", "null"]
          properties:
            lat:
              type: number
              format: double
            lon:
              type: number
              format: double
        geometry:
          $ref: "#/components/schemas/GeoJSONPoint"
        provenance:
          type: object
          required: [countryCodeBasis, geoResolution]
          properties:
            countryCodeBasis:
              type: string
              enum: [resolved_geography, indexed_observation]
            geoResolution:
              type: ["object", "null"]
              additionalProperties: true
              description: Resolver method, confidence, version, and chokepoint context when available.

    SourceStatus:
      type: object
      required: [sourceType, state, failureCount]
      properties:
        sourceType:
          type: string
          description: Data source identifier
        state:
          type: string
          enum: [closed, open, half_open]
          description: Circuit breaker state
        failureCount:
          type: integer
          description: Number of consecutive failures
        lastSuccessAt:
          type: integer
          format: int64
          description: Last successful fetch time (milliseconds)
        lastSuccessAtISO:
          type: ["string", "null"]
          format: date-time
          description: Last successful fetch time as ISO 8601 string
        lastFailureAt:
          type: integer
          format: int64
          description: Last failure time (milliseconds)
        lastFailureAtISO:
          type: ["string", "null"]
          format: date-time
          description: Last failure time as ISO 8601 string
        lastError:
          type: string
          description: Most recent error message

    CountryCIIItem:
      type: object
      required: [countryCode, countryName, value, baseline, delta, computedAt]
      properties:
        countryCode:
          type: string
          description: ISO alpha-2 country code
          pattern: "^[A-Z]{2}$"
        countryName:
          type: string
          description: Country display name
        value:
          type: number
          minimum: 0
          maximum: 100
          description: Current instability index (0-100)
        baseline:
          type: number
          description: Baseline instability score for this country
        delta:
          type: number
          description: Change from baseline
        components:
          type: object
          description: Breakdown of instability components
          properties:
            unrest:
              type: number
            security:
              type: number
            info:
              type: number
            eventCounts:
              type: object
            multiplier:
              type: number
        computedAt:
          type: integer
          format: int64
          description: Computation time as Unix timestamp (milliseconds)
        computedAtISO:
          type: string
          format: date-time
          description: Computation time as ISO 8601 string

    MilitaryPostureItem:
      type: object
      required: [theater, posture, aircraftCount, computedAt]
      properties:
        theater:
          type: string
          description: Theater name (e.g. "iran", "taiwan", "blacksea", "scs")
        posture:
          type: string
          enum: [normal, elevated, critical]
          description: Current military posture assessment
        aircraftCount:
          type: integer
          description: Number of detected military aircraft
        breakdown:
          type: object
          description: Aircraft count by type
          properties:
            tanker:
              type: integer
            awacs:
              type: integer
            fighter:
              type: integer
            recon:
              type: integer
            transport:
              type: integer
            bomber:
              type: integer
            drone:
              type: integer
            military_generic:
              type: integer
        computedAt:
          type: integer
          format: int64
          description: Assessment time as Unix timestamp (milliseconds)
        computedAtISO:
          type: string
          format: date-time
          description: Assessment time as ISO 8601 string

    AircraftTrack:
      type: object
      required: [id, icao24, callsign, coordinates, theater, lastSeenAt]
      properties:
        id:
          type: string
          description: Track ID
        icao24:
          type: string
          description: ICAO 24-bit transponder address
        callsign:
          type: string
          description: Aircraft callsign
        aircraftType:
          type: string
          description: Classified aircraft type (tanker, awacs, fighter, recon, etc.)
        operator:
          type: string
          description: Operating entity
        confidence:
          type: number
          description: Classification confidence score
        coordinates:
          type: object
          required: [lat, lon]
          properties:
            lat:
              type: number
              format: double
            lon:
              type: number
              format: double
        altitude:
          type: number
          description: Altitude in meters
        heading:
          type: number
          description: Heading in degrees
        speed:
          type: number
          description: Ground speed in m/s
        theater:
          type: string
          description: Theater assignment
        isInteresting:
          type: boolean
          description: Whether flagged as operationally interesting
        lastSeenAt:
          type: integer
          format: int64
          description: Last seen time as Unix timestamp (milliseconds)
        lastSeenAtISO:
          type: string
          format: date-time
          description: Last seen time as ISO 8601 string

    NavalVessel:
      type: object
      required: [id, name, vesselType, coordinates]
      properties:
        id:
          type: string
          description: Vessel ID
        name:
          type: string
          description: Vessel name
        hullNumber:
          type: string
          description: Hull number designation
        vesselType:
          type: string
          description: Vessel type classification
        operator:
          type: string
          description: Operating navy
        region:
          type: string
          description: Deployment region
        strikeGroup:
          type: string
          description: Strike group assignment
        status:
          type: string
          description: Current operational status
        coordinates:
          type: object
          required: [lat, lon]
          properties:
            lat:
              type: number
              format: double
            lon:
              type: number
              format: double
        confidence:
          type: string
          enum: [low, medium, high]
          description: Categorical position confidence from the source model.
        source:
          type: string
          description: Data source
        fetchedAt:
          type: integer
          format: int64
          description: Fetch time as Unix timestamp (milliseconds)
        fetchedAtISO:
          type: string
          format: date-time
          description: Fetch time as ISO 8601 string

    GPSJammingZone:
      type: object
      required: [id, h3Index, coordinates, severity, fetchedAt]
      properties:
        id:
          type: string
          description: Zone ID
        h3Index:
          type: string
          description: H3 hexagonal cell index
        coordinates:
          type: object
          required: [lat, lon]
          properties:
            lat:
              type: number
              format: double
            lon:
              type: number
              format: double
        sampleCount:
          type: integer
          description: Number of ADS-B samples in this cell
        aircraftCount:
          type: integer
          description: Number of distinct aircraft reporting
        navAccuracyAvg:
          type: number
          description: Average navigation accuracy category
        severity:
          type: string
          description: Severity classification of interference
        fetchedAt:
          type: integer
          format: int64
          description: Fetch time as Unix timestamp (milliseconds)
        fetchedAtISO:
          type: string
          format: date-time
          description: Fetch time as ISO 8601 string

    OsintEventListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/OsintEvent"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit
            filters:
              type: object
              properties:
                category:
                  type: ["string", "null"]
                severity:
                  type: ["string", "null"]

    OsintMapEventListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/OsintMapEvent"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit
            filters:
              type: object
              properties:
                region:
                  type: ["string", "null"]

    CIIListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CIIItem"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit
            filters:
              type: object
              properties:
                region:
                  type: ["string", "null"]
                category:
                  type: ["string", "null"]

    IntelBriefResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          allOf:
            - $ref: "#/components/schemas/IntelBrief"

    StockIndexResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          allOf:
            - $ref: "#/components/schemas/StockIndex"

    CountryPredictionMarketListResponse:
      type: object
      required: [data, meta]
      description: |
        Response wrapper for `/osint/countries/{countryCode}/predictions`.
        See `PredictionMarketListResponse` for the unified
        Polymarket+Kalshi response wrapper used by `/osint/prediction-markets`.
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CountryPredictionMarket"
        meta:
          type: object
          properties:
            countryCode:
              type: string
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit

    CountryNewsListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CountryNewsItem"
        meta:
          type: object
          properties:
            countryCode:
              type: string
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit

    CountryCIIResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          allOf:
            - $ref: "#/components/schemas/CountryCIIItem"
        meta:
          type: object
          properties:
            countryCode:
              type: string

    MilitaryPostureListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/MilitaryPostureItem"
        meta:
          allOf:
            - $ref: "#/components/schemas/FeedAvailabilityMeta"
            - type: object
              properties:
                count:
                  type: integer
                limit:
                  type: integer
                hasMore:
                  type: boolean
                  description: Whether more results are available beyond the current limit

    AircraftListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AircraftTrack"
        meta:
          allOf:
            - $ref: "#/components/schemas/FeedAvailabilityMeta"
            - type: object
              properties:
                count:
                  type: integer
                limit:
                  type: integer
                hasMore:
                  type: boolean
                  description: Whether more results are available beyond the current limit
                filters:
                  type: object
                  properties:
                    theater:
                      type: ["string", "null"]

    VesselListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/NavalVessel"
        meta:
          allOf:
            - $ref: "#/components/schemas/FeedAvailabilityMeta"
            - type: object
              properties:
                count:
                  type: integer
                limit:
                  type: integer
                hasMore:
                  type: boolean
                  description: Whether more results are available beyond the current limit
                filters:
                  type: object
                  properties:
                    region:
                      type: ["string", "null"]

    GPSJammingListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/GPSJammingZone"
        meta:
          allOf:
            - $ref: "#/components/schemas/FeedAvailabilityMeta"
            - type: object
              properties:
                count:
                  type: integer
                limit:
                  type: integer
                hasMore:
                  type: boolean
                  description: Whether more results are available beyond the current limit
                filters:
                  type: object
                  properties:
                    severity:
                      type: ["string", "null"]

    FeedAvailabilityMeta:
      type: object
      required: [emptyReason, sourceStatus, lastSuccessfulIngestAt]
      properties:
        emptyReason:
          type: [string, "null"]
          enum: [no_matching_activity, source_degraded, source_stale, source_unavailable, source_unknown, null]
        sourceStatus:
          type: string
          enum: [healthy, degraded, stale, unavailable, unknown]
        lastSuccessfulIngestAt:
          type: [string, "null"]
          format: date-time

    SourceStatusListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SourceStatus"
        meta:
          type: object
          properties:
            count:
              type: integer

    # =========================================================================
    # Y2 Event Provenance Schemas
    # =========================================================================

    EntityKind:
      type: string
      enum: [person, organization, country, region, vessel, aircraft, facility, asset, indicator, cve, malware_family, threat_actor, vendor, software, ai_model, api_service, protocol]

    Y2EventProvenance:
      type: object
      required: [source]
      properties:
        source:
          type: string
          description: Source identifier (always "y2_report")
          enum: [y2_report]
        classification:
          type: string
          description: Event temporal classification from GroundSource methodology
          enum: [ongoing, past, forecast, analysis]
        confidence:
          type: number
          format: double
          minimum: 0
          maximum: 1
          description: Extraction confidence score (0.5-1.0)
        entities:
          type: array
          description: Named entities extracted from the report
          items:
            type: object
            required: [name, kind]
            properties:
              name:
                type: string
                description: Entity name
              kind:
                $ref: "#/components/schemas/EntityKind"
                description: Entity type

    Y2Event:
      type: object
      required: [id, sourceType, title, category, severity, eventTime]
      properties:
        id:
          type: string
          description: Event ID
        sourceType:
          type: string
          description: Data source type (always "y2_report")
          enum: [y2_report]
        title:
          type: string
          description: Event title/headline
        description:
          type: string
          description: Detailed event description
        category:
          $ref: "#/components/schemas/OsintEventCategoryEnum"
        severity:
          $ref: "#/components/schemas/OsintSeverityEnum"
        coordinates:
          type: ["object", "null"]
          description: Geographic coordinates (null if unavailable)
          properties:
            lat:
              type: number
              format: double
              description: Latitude
            lon:
              type: number
              format: double
              description: Longitude
        locationName:
          type: string
          description: Human-readable location name
        countryCode:
          type: string
          description: ISO alpha-2 country code
          pattern: "^[A-Z]{2}$"
        region:
          $ref: "#/components/schemas/OsintRegionEnum"
        eventTime:
          type: integer
          format: int64
          description: Event time as Unix timestamp (milliseconds)
        url:
          type: string
          description: Link to the originating report
        provenance:
          type: ["object", "null"]
          description: Extraction provenance metadata linking to the Y2 report
          allOf:
            - $ref: "#/components/schemas/Y2EventProvenance"

    Y2EventListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Y2Event"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit

    # =========================================================================
    # FININT (Financial Intelligence) Schemas
    # =========================================================================

    FinintIndicator:
      type: object
      required: [indicatorId, source, label, category, value, fetchedAt]
      properties:
        indicatorId:
          type: string
          description: |
            Unique indicator key. Aggregate rows use `<SOURCE>:<METRIC>`
            (e.g., `FRED:DGS10`, `YF:^GSPC`, `EIA:WTI`). Per-dimension
            rows use `<SOURCE>:<METRIC>:<DIMENSION>`
            (e.g., `EIA:NUKE_OUTAGE:vogtle`, `EIA:RTO_LOAD:PJM`,
            `EIA:CRUDE_IMPORT:houston`).
        source:
          type: string
          enum: [fred, yfinance, eia]
          description: Data source
        label:
          type: string
          description: Human-readable indicator name
        category:
          type: string
          enum: [rates, credit, inflation, labor, gdp, business, consumer, housing, money_supply, volatility, commodities, fx, equities, energy, crypto]
          description: Indicator category
        value:
          type: number
          description: Current indicator value
        previousValue:
          type: [number, "null"]
          description: Previous value (for change calculation)
        change:
          type: [number, "null"]
          description: Absolute change from previous value
        changePct:
          type: [number, "null"]
          description: Percentage change from previous value
        unit:
          type: [string, "null"]
          description: Value unit (e.g., "%", "$", "index", "$/bbl")
        recent:
          type: array
          description: Recent data points for sparkline rendering
          items:
            type: object
            properties:
              value:
                type: number
              date:
                type: string
        signals:
          type: array
          description: Active signal descriptions (threshold crossings)
          items:
            type: string
        fetchedAt:
          type: integer
          format: int64
          description: Unix timestamp of last data fetch
        dimension:
          type: [string, "null"]
          description: |
            Dimension key (e.g., `vogtle`, `PJM`, `houston`). Null on
            US-aggregate rows.
        dimensionType:
          type: [string, "null"]
          enum: [facility, rto, port, state, null]
          description: |
            Dimension classifier. Null on US-aggregate rows. Currently only
            EIA emits per-dimension rows.
        lat:
          type: [number, "null"]
          format: double
          description: Latitude of the dimension (facility/RTO HQ/port). Null on aggregate rows.
        lon:
          type: [number, "null"]
          format: double
          description: Longitude of the dimension. Null on aggregate rows.

    FinintIndicatorListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FinintIndicator"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
              description: Whether more results are available beyond the current limit
            filters:
              type: object
              description: Applied filter values
              properties:
                category:
                  type: string
                source:
                  type: string
                dimensionType:
                  type: [string, "null"]

    # =========================================================================
    # Cyber Threat Schemas
    # =========================================================================

    CyberThreat:
      type: object
      required: [id, sourceType, sourceId, title, severity, eventTime]
      properties:
        id:
          type: string
          description: Event ID
        sourceType:
          type: string
          enum: [urlhaus, feodo, rss]
          description: Threat intelligence source
        sourceId:
          type: string
          description: Source-specific identifier
        title:
          type: string
          description: Threat description
        description:
          type: [string, "null"]
        severity:
          $ref: "#/components/schemas/OsintSeverityEnum"
        countryCode:
          type: [string, "null"]
          description: ISO alpha-2 country code of threat origin
        eventTime:
          type: integer
          format: int64
          description: Threat detection timestamp (ms)
        eventTimeISO:
          type: string
          format: date-time
        url:
          type: [string, "null"]
          description: Malicious URL or indicator
        threat:
          type: [object, "null"]
          description: Threat-specific metadata
          properties:
            type:
              type: string
              description: Threat type (malware_distribution, phishing, c2, etc.)
            tags:
              type: array
              items:
                type: string
              description: Threat tags (ransomware, botnet, etc.)
            host:
              type: string
              description: Host IP or domain
            source:
              type: string
              description: Intelligence source (urlhaus, feodo)

    CyberThreatListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CyberThreat"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
            filters:
              type: object
              properties:
                severity:
                  type: [string, "null"]

    # =========================================================================
    # Prediction Market Schemas
    # =========================================================================

    PredictionMarket:
      type: object
      required: [id, type, marketId, title, probability, volume, outcomes, probabilityBasis, volumeMeasurement, source]
      properties:
        id:
          type: string
          pattern: "^mkt_[a-f0-9]{24}$"
        type:
          type: string
          const: prediction_market
        marketId:
          type: string
          description: Market identifier (Polymarket event ID or Kalshi ticker)
        title:
          type: string
          description: Market question
        eventTitle:
          type: [string, "null"]
          description: Parent event title
        probability:
          type: number
          minimum: 0
          maximum: 1
          description: Current normalized probability from 0 to 1.
        volume:
          type: number
          description: Total trading volume
        liquidity:
          type: number
          description: Current liquidity
        outcomes:
          type: array
          items:
            $ref: "#/components/schemas/PredictionOutcome"
          description: Typed outcome labels with normalized fractional probabilities.
        probabilityBasis:
          type: string
          const: fraction_0_to_1
        volumeMeasurement:
          $ref: "#/components/schemas/ProviderReportedMeasurement"
        url:
          type: [string, "null"]
          description: Market URL (Polymarket or Kalshi)
        endDate:
          type: [string, "null"]
          description: Market close date
        source:
          type: string
          enum: [polymarket, kalshi]
          description: Prediction market platform

    PredictionOutcome:
      type: object
      required: [label, probability]
      additionalProperties: false
      properties:
        label:
          type: string
        probability:
          type: number
          minimum: 0
          maximum: 1

    ProviderReportedMeasurement:
      type: object
      required: [value, currency, basis]
      additionalProperties: false
      properties:
        value:
          type: [number, "null"]
        currency:
          type: [string, "null"]
          description: ISO 4217 code when the source identifies one; otherwise null.
        basis:
          type: string
          const: provider_reported

    PredictionMarketListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PredictionMarket"
        meta:
          type: object
          properties:
            count:
              type: integer
            limit:
              type: integer
            hasMore:
              type: boolean
            filters:
              type: object
              properties:
                countryCode:
                  type: [string, "null"]

    # =========================================================================
    # Webhook Management Schemas
    # =========================================================================

    WebhookConfig:
      type: object
      required: [id, type, name, url, status, signing, customHeaders, deliveryHealth, createdAt, updatedAt, links]
      properties:
        id:
          type: string
          pattern: "^whk_[a-f0-9]{24}$"
          description: Stable public webhook configuration ID
        type:
          type: string
          const: webhook
        name:
          type: string
          description: Webhook display name
        url:
          type: string
          format: uri
          description: Webhook endpoint URL
        status:
          type: string
          enum: [active, disabled]
        signing:
          type: object
          required: [configured, algorithm]
          properties:
            configured: { type: boolean }
            algorithm: { type: [string, "null"], enum: [hmac-sha256, null] }
        customHeaders:
          type: array
          description: Configured header names; values are never returned.
          items: { type: string }
        deliveryHealth:
          type: object
          required: [consecutiveFailures, lastUsedAt]
          properties:
            consecutiveFailures: { type: integer, minimum: 0 }
            lastUsedAt: { type: [string, "null"], format: date-time }
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        links:
          type: object
          required: [self, test]
          properties:
            self: { type: string }
            test: { type: string }

    WebhookListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookConfig"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    WebhookCreateRequest:
      type: object
      required: [name, url]
      properties:
        name:
          type: string
          description: Webhook display name
          example: "My Webhook"
        url:
          type: string
          format: uri
          description: Webhook endpoint URL (must be HTTPS)
          example: "https://example.com/webhook"
        secret:
          type: string
          description: Shared secret for signature verification
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom headers to include in webhook deliveries

    WebhookCreateResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/WebhookConfig"
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Webhook configuration created"

    WebhookUpdateRequest:
      type: object
      description: All fields are optional; only supplied fields change.
      properties:
        name:
          type: string
        url:
          type: string
          format: uri
        secret:
          type: string
        headers:
          type: object
          additionalProperties:
            type: string
        isActive:
          type: boolean

    WebhookReplaceRequest:
      allOf:
        - $ref: "#/components/schemas/WebhookCreateRequest"
        - type: object
          properties:
            isActive:
              type: boolean
      description: Full mutable webhook state. Omitted optional fields reset to defaults.

    WebhookUpdateResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/WebhookConfig"
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Webhook configuration updated"

    WebhookDeleteResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [webhookId, deleted]
          properties:
            webhookId:
              type: string
            deleted:
              type: boolean
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Webhook configuration deleted"

    WebhookTestResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [success]
          properties:
            success:
              type: boolean
            statusCode:
              type: integer
              description: HTTP status code from the webhook endpoint
            error:
              type: string
              description: Error message if the test failed
            responseTime:
              type: integer
              description: Response time in milliseconds
        meta:
          type: object
          properties:
            message:
              type: string

    DeliveryMethodEnum:
      type: string
      description: Subscription delivery method
      enum:
        - email
        - sms
        - webhook
        - both_email_sms

    EmailAudienceEnum:
      type: string
      description: Email recipients for email-capable subscription delivery
      enum:
        - individual
        - workspace

    DeliveryUpdateRequest:
      type: object
      required: [deliveryMethod]
      properties:
        deliveryMethod:
          $ref: "#/components/schemas/DeliveryMethodEnum"
        emailAudience:
          $ref: "#/components/schemas/EmailAudienceEnum"
        webhookConfigId:
          type: string
          pattern: "^whk_[a-f0-9]{24}$"
          description: Required when deliveryMethod is "webhook"

    DeliveryUpdateResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [id, type, delivery, links]
          properties:
            id:
              type: string
              pattern: "^sub_[a-f0-9]{24}$"
            type:
              type: string
              const: subscription
            delivery:
              type: object
              required: [method, emailAudience, webhookId]
              properties:
                method:
                  $ref: "#/components/schemas/DeliveryMethodEnum"
                emailAudience:
                  $ref: "#/components/schemas/EmailAudienceEnum"
                webhookId:
                  type: [string, "null"]
                  pattern: "^whk_[a-f0-9]{24}$"
            links:
              type: object
              required: [delivery]
              properties:
                delivery: { type: string }
        meta:
          type: object
          properties:
            message:
              type: string
              example: "Delivery method updated"

    # =========================================================================
    # Webhook Payload Schemas (for documentation)
    # =========================================================================

    # Webhook Headers:
    # Headers sent with every webhook delivery:
    # - Content-Type: application/cloudevents+json
    # - User-Agent: Y2-Webhook-Delivery/3.0
    # - X-Y2-Timestamp: <unix_timestamp_seconds>
    # - X-Y2-Event-Id: <stable_logical_event_id>
    # - X-Y2-Attempt-Id: <unique_transport_attempt_id>
    # - X-Y2-Attempt: <one_based_attempt_number>
    # - Idempotency-Key: <stable_logical_event_id>
    # - X-Y2-Signature: sha256=<hmac_sha256_hex> (only if secret configured)
    # - Plus any custom headers you configured in the webhook settings
    #
    # To verify the webhook:
    # 1. Compute HMAC-SHA256 over the raw JSON body with the configured secret
    # 2. Prefix the hex digest with "sha256=" and compare in constant time
    # 3. Optionally reject X-Y2-Timestamp values older than 5 minutes

    Y2WebhookPayload:
      type: object
      description: |
        Compact CloudEvents 1.0 payload sent after intelligence report generation.
        The body is immutable across retries; transport attempts are identified by headers.

        **Headers:**
        - `X-Y2-Timestamp`: Send time as Unix seconds
        - `X-Y2-Event-Id`: Stable logical event ID; matches the body `id`
        - `X-Y2-Attempt-Id`: Unique identifier for this transport attempt
        - `X-Y2-Attempt`: One-based attempt number
        - `Idempotency-Key`: Stable logical event ID
        - `X-Y2-Signature`: `sha256=<hex>` HMAC-SHA256 of the raw body, present when a secret is configured
      required:
        - specversion
        - id
        - source
        - type
        - subject
        - time
        - datacontenttype
        - dataschema
        - data
      properties:
        specversion:
          type: string
          const: "1.0"
        id:
          type: string
          description: Stable logical event ID reused by every retry.
        source:
          type: string
          format: uri
        type:
          type: string
          const: dev.y2.report.generated.v1
        subject:
          type: string
          pattern: "^reports/rpt_[a-f0-9]{24}$"
        time:
          type: string
          format: date-time
        datacontenttype:
          type: string
          const: application/json
        dataschema:
          type: string
          const: https://api.y2.dev/schemas/events/report-generated-v1.json
        data:
          $ref: "#/components/schemas/WebhookReportGeneratedData"

    WebhookReportGeneratedData:
      type: object
      required: [report, subscription, links]
      properties:
        report:
          type: object
          required: [id, profileId, summary, status, generatedAt, language, intelligence, audio]
          properties:
            id: { type: string, pattern: "^rpt_[a-f0-9]{24}$" }
            profileId: { type: string, pattern: "^prf_[a-f0-9]{24}$" }
            summary: { type: [string, "null"] }
            status: { type: string, const: published }
            generatedAt: { type: string, format: date-time }
            language: { type: string, const: en }
            intelligence:
              type: object
              required: [signalCount, graphNodeCount]
              properties:
                signalCount: { type: integer, minimum: 0 }
                graphNodeCount: { type: integer, minimum: 0 }
            audio:
              type: object
              required: [status, durationSeconds]
              properties:
                status: { type: string, enum: [available, unavailable] }
                durationSeconds: { type: [number, "null"] }
        subscription:
          type: object
          required: [id]
          properties:
            id: { type: string, pattern: "^sub_[a-f0-9]{24}$" }
        links:
          type: object
          additionalProperties:
            type: string
            format: uri

    # =========================================================================
    # Intel v2 Schemas — Ontology-backed entities, incidents, markets, graphs
    # =========================================================================

    IncidentListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicIncident"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    IncidentDetail:
      type: object
      required: [incident]
      properties:
        incident:
          $ref: "#/components/schemas/PublicIncident"
        observations:
          type: array
          items:
            $ref: "#/components/schemas/PublicObservation"
        markets:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarket"
        entities:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
        primaryPlace:
          oneOf:
            - $ref: "#/components/schemas/PublicPlace"
            - type: "null"
          description: Primary place for the incident (or null)
        relatedIncidents:
          type: array
          items:
            $ref: "#/components/schemas/PublicIncident"

    IncidentDetailResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/IncidentDetail"

    EntityListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    EntityDetail:
      type: object
      required: [entity]
      properties:
        entity:
          $ref: "#/components/schemas/PublicEntity"
        fromRelations:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntityRelation"
        toRelations:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntityRelation"
        relatedEntities:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
        incidents:
          type: array
          items:
            $ref: "#/components/schemas/PublicIncident"
        markets:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarket"
        observations:
          type: array
          items:
            $ref: "#/components/schemas/PublicObservation"

    EntityDetailResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/EntityDetail"

    IntelGraph:
      type: object
      required: [nodes, edges]
      properties:
        nodes:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
        edges:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntityRelation"
        capped:
          type: boolean
          description: True when traversal stopped early (200-node cap or depth cap)
        depth:
          type: integer
          description: BFS depth traversed

    EntityGraphResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/IntelGraph"
        meta:
          $ref: "#/components/schemas/PublicGraphMeta"

    CyberGraphResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/IntelGraph"
        meta:
          $ref: "#/components/schemas/PublicGraphMeta"

    MarketListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarket"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    CVEListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
          description: Entities filtered to `kind=cve`
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    ThreatActorListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicEntity"
          description: Entities filtered to `kind=threat_actor`
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    FinintIntelListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicFinintObservation"
        meta:
          $ref: "#/components/schemas/PublicListMeta"
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    SignalListResponse:
      type: object
      required: [data, meta, links]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicSignal"
        meta:
          allOf:
            - $ref: "#/components/schemas/PublicListMeta"
            - type: object
              required: [filters]
              properties:
                filters:
                  type: object
                  additionalProperties:
                    type: [string, integer, number, boolean, "null"]
        links:
          $ref: "#/components/schemas/PublicCollectionLinks"

    PublicCollectionLinks:
      type: object
      required: [self, next]
      additionalProperties: false
      properties:
        self:
          type: string
        next:
          type: [string, "null"]

    PublicChangeEvent:
      type: object
      required: [id, type, changeType, resource, changedAt, tombstone, schemaVersion]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^chg_[a-f0-9]{24}$"
        type:
          type: string
          const: change
        changeType:
          type: string
          enum: [created, updated, deleted]
        resource:
          type: object
          required: [type, id]
          additionalProperties: false
          properties:
            type:
              type: string
            id:
              type: string
              description: Stable public resource ID; never a Convex document ID.
        changedAt:
          type: string
          format: date-time
        tombstone:
          type: boolean
          description: True when the referenced resource has been deleted.
        schemaVersion:
          type: string
          const: "1.0"

    ChangeFeedMeta:
      allOf:
        - $ref: "#/components/schemas/PublicListMeta"
        - type: object
          required: [watermark, fromExclusive]
          properties:
            watermark:
              type: [string, "null"]
              description: Exclusive checkpoint after the final event in this response.
            fromExclusive:
              type: boolean
              const: true

    ChangeFeedLinks:
      type: object
      required: [self, next, resume]
      additionalProperties: false
      properties:
        self: { type: string }
        next: { type: [string, "null"] }
        resume:
          type: [string, "null"]
          description: URL containing the checkpoint to persist even when the feed is caught up.

    ChangeFeedResponse:
      type: object
      required: [data, meta, links]
      additionalProperties: false
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicChangeEvent"
        meta:
          $ref: "#/components/schemas/ChangeFeedMeta"
        links:
          $ref: "#/components/schemas/ChangeFeedLinks"

    PublicResourceLinks:
      type: object
      additionalProperties:
        type: [string, "null"]

    PublicPageMeta:
      type: object
      required: [limit, nextCursor, hasMore]
      additionalProperties: false
      properties:
        limit:
          type: integer
          minimum: 1
        nextCursor:
          type: [string, "null"]
        hasMore:
          type: boolean

    PublicListMeta:
      type: object
      required: [count, pageCount, limit, isDone, hasMore, nextCursor, asOf, page]
      properties:
        count:
          type: integer
          minimum: 0
          deprecated: true
        pageCount:
          type: integer
          minimum: 0
        limit:
          type: integer
          minimum: 1
        isDone:
          type: boolean
        hasMore:
          type: boolean
        nextCursor:
          type: [string, "null"]
        asOf:
          type: string
          format: date-time
        page:
          $ref: "#/components/schemas/PublicPageMeta"

    GeoJSONPoint:
      type: [object, "null"]
      description: WGS 84 point with longitude first, then latitude.
      required: [type, coordinates]
      additionalProperties: false
      properties:
        type:
          type: string
          const: Point
        coordinates:
          type: array
          minItems: 2
          maxItems: 2
          prefixItems:
            - type: number
              minimum: -180
              maximum: 180
            - type: number
              minimum: -90
              maximum: 90

    GeoJSONFeatureCollection:
      type: object
      required: [type, features, numberReturned, timeStamp, links]
      additionalProperties: false
      properties:
        type:
          type: string
          const: FeatureCollection
        features:
          type: array
          items:
            type: object
            required: [type, geometry, properties]
            properties:
              type: { type: string, const: Feature }
              id: { type: string }
              geometry:
                anyOf:
                  - $ref: "#/components/schemas/GeoJSONPoint"
                  - type: object
                    required: [type, coordinates]
                    properties:
                      type: { type: string, const: Polygon }
                      coordinates:
                        type: array
                        items:
                          type: array
                          items:
                            type: array
                            minItems: 2
                            maxItems: 2
                            prefixItems:
                              - { type: number, minimum: -180, maximum: 180 }
                              - { type: number, minimum: -90, maximum: 90 }
                  - type: "null"
              properties:
                type: object
        numberReturned: { type: integer, minimum: 0 }
        timeStamp: { type: string, format: date-time }
        links:
          type: array
          items:
            type: object
            required: [rel, href]
            properties:
              rel: { type: string, enum: [self, next] }
              href: { type: string }

    PublicEntity:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^ent_[a-f0-9]{24}$"
        type:
          type: string
          const: entity
        kind:
          $ref: "#/components/schemas/EntityKind"
        name:
          type: string
        aliases:
          type: array
          items:
            type: string
        externalIds:
          type: object
          additionalProperties:
            type: [string, number, boolean, "null"]
        attributes:
          type: object
          properties:
            isKnownExploitedVulnerability:
              type: boolean
            severity:
              type: string
            cvssScore:
              type: number
            publishedAt:
              type: string
              format: date-time
          additionalProperties: false
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        links:
          $ref: "#/components/schemas/PublicResourceLinks"

    PublicEntityRelation:
      type: object
      required: [id, type, from, to, confidence, validFrom, validTo]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^rel_[a-f0-9]{24}$"
        type:
          type: string
        from:
          type: string
          pattern: "^ent_[a-f0-9]{24}$"
        to:
          type: string
          pattern: "^ent_[a-f0-9]{24}$"
        confidence:
          type: number
          minimum: 0
          maximum: 1
        validFrom:
          type: [string, "null"]
          format: date-time
        validTo:
          type: [string, "null"]
          format: date-time

    PublicIncidentQuality:
      type: object
      required: [observationCount, primarySourceCount, corroborationScore]
      additionalProperties: false
      properties:
        observationCount:
          type: integer
          minimum: 0
        primarySourceCount:
          type: integer
          minimum: 0
        corroborationScore:
          type: number
          minimum: 0
          maximum: 1

    PublicIncident:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^inc_[a-f0-9]{24}$"
        type:
          type: string
          const: incident
        title:
          type: string
        category:
          type: string
        severity:
          type: string
          enum: [low, medium, high, critical]
        status:
          type: string
          enum: [active, resolved, forecast]
        occurredAt:
          type: string
          format: date-time
        endedAt:
          type: [string, "null"]
          format: date-time
        firstObservedAt:
          type: string
          format: date-time
          description: Earliest time Y2 observed the incident, independent of when it occurred.
        lastObservedAt:
          type: string
          format: date-time
          description: Latest time Y2 observed or refreshed the incident, independent of when it occurred.
        resolutionReason:
          type: [string, "null"]
        quality:
          $ref: "#/components/schemas/PublicIncidentQuality"
        links:
          $ref: "#/components/schemas/PublicResourceLinks"

    PublicObservationSource:
      type: object
      required: [type, recordId]
      additionalProperties: false
      properties:
        type:
          type: string
        recordId:
          type: string

    PublicObservationLocation:
      type: object
      required: [name, countryCode, region]
      additionalProperties: false
      properties:
        name:
          type: [string, "null"]
        countryCode:
          type: [string, "null"]
        region:
          type: [string, "null"]

    PublicProvenance:
      type: object
      required: [tier, method]
      additionalProperties: false
      properties:
        tier:
          type: string
          enum: [primary, derived, synthesized]
        method:
          type: string
          enum: [direct_ingest, model_extraction]

    PublicObservation:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^obs_[a-f0-9]{24}$"
        type:
          type: string
          const: observation
        source:
          $ref: "#/components/schemas/PublicObservationSource"
        title:
          type: string
        description:
          type: [string, "null"]
        category:
          type: string
        severity:
          type: string
        occurredAt:
          type: string
          format: date-time
        observedAt:
          type: string
          format: date-time
        geometry:
          $ref: "#/components/schemas/GeoJSONPoint"
        location:
          $ref: "#/components/schemas/PublicObservationLocation"
        provenance:
          $ref: "#/components/schemas/PublicProvenance"
        links:
          $ref: "#/components/schemas/PublicResourceLinks"

    PublicPlace:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^plc_[a-f0-9]{24}$"
        type:
          type: string
          const: place
        kind:
          type: string
        name:
          type: string
        countryCode:
          type: [string, "null"]
        admin1:
          type: [string, "null"]
        geometry:
          $ref: "#/components/schemas/GeoJSONPoint"
        bbox:
          type: [array, "null"]
          minItems: 4
          maxItems: 4
          items:
            type: number
        h3Index:
          type: [string, "null"]

    PublicMoney:
      type: [object, "null"]
      required: [value, currency, basis]
      additionalProperties: false
      properties:
        value:
          type: number
        currency:
          type: string
        basis:
          type: string

    PublicProbability:
      type: [object, "null"]
      required: [value, scale]
      additionalProperties: false
      properties:
        value:
          type: number
          minimum: 0
          maximum: 1
        scale:
          type: string
          const: 0_to_1

    PublicMarket:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^mkt_[a-f0-9]{24}$"
        type:
          type: string
          const: market
        source:
          type: string
        question:
          type: string
        category:
          type: string
        status:
          type: string
        resolvesAt:
          type: [string, "null"]
          format: date-time
        probability:
          $ref: "#/components/schemas/PublicProbability"
        volume:
          $ref: "#/components/schemas/PublicMoney"
        liquidity:
          $ref: "#/components/schemas/PublicMoney"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        links:
          $ref: "#/components/schemas/PublicResourceLinks"

    PublicFinintObservation:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^fin_[a-f0-9]{24}$"
        type:
          type: string
          const: financial_observation
        indicator:
          type: object
          required: [id, label, category]
          additionalProperties: false
          properties:
            id:
              type: string
            label:
              type: string
            category:
              type: string
        source:
          type: object
          required: [type]
          additionalProperties: false
          properties:
            type:
              type: string
        dimension:
          type: [object, "null"]
          required: [key, type]
          additionalProperties: false
          properties:
            key:
              type: string
            type:
              type: string
        measurement:
          type: object
          required: [value, unit, previousValue, change, changePercent]
          additionalProperties: false
          properties:
            value:
              type: number
            unit:
              type: string
            previousValue:
              type: [number, "null"]
            change:
              type: [number, "null"]
            changePercent:
              type: [number, "null"]
        observedAt:
          type: string
          format: date-time
        geometry:
          $ref: "#/components/schemas/GeoJSONPoint"
        signals:
          type: array
          items:
            type: string

    PublicGraphMeta:
      type: object
      required: [depth, capped]
      additionalProperties: false
      properties:
        depth:
          type: integer
          minimum: 0
        capped:
          type: boolean

    PublicResourceReference:
      type: object
      required: [id]
      additionalProperties: false
      properties:
        id:
          type: string

    PublicSignal:
      type: object
      required: [id, type]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^sig_[a-f0-9]{24}$"
        type:
          type: string
          const: signal
        visibility:
          type: string
        reportId:
          type: string
          pattern: "^rpt_[a-f0-9]{24}$"
        profileId:
          type: string
          pattern: "^prf_[a-f0-9]{24}$"
        profileName:
          type: [string, "null"]
        profileTopic:
          type: [string, "null"]
        reportTopic:
          type: [string, "null"]
        occurredAt:
          type: string
          format: date-time
        reportGeneratedAt:
          type: string
          format: date-time
        domain:
          type: string
        actionType:
          type: string
        priority:
          type: string
        confidence:
          type: number
          minimum: 0
          maximum: 1
        inferenceType:
          type: string
        timeHorizon:
          type: string
        title:
          type: string
        signal:
          type: string
        decision:
          type: string
        justification:
          type: string
        summary:
          type: [string, "null"]
        subjects:
          type: array
          items:
            type: object
            required: [kind, label, key, entityId]
            additionalProperties: false
            properties:
              kind:
                type: string
              label:
                type: string
              key:
                type: string
              entityId:
                type: [string, "null"]
        tags:
          type: array
          items:
            type: string
        sources:
          type: array
          items:
            type: object
            required: [id, url]
            additionalProperties: false
            properties:
              id:
                type: string
                pattern: "^src_[a-f0-9]{24}$"
              url:
                type: string
                format: uri
        geometry:
          $ref: "#/components/schemas/GeoJSONPoint"
        location:
          $ref: "#/components/schemas/PublicObservationLocation"
        relationships:
          type: object
          required: [observations, incidents, places, entities]
          additionalProperties: false
          properties:
            observations:
              type: array
              items:
                $ref: "#/components/schemas/PublicResourceReference"
            incidents:
              type: array
              items:
                $ref: "#/components/schemas/PublicResourceReference"
            places:
              type: array
              items:
                $ref: "#/components/schemas/PublicResourceReference"
            entities:
              type: array
              items:
                $ref: "#/components/schemas/PublicResourceReference"
