openapi: 3.1.0
info:
  title: Open District API
  version: 1.0.0
  description: |
    Open District connects public data, enterprise systems, and personal context
    in one secure platform. Every fact has a source. Every answer has citations.

    ## Surfaces

    - **REST** — Health check at `https://app.opendistrict.org/api/health`
    - **MCP** — Streamable HTTP at `https://mcp.opendistrict.org/mcp` (JSON-RPC 2.0)

    ## Authentication

    REST health is public. All MCP tools require a Bearer token issued via
    scoped API key at [app.opendistrict.org](https://app.opendistrict.org).
    Scopes: `admin`, `api_keys`, `read`.

    ## Machine-readable agent instructions

    See [/agents.md](/agents.md) for LLM-native integration guide.
  contact:
    name: Open District
    email: hello@opendistrict.org
    url: https://opendistrict.org
  license:
    name: Proprietary
    url: https://app.opendistrict.org/legal/terms

servers:
  - url: https://app.opendistrict.org
    description: Application server (REST)
  - url: https://mcp.opendistrict.org
    description: MCP server (Streamable HTTP / JSON-RPC)

tags:
  - name: REST
    description: Public REST endpoints
  - name: MCP
    description: Model Context Protocol tools via JSON-RPC 2.0

paths:
  /api/health:
    get:
      tags: [REST]
      summary: Service health check
      description: Returns component-level health status. No authentication required.
      operationId: getHealth
      responses:
        "200":
          description: Health status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
                checks:
                  database: { status: ok, latency_ms: 4 }
                  search: { status: ok, latency_ms: 12 }
                  event_store: { status: ok, latency_ms: 6 }
                version: "1.0.0"
                uptime_s: 84213
        "503":
          description: Service degraded or down
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /mcp:
    post:
      tags: [MCP]
      summary: MCP Streamable HTTP endpoint
      description: |
        JSON-RPC 2.0 endpoint implementing the Model Context Protocol.
        Supports `tools/list` and `tools/call` methods.

        All requests require `Authorization: Bearer <token>` header.

        ## Available Tools

        | # | Tool | Description |
        |---|------|-------------|
        | 1 | `unified_search` | Semantic + lexical search across indexed data |
        | 2 | `query` | Federated SQL with read-only gate |
        | 3 | `entities_lookup` | Entity graph lookup with cited paths |
        | 4 | `entity_path` | Trace entity relationships with provenance |
        | 5 | `sources_list` | List/manage source connector configurations |
        | 6 | `pipelines_manage` | Manage scheduled DAG jobs |
        | 7 | `events_query` | Query append-only event store |
        | 8 | `audit_query` | Query audit log entries |
        | 9 | `health_check` | Service health check |
        | 10 | `schema_introspect` | Introspect available schemas and types |

        See individual tool schemas below for parameters and return types.
      operationId: mcpEndpoint
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JsonRpcRequest"
            examples:
              toolsList:
                summary: List available tools
                value:
                  jsonrpc: "2.0"
                  id: 1
                  method: tools/list
              toolsCallSearch:
                summary: Call search
                value:
                  jsonrpc: "2.0"
                  id: 2
                  method: tools/call
                  params:
                    name: search
                    arguments:
                      q: "zoning permits downtown"
                      limit: 10
      responses:
        "200":
          description: JSON-RPC response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
              examples:
                toolsListResponse:
                  summary: Tools list response
                  value:
                    jsonrpc: "2.0"
                    id: 1
                    result:
                      tools:
                        - name: search
                          description: Semantic + lexical search across indexed data
                          inputSchema:
                            type: object
                            properties:
                              q: { type: string }
                            required: [q]
                toolsCallResponse:
                  summary: Tool call response
                  value:
                    jsonrpc: "2.0"
                    id: 2
                    result:
                      content:
                        - type: text
                          text: '{"results":[...],"total":42}'
        "401":
          description: Missing or invalid Bearer token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcError"
        "400":
          description: Invalid JSON-RPC request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcError"

x-mcp-tools:
  - name: query
    description: |
      Federated SQL query with read-only gate. Executes across connected sources
      without write side-effects. Results include source attribution.
    parameters:
      type: object
      properties:
        sql:
          type: string
          description: Read-only SQL statement
        params:
          type: object
          additionalProperties: true
          description: Parameterized query bindings
      required: [sql]
    returns:
      type: object
      properties:
        rows:
          type: array
          items: { type: object }
        columns:
          type: array
          items: { type: string }
        row_count: { type: integer }
        sources:
          type: array
          items: { type: string }
        duration_ms: { type: number }

  - name: search
    description: |
      Semantic + lexical search across indexed data. Hybrid retrieval using
      Qdrant (1024-dim embeddings) with lexical fallback. Returns ranked results
      with source citations.
    parameters:
      type: object
      properties:
        q:
          type: string
          description: Search query (natural language or keywords)
        filters:
          type: object
          description: Optional filters
          properties:
            source:
              type: string
              description: Filter by source connector name
            entity_type:
              type: string
              description: Filter by entity type
            date_from:
              type: string
              format: date
              description: Start date filter (ISO 8601)
            date_to:
              type: string
              format: date
              description: End date filter (ISO 8601)
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
          description: Maximum number of results
      required: [q]
    returns:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              score: { type: number }
              content: { type: string }
              source: { type: string }
              entity_type: { type: string }
              citations:
                type: array
                items: { type: string }
        total: { type: integer }

  - name: schema
    description: |
      Introspect available schemas, types, and table structures across
      connected sources. Useful for discovering queryable surfaces.
    parameters:
      type: object
      properties:
        source:
          type: string
          description: Scope to specific source connector
        table:
          type: string
          description: Scope to specific table
    returns:
      type: object
      properties:
        schemas:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              source: { type: string }
              tables:
                type: array
                items: { type: string }
        tables:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              columns:
                type: array
                items:
                  type: object
                  properties:
                    name: { type: string }
                    type: { type: string }
                    nullable: { type: boolean }
        columns:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              type: { type: string }
              nullable: { type: boolean }
              description: { type: string }

  - name: catalog
    description: |
      Browse and search the connector catalog. Lists available source
      templates, their types, and sync status.
    parameters:
      type: object
      properties:
        type:
          type: string
          description: Filter by connector type (rest, oauth, webhook, db)
        category:
          type: string
          description: Filter by category (public, enterprise, personal)
        search:
          type: string
          description: Search connector names and descriptions
    returns:
      type: object
      properties:
        connectors:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name: { type: string }
              type: { type: string }
              category: { type: string }
              status: { type: string }
        count: { type: integer }

  - name: topology
    description: |
      Inspect the entity graph topology. Returns node/edge counts,
      entity types, and relationship summaries.
    parameters:
      type: object
      properties:
        entity_type:
          type: string
          description: Scope to specific entity type
        depth:
          type: integer
          minimum: 1
          maximum: 5
          default: 1
          description: Traversal depth for relationship summary
    returns:
      type: object
      properties:
        nodes: { type: integer }
        edges: { type: integer }
        types:
          type: array
          items: { type: string }
        relationships:
          type: array
          items:
            type: object
            properties:
              from_type: { type: string }
              to_type: { type: string }
              relation: { type: string }
              count: { type: integer }

  - name: metrics
    description: |
      Query platform usage and data freshness metrics. Tenant-scoped
      statistics on queries, syncs, and coverage.
    parameters:
      type: object
      properties:
        scope:
          type: string
          enum: [tenant, system]
          description: tenant (default) or system
        period:
          type: string
          description: Time period (e.g. 7d, 30d, default 7d)
    returns:
      type: object
      properties:
        queries_total: { type: integer }
        syncs_total: { type: integer }
        sources_active: { type: integer }
        data_freshness:
          type: object
          additionalProperties: { type: string }

  - name: entities
    description: |
      Entity graph lookup with cited paths. Returns entities and their
      provenance chains from the knowledge graph.
    parameters:
      type: object
      properties:
        id:
          type: string
          description: Specific entity ID
        type:
          type: string
          description: Entity type filter
        name:
          type: string
          description: Fuzzy name match
        depth:
          type: integer
          minimum: 0
          maximum: 5
          default: 1
          description: Graph traversal depth
    returns:
      type: object
      properties:
        entities:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              type: { type: string }
              name: { type: string }
              properties: { type: object }
        edges:
          type: array
          items:
            type: object
            properties:
              from: { type: string }
              to: { type: string }
              relation: { type: string }
        citations:
          type: array
          items:
            type: object
            properties:
              source: { type: string }
              path: { type: string }
              timestamp: { type: string, format: date-time }

  - name: pipelines
    description: |
      Manage scheduled DAG jobs. Inspect pipeline definitions, trigger
      runs, and check execution status.
    parameters:
      type: object
      properties:
        action:
          type: string
          enum: [list, get, trigger]
          description: Operation to perform
        id:
          type: string
          description: Pipeline ID (required for get/trigger)
      required: [action]
    returns:
      type: object
      properties:
        pipelines:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name: { type: string }
              schedule: { type: string }
              status: { type: string }
              last_run: { type: string, format: date-time }
        run:
          type: object
          properties:
            id: { type: string }
            status: { type: string }
            started_at: { type: string, format: date-time }
            completed_at: { type: string, format: date-time }
        triggered: { type: boolean }

  - name: events
    description: |
      Query append-only event store. Provides temporal provenance on all
      facts with cursor-based pagination.
    parameters:
      type: object
      properties:
        filter:
          type: object
          properties:
            source: { type: string, description: Source connector name }
            entity_id: { type: string, description: Entity ID }
            event_type: { type: string, description: Event type }
            since: { type: string, format: date-time, description: Start time }
            until: { type: string, format: date-time, description: End time }
        cursor:
          type: string
          description: Pagination cursor from previous response
        limit:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
          description: Maximum events per page
    returns:
      type: object
      properties:
        events:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              type: { type: string }
              source: { type: string }
              entity_id: { type: string }
              payload: { type: object }
              timestamp: { type: string, format: date-time }
        next_cursor: { type: string }

  - name: audit
    description: |
      Query audit log entries. Every query and action is recorded with
      actor, resource, and timestamp. Cursor-based pagination.
    parameters:
      type: object
      properties:
        filter:
          type: object
          properties:
            actor: { type: string, description: Actor identifier }
            action: { type: string, description: Action type }
            resource: { type: string, description: Resource identifier }
            since: { type: string, format: date-time, description: Start time }
            until: { type: string, format: date-time, description: End time }
        cursor:
          type: string
          description: Pagination cursor
        limit:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
          description: Maximum entries per page
    returns:
      type: object
      properties:
        entries:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              actor: { type: string }
              action: { type: string }
              resource: { type: string }
              details: { type: object }
              timestamp: { type: string, format: date-time }
        next_cursor: { type: string }



components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: Scoped API key issued at app.opendistrict.org

  schemas:
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded, down]
        checks:
          type: object
          additionalProperties:
            type: object
            properties:
              status: { type: string }
              latency_ms: { type: number }
        version: { type: string }
        uptime_s: { type: number }
      required: [status, checks]

    JsonRpcRequest:
      type: object
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          oneOf:
            - type: integer
            - type: string
        method:
          type: string
          enum: [tools/list, tools/call]
        params:
          type: object
          properties:
            name:
              type: string
              description: Tool name (required for tools/call)
            arguments:
              type: object
              description: Tool arguments (required for tools/call)
      required: [jsonrpc, id, method]

    JsonRpcResponse:
      type: object
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          oneOf:
            - type: integer
            - type: string
        result:
          type: object
          description: Present on success
        error:
          $ref: "#/components/schemas/JsonRpcErrorObject"
      required: [jsonrpc, id]

    JsonRpcError:
      type: object
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          oneOf:
            - type: integer
            - type: string
        error:
          $ref: "#/components/schemas/JsonRpcErrorObject"
      required: [jsonrpc, id, error]

    JsonRpcErrorObject:
      type: object
      properties:
        code:
          type: integer
          description: JSON-RPC error code (-32600 to -32603, or custom)
        message:
          type: string
        data:
          description: Additional error data
      required: [code, message]
