openapi: 3.1.0
info:
  title: JuriStack API
  version: 1.0.0
  description: |
    The jurisdiction layer for builders. JuriStack provides machine-readable
    building code adoption data by jurisdiction. Currently covers all 110
    Nebraska jurisdictions (94 counties + 16 cities).

    **Data disclaimer:** Records reflect known code adoptions with confidence
    ratings. Always verify with local authority before construction.

    ## Response Format

    All endpoints except `/v1/health` return a standard envelope:

    ```json
    {
      "data": { ... },
      "meta": { "state": "NE", "total": 110, "page": 1, "limit": 20, "pages": 6 },
      "links": { "self": "/v1/jurisdictions?limit=20" }
    }
    ```

    Errors return `{ "error": "message", "code": 400, "message": "..." }`.
  contact:
    name: JuriStack
    url: https://juristack.io
  license:
    name: Commercial
    url: https://juristack.io/terms

servers:
  - url: http://178.156.211.155/api
    description: Production (Nebraska)

tags:
  - name: System
    description: Health and operational statistics
  - name: Jurisdictions
    description: Browse and look up jurisdictions by ID or filter
  - name: Search
    description: Full-text search across code adoptions and amendments

paths:

  /v1/health:
    get:
      operationId: getHealth
      summary: Health check
      description: |
        Returns the operational status of the API server. This endpoint does
        **not** use the standard data envelope.
      tags: [System]
      responses:
        "200":
          description: API is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
                version: "1.0.0"
                db: connected
                jurisdictions: 221
                timestamp: "2026-02-28T06:31:32.358Z"

  /v1/stats:
    get:
      operationId: getStats
      summary: Data coverage statistics
      description: |
        Returns aggregate statistics about the JuriStack database — total
        jurisdictions, code adoption counts, confidence distribution, and a
        breakdown by code type and version.
      tags: [System]
      responses:
        "200":
          description: Current database statistics
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Envelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/StatsData"
              example:
                data:
                  totalJurisdictions: 221
                  totalCodes: 1628
                  totalAmendments: 18
                  conflicts: 3
                  highConfidenceCodes: 143
                  coveragePercent: 19.5
                  codeTypes:
                    - code_type: IRC
                      count: 221
                    - code_type: IBC
                      count: 221
                  byVersion:
                    - version: "2018"
                      count: 1200
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/jurisdictions:
    get:
      operationId: listJurisdictions
      summary: List jurisdictions
      description: |
        Returns a paginated list of all jurisdictions. Use `q` to filter by
        name, `type` to narrow to counties or cities, and `state` to scope
        to a specific state (currently `NE` and `IA` are populated).
      tags: [Jurisdictions]
      parameters:
        - name: state
          in: query
          description: ISO 3166-2 state code (e.g. `NE`)
          schema:
            type: string
            example: NE
        - name: type
          in: query
          description: Jurisdiction type filter
          schema:
            type: string
            enum: [county, city]
        - name: q
          in: query
          description: Free-text name search (case-insensitive substring match)
          schema:
            type: string
            example: douglas
        - name: limit
          in: query
          description: Number of results per page (max 100)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          description: Zero-based record offset for pagination
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Paginated list of jurisdictions
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Envelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          jurisdictions:
                            type: array
                            items:
                              $ref: "#/components/schemas/JurisdictionSummary"
                        required: [jurisdictions]
              example:
                data:
                  jurisdictions:
                    - id: ne_omaha_douglas
                      name: Omaha
                      type: city
                      county: Douglas
                      population: 486051
                meta:
                  state: NE
                  total: 110
                  page: 1
                  limit: 20
                  pages: 6
                links:
                  self: "/v1/jurisdictions?limit=20"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/jurisdictions/{id}:
    get:
      operationId: getJurisdiction
      summary: Get jurisdiction by ID
      description: |
        Returns full detail for a single jurisdiction including all code
        adoptions, local amendments, and source URLs.

        **ID format:** Jurisdiction IDs take the form `{state}_{slug}` or
        `{state}-county-{name}-county`. Use the list endpoint to discover IDs.
        Example: `ne_omaha_douglas`, `ne_adams_adams`.
      tags: [Jurisdictions]
      parameters:
        - name: id
          in: path
          required: true
          description: Jurisdiction identifier (e.g. `ne_omaha_douglas`)
          schema:
            type: string
            example: ne_omaha_douglas
      responses:
        "200":
          description: Full jurisdiction record
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Envelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/JurisdictionDetail"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/jurisdictions/{id}/codes:
    get:
      operationId: getJurisdictionCodes
      summary: Get code adoptions for a jurisdiction
      description: |
        Returns only the code adoptions for a jurisdiction. Useful when you
        already have jurisdiction metadata and just need the codes list.
      tags: [Jurisdictions]
      parameters:
        - name: id
          in: path
          required: true
          description: Jurisdiction identifier
          schema:
            type: string
            example: ne_omaha_douglas
      responses:
        "200":
          description: Code adoptions for the jurisdiction
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Envelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          codes:
                            type: array
                            items:
                              $ref: "#/components/schemas/CodeAdoption"
                        required: [codes]
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/search:
    get:
      operationId: searchCodes
      summary: Full-text search
      description: |
        Searches across code types, versions, amendments, and notes using
        SQLite FTS5. Returns jurisdictions grouped by relevance.

        Useful for queries like `frost depth`, `sprinkler`, or `IBC 2021`.
        FTS5 operators (`AND`, `OR`, `NOT`, phrase `"..."`) are supported.
      tags: [Search]
      parameters:
        - name: q
          in: query
          required: true
          description: Full-text search query (FTS5 syntax supported)
          schema:
            type: string
            example: frost depth
        - name: limit
          in: query
          description: Maximum number of matching rows (pre-grouping)
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Grouped search results
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Envelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SearchData"
              example:
                data:
                  query: frost depth
                  results:
                    - jurisdiction_id: ne_omaha_douglas
                      jurisdiction_name: Omaha
                      matches:
                        - code_type: IBC
                          version: "2018"
                          amendments: "Frost depth 42 inches | Modified sprinkler requirements"
                          notes: "IBC 2018 confirmed from Omaha municipal code Ch.49"
                meta:
                  state: NE
                  total: 1
                  page: 1
                  limit: 50
                links:
                  self: "/v1/search?q=frost+depth"
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  schemas:

    # ── Envelope (shared wrapper) ─────────────────────────────────────────────

    Envelope:
      type: object
      description: |
        Standard response wrapper used by all endpoints except `/v1/health`.
      properties:
        data:
          description: The primary response payload
        meta:
          $ref: "#/components/schemas/ResponseMeta"
        links:
          $ref: "#/components/schemas/ResponseLinks"
      required: [data]

    ResponseMeta:
      type: object
      description: Pagination and context metadata.
      properties:
        state:
          type: string
          description: State scope of the response (e.g. `NE`)
          example: NE
        total:
          type: integer
          description: Total records matching the query
          example: 110
        page:
          type: integer
          description: Current page number (1-indexed)
          example: 1
        limit:
          type: integer
          description: Records per page
          example: 20
        pages:
          type: integer
          description: Total number of pages
          example: 6

    ResponseLinks:
      type: object
      description: HATEOAS-style navigation links.
      properties:
        self:
          type: string
          description: The canonical URL for this response
          example: "/v1/jurisdictions?limit=20"
        next:
          type: string
          description: URL for the next page (omitted on last page)
          example: "/v1/jurisdictions?limit=20&offset=20"
        prev:
          type: string
          description: URL for the previous page (omitted on first page)
          example: "/v1/jurisdictions?limit=20&offset=0"

    # ── Core objects ──────────────────────────────────────────────────────────

    CodeAdoption:
      type: object
      description: A single building code adopted (or inherited) by a jurisdiction.
      properties:
        type:
          type: string
          description: |
            Code family abbreviation. Common values: `IBC`, `IRC`, `IECC`,
            `IEBC`, `NEC`, `UPC`, `RRNC`, `IFC`, `IMC`, `IPC`.
          example: IRC
        version:
          type: string
          description: Edition year or identifier (e.g. `2018`, `2021`, `NE 71-1575`)
          example: "2018"
        confidence:
          type: string
          description: |
            Data confidence level:
            - `high` / `verified` — confirmed via primary source (ordinance, official website)
            - `medium` — inferred from state baseline or secondary source
            - `low` — unconfirmed, needs verification
          enum: [high, verified, medium, low]
          example: high
        ordinance:
          type:
            - string
            - "null"
          description: Local ordinance number, if known
          example: null
        adopted_date:
          type:
            - string
            - "null"
          format: date
          description: Date the code was formally adopted (ISO 8601)
          example: "2021-08-28"
        effective_date:
          type:
            - string
            - "null"
          format: date
          description: Date the code became effective (ISO 8601)
          example: null
        source:
          type:
            - string
            - "null"
          description: |
            Source URL(s). Multiple sources are pipe-separated
            (`url1 | url2`).
          example: "https://library.municode.com/ne/omaha/codes/code_of_ordinances"
        notes:
          type:
            - string
            - "null"
          description: Human-readable notes (internal flags stripped)
          example: "IBC 2018 confirmed from Omaha municipal code Ch.49"
      required: [type, version, confidence]

    Source:
      type: object
      description: A source document or URL associated with a jurisdiction's code record.
      properties:
        url:
          type: string
          format: uri
          description: Link to the source document
          example: "https://library.municode.com/ne/omaha/codes/code_of_ordinances"
        status:
          type: string
          description: Availability status of this source
          enum: [active, stale, unreachable, unknown]
          example: active
      required: [url, status]

    JurisdictionSummary:
      type: object
      description: Lightweight jurisdiction record used in list views.
      properties:
        id:
          type: string
          description: |
            Stable jurisdiction identifier. Format varies:
            `{state}_{slug}` (e.g. `ne_omaha_douglas`) or
            `{state}-county-{name}-county` (e.g. `ne-county-adams-county`).
          example: ne_omaha_douglas
        name:
          type: string
          description: Display name of the jurisdiction
          example: Omaha
        type:
          type: string
          enum: [county, city]
          example: city
        county:
          type:
            - string
            - "null"
          description: County name (same as `name` for county jurisdictions)
          example: Douglas
        population:
          type:
            - integer
            - "null"
          description: Population estimate (may be null for smaller jurisdictions)
          example: 486051
      required: [id, name, type]

    JurisdictionDetail:
      type: object
      description: Full jurisdiction record including code adoptions, amendments, and sources.
      properties:
        id:
          type: string
          example: ne_omaha_douglas
        jurisdiction:
          type: string
          description: Display name
          example: Omaha
        type:
          type: string
          enum: [county, city]
          example: city
        county:
          type:
            - string
            - "null"
          example: Douglas
        state:
          type: string
          description: Two-letter state abbreviation
          example: NE
        population:
          type:
            - integer
            - "null"
          example: 486051
        codes:
          type: array
          description: All known code adoptions for this jurisdiction
          items:
            $ref: "#/components/schemas/CodeAdoption"
        amendments:
          type: array
          description: Local amendments on top of adopted codes
          items:
            type: string
          example: ["Frost depth 42 inches"]
        coverage:
          type: string
          description: |
            Completeness estimate based on number of code types recorded:
            - `complete` — 4 or more code types
            - `partial` — 2-3 code types
            - `minimal` — fewer than 2 code types
          enum: [complete, partial, minimal]
          example: complete
        sources:
          type: array
          description: Source documents used to build this record
          items:
            $ref: "#/components/schemas/Source"
      required: [id, jurisdiction, type, state, codes, amendments, coverage]

    # ── Stats ─────────────────────────────────────────────────────────────────

    StatsData:
      type: object
      description: Aggregate statistics about the JuriStack database.
      properties:
        totalJurisdictions:
          type: integer
          description: Total number of jurisdictions in the database
          example: 221
        totalCodes:
          type: integer
          description: Total number of individual code adoption records
          example: 1628
        totalAmendments:
          type: integer
          description: Total number of local amendment records
          example: 18
        conflicts:
          type: integer
          description: Number of code records flagged with conflicting data
          example: 3
        highConfidenceCodes:
          type: integer
          description: Number of code records with high or verified confidence
          example: 143
        coveragePercent:
          type: number
          format: float
          description: Percentage of codes with high/verified confidence
          example: 19.5
        codeTypes:
          type: array
          description: Breakdown of records by code type, sorted by frequency
          items:
            type: object
            properties:
              code_type:
                type: string
                example: IRC
              count:
                type: integer
                example: 221
            required: [code_type, count]
        byVersion:
          type: array
          description: Breakdown of records by edition year, sorted by frequency
          items:
            type: object
            properties:
              version:
                type: string
                example: "2018"
              count:
                type: integer
                example: 1200
            required: [version, count]
      required:
        [totalJurisdictions, totalCodes, totalAmendments, codeTypes, byVersion]

    # ── Search ────────────────────────────────────────────────────────────────

    SearchMatch:
      type: object
      description: A single matching code record within a search result.
      properties:
        code_type:
          type: string
          example: IBC
        version:
          type: string
          example: "2018"
        amendments:
          type:
            - string
            - "null"
          description: Amendment text that matched the query (pipe-separated if multiple)
          example: "Frost depth 42 inches | Modified sprinkler requirements"
        notes:
          type:
            - string
            - "null"
          description: Notes that matched the query (internal flags stripped)
          example: "IBC 2018 confirmed from Omaha municipal code Ch.49"
      required: [code_type, version]

    SearchResult:
      type: object
      description: A jurisdiction with one or more matching code records.
      properties:
        jurisdiction_id:
          type: string
          example: ne_omaha_douglas
        jurisdiction_name:
          type: string
          example: Omaha
        matches:
          type: array
          items:
            $ref: "#/components/schemas/SearchMatch"
      required: [jurisdiction_id, jurisdiction_name, matches]

    SearchData:
      type: object
      properties:
        query:
          type: string
          description: The original search query
          example: frost depth
        results:
          type: array
          items:
            $ref: "#/components/schemas/SearchResult"
      required: [query, results]

    # ── Health ────────────────────────────────────────────────────────────────

    HealthResponse:
      type: object
      description: |
        Health check response. This endpoint does **not** use the standard
        envelope — it returns a flat object.
      properties:
        status:
          type: string
          enum: [ok, degraded, error]
          example: ok
        version:
          type: string
          description: API version string
          example: "1.0.0"
        db:
          type: string
          description: Database connection status
          enum: [connected, disconnected]
          example: connected
        jurisdictions:
          type: integer
          description: Total number of jurisdictions currently in the database
          example: 221
        timestamp:
          type: string
          format: date-time
          description: Server time at response (ISO 8601)
          example: "2026-02-28T06:31:32.358Z"
      required: [status]

    # ── Error ─────────────────────────────────────────────────────────────────

    Error:
      type: object
      description: Standard error response.
      properties:
        error:
          type: string
          description: Short error description
          example: Jurisdiction not found
        code:
          type: integer
          description: HTTP status code
          example: 404
        message:
          type: string
          description: Detailed error message
          example: No jurisdiction found with id 'ne_bad_id'
      required: [error]

  # ── Reusable responses ──────────────────────────────────────────────────────

  responses:
    NotFound:
      description: The requested resource was not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Jurisdiction not found
            code: 404
            message: No jurisdiction found with id 'ne_bad_id'

    BadRequest:
      description: Missing or invalid query parameter
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Provide q parameter
            code: 400
            message: The 'q' query parameter is required

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Internal server error
            code: 500
            message: SQLITE_ERROR no such table
