openapi: 3.0.3
info:
  title: Pexara insurance agency change-log
  version: 0.2.0
  description: |
    Machine-readable specification for the insurance agency change-log.

    **This host (pexara.ai) is discovery.** It serves llms.txt, the Statelog
    offer (offer.json / offer.md), this OpenAPI document, a static coverage
    snapshot, changelog markdown, and a GET /v1/* catch-all that returns 404
    `not_on_this_host`. It does **not** run the query API. The live query
    API is `https://api.pexara.ai`.

    Paths are marked:
    - `x-pexara-status: on_this_host` — actually served here
    - `x-pexara-status: live_on_query_host` — live at https://api.pexara.ai;
      on this host these paths 404

    Unusual fact: government registers publish a start date then delete the
    row. Terminations exist nowhere else. Answers are windows. Every historical
    row carries `uncertainty_days`. Do not round a window to a date.

    Observation started 2026-08-12. Coverage snapshot as of 2026-08-14.

    Identifiers: NPN, EIN, NAIC.

    Metered prices on https://api.pexara.ai (USD per request), 25 free calls/day,
    50 free rows/day, page cap 25, then 402:
    {
      "GET /v1/agency/{npn}/changes": 0.004,
      "GET /v1/terminations": 0.01,
      "GET /v1/carrier/{naic_id}/attrition": 0.02,
      "GET /v1/signals/at-risk": 0.05,
      "GET /v1/control-changes": 0.02
    }

    Stripe is false. x402 settlement is false. GET /v1/quote is the price list,
    not checkout. A 402 is a quote, not a payment rail. Collection is not live.

    Honesty: `GET /v1/control-changes` on the laptop API currently omits
    `uncertainty_days` even though `ended_after` / `ended_by` are present.
  contact:
    name: Pexara
    url: https://pexara.ai

servers:
  - url: https://pexara.ai
    description: This origin — discovery files, coverage snapshot, and /v1 404 catch-all. Not the query API.
  - url: https://api.pexara.ai
    description: Live query API. Free /v1/coverage, /v1/agency/{npn}, /v1/search. Metered change-log routes (25 free/day then 402 quote). Stripe/x402 not collected.

tags:
  - name: on_this_host
    description: Discovery files actually served from pexara.ai
  - name: live_on_query_host
    description: Query API live at https://api.pexara.ai. On pexara.ai these paths return 404 not_on_this_host.

paths:
  /llms.txt:
    get:
      operationId: brandLlmsTxt
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Brand index for machines (last-mile and change-log)
      responses:
        '200':
          description: text/plain llms.txt (v2)
          content:
            text/plain:
              schema: { type: string }

  /intelligence/insurance/llms.txt:
    get:
      operationId: insuranceLlmsTxt
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Most-specific change-log machine index
      responses:
        '200':
          description: text/plain nested llms.txt
          content:
            text/plain:
              schema: { type: string }

  /intelligence/insurance/llms-full.txt:
    get:
      operationId: insuranceLlmsFullTxt
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Concatenated markdown for the change-log product only
      responses:
        '200':
          description: text/plain concatenated machine page
          content:
            text/plain:
              schema: { type: string }

  /intelligence/insurance/openapi.yaml:
    get:
      operationId: insuranceOpenapi
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: This OpenAPI document
      responses:
        '200':
          description: OpenAPI 3 YAML
          content:
            application/yaml:
              schema: { type: string }

  /intelligence/insurance/coverage.json:
    get:
      operationId: insuranceCoverageSnapshot
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Static coverage snapshot (not the live query API)
      description: |
        `query_api_on_this_host` is false. Observation started 2026-08-12,
        as_of 2026-08-14. `tx.agency_appointments` has verified counts.
        `fl.agency_appointments` is suspect. Other enabled source_ids are id+status
        only — no invented rows.
      responses:
        '200':
          description: Snapshot JSON
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageSnapshot'

  /intelligence/insurance/offer.json:
    get:
      operationId: insuranceOfferJson
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Statelog machine offer (JSON)
      description: |
        status deployed_for_discovery. Real prices. pay_path.collection_live is
        false. Stripe is false. x402 settlement is false. No invented coverage
        row counts. Live counts are GET https://api.pexara.ai/v1/coverage.
      responses:
        '200':
          description: Machine offer JSON
          content:
            application/json:
              schema: { type: object }

  /intelligence/insurance/offer.md:
    get:
      operationId: insuranceOfferMd
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Statelog machine offer (markdown)
      responses:
        '200':
          description: Markdown
          content:
            text/markdown:
              schema: { type: string }

  /intelligence/insurance/changelog.md:
    get:
      operationId: insuranceChangelogMd
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: LLM-friendly change-log notes
      responses:
        '200':
          description: Markdown
          content:
            text/markdown:
              schema: { type: string }

  /v1/{path}:
    get:
      operationId: v1CatchAllOnThisHost
      tags: [on_this_host]
      x-pexara-status: on_this_host
      summary: Catch-all — query API is not on this host
      description: |
        Any GET /v1/* on pexara.ai returns 404 JSON `not_on_this_host` pointing
        at discovery and the live query host https://api.pexara.ai. This captures
        unanswered demand. It is not the live query API. Call https://api.pexara.ai instead.
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
      responses:
        '404':
          description: Query API is not served from this host
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotOnThisHost'

  /v1/coverage:
    get:
      operationId: v1Coverage
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Observation window and capture success (free on https://api.pexara.ai; 404 on this host)
      description: |
        Free and LIVE at https://api.pexara.ai. Must state the real observation
        window — never a rounded date. Never round uncertainty_days. Observation
        started 2026-08-12. On pexara.ai this path returns 404 not_on_this_host.
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CoverageLive' }

  /v1/agency/{npn}:
    get:
      operationId: v1Agency
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Current profile and active appointments (free on https://api.pexara.ai; 404 on this host)
      parameters:
        - $ref: '#/components/parameters/npn'
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Agency' }

  /v1/search:
    get:
      operationId: v1Search
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Find an agency by name, NPN, or EIN (free on https://api.pexara.ai; 404 on this host)
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string, minLength: 2 }
        - name: limit
          in: query
          schema: { type: integer, maximum: 100, default: 25 }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchResult' }

  /v1/quote:
    get:
      operationId: v1Quote
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Current price list (free on https://api.pexara.ai; 404 on this host)
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live price list on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PriceList' }

  /v1/agency/{npn}/changes:
    get:
      operationId: v1AgencyChanges
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Observed change history (metered $0.004 on https://api.pexara.ai; 404 on this host)
      description: |
        Each event is bounded by occurred_after..observed_by. uncertainty_days is
        the width of that window. 25 free/day then 402.
      parameters:
        - $ref: '#/components/parameters/npn'
        - name: limit
          in: query
          schema: { type: integer, maximum: 1000, default: 200 }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeHistory' }
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /v1/terminations:
    get:
      operationId: v1Terminations
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Appointments that ended (metered $0.010 on https://api.pexara.ai; 404 on this host)
      description: |
        The rows the register never published. ended_after..ended_by bounds each
        termination; uncertainty_days is the width of that window. 25 free/day then 402.
      parameters:
        - name: since
          in: query
          schema: { type: string, format: date }
        - name: naic_id
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, maximum: 1000, default: 100 }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Terminations' }
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /v1/carrier/{naic_id}/attrition:
    get:
      operationId: v1CarrierAttrition
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Agencies a carrier gained and lost (metered $0.020 on https://api.pexara.ai; 404 on this host)
      parameters:
        - name: naic_id
          in: path
          required: true
          schema: { type: string }
          description: NAIC company code
        - name: since
          in: query
          schema: { type: string, format: date }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CarrierAttrition' }
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /v1/signals/at-risk:
    get:
      operationId: v1AtRisk
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Distress signals (metered $0.050 on https://api.pexara.ai; 404 on this host)
      description: Appointment drain, or a designated responsible licensed person gone unreplaced. 25 free/day then 402.
      parameters:
        - name: limit
          in: query
          schema: { type: integer, maximum: 200, default: 50 }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AtRisk' }
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /v1/control-changes:
    get:
      operationId: v1ControlChanges
      tags: [live_on_query_host]
      x-pexara-status: live_on_query_host
      summary: Ownership and officer changes (metered $0.020 on https://api.pexara.ai; 404 on this host)
      description: |
        25 free/day then 402. Honesty: the laptop API currently omits
        uncertainty_days on these rows (ended_after / ended_by are present).
      parameters:
        - name: since
          in: query
          schema: { type: string, format: date }
        - name: limit
          in: query
          schema: { type: integer, maximum: 1000, default: 100 }
      responses:
        '404':
          description: On this host
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NotOnThisHost' }
        '200':
          description: Live on https://api.pexara.ai. On pexara.ai this path is 404
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ControlChanges' }
        '402':
          $ref: '#/components/responses/PaymentRequired'

components:
  parameters:
    npn:
      name: npn
      in: path
      required: true
      schema: { type: string }
      description: National Producer Number

  responses:
    PaymentRequired:
      description: Free tier exhausted. Structured quote, not a wall. Live 402 quote on https://api.pexara.ai after 25 free/day. Stripe/x402 not collected.
      headers:
        X-Price-USD:
          schema: { type: string }
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PaymentRequired'
          example:
            error: payment_required
            reason: free_tier_exhausted
            detail: The free allowance of 25 metered calls per day is used up. This endpoint reads accumulated snapshot history that is not published by the source at any price.
            quote:
              endpoint: GET /v1/carrier/{naic_id}/attrition
              price: 0.02
              currency: USD
              unit: request
              accepts: [x402, api_key]
              pay_url: /v1/quote
            free_alternatives:
              - GET /v1/agency/{npn}
              - GET /v1/search
              - GET /v1/coverage

  schemas:
    NotOnThisHost:
      type: object
      required: [error, detail, query_api, discovery, openapi, coverage_snapshot]
      properties:
        error: { type: string, enum: [not_on_this_host] }
        detail: { type: string }
        query_api: { type: string, format: uri, example: https://api.pexara.ai }
        discovery: { type: string, format: uri, example: https://pexara.ai/intelligence/insurance/llms.txt }
        openapi: { type: string, format: uri, example: https://pexara.ai/intelligence/insurance/openapi.yaml }
        coverage_snapshot: { type: string, format: uri, example: https://pexara.ai/intelligence/insurance/coverage.json }

    PaymentRequired:
      type: object
      required: [error, reason, detail, quote, free_alternatives]
      properties:
        error: { type: string, enum: [payment_required] }
        reason: { type: string, enum: [free_tier_exhausted, row_allowance_exhausted] }
        detail: { type: string }
        quote:
          $ref: '#/components/schemas/Quote'
        free_alternatives:
          type: array
          items: { type: string }

    Quote:
      type: object
      required: [endpoint, price, currency, unit, accepts, pay_url]
      properties:
        endpoint: { type: string, example: GET /v1/carrier/{naic_id}/attrition }
        price: { type: number, format: double, description: Real per-endpoint price. 0.02 is attrition/control-changes, not a round number for everything. }
        currency: { type: string, enum: [USD] }
        unit: { type: string, enum: [request] }
        accepts:
          type: array
          items: { type: string, enum: [x402, api_key] }
        pay_url: { type: string, example: /v1/quote }

    CoverageSnapshot:
      type: object
      required: [kind, query_api_on_this_host, as_of, observation, sources]
      properties:
        kind: { type: string, enum: [coverage_snapshot] }
        query_api_on_this_host: { type: boolean, enum: [false] }
        query_api: { type: string, format: uri, example: https://api.pexara.ai }
        query_api_live: { type: boolean, enum: [true] }
        as_of: { type: string, format: date }
        observation:
          type: object
          properties:
            started: { type: string, format: date }
            as_of: { type: string, format: date }
            note: { type: string }
        sources:
          type: array
          items:
            type: object
            required: [source_id, status]
            properties:
              source_id: { type: string }
              status: { type: string, enum: [ok, suspect, enabled] }
              rows: { type: integer }
              disappear: { type: integer }
              note: { type: string }

    CoverageLive:
      type: object
      description: Live GET /v1/coverage body on https://api.pexara.ai. Must include the real observation window. Never round uncertainty_days.
      properties:
        sources:
          type: array
          items:
            type: object
            properties:
              source_id: { type: string }
              runs: { type: integer }
              runs_ok: { type: integer }
              first_capture: { type: string }
              latest_capture: { type: string }
              spells: { type: integer }
              terminations: { type: integer }

    Agency:
      type: object
      properties:
        npn: { type: string }
        name: { type: string, nullable: true }
        ein: { type: string, nullable: true }
        found: { type: boolean }
        carrier_count: { type: integer }
        carriers:
          type: array
          items:
            type: object
            properties:
              carrier: { type: string }
              naic_id: { type: string }
              appointed: { type: string }
              begin_provenance: { type: string }

    SearchResult:
      type: object
      properties:
        query: { type: string }
        count: { type: integer }
        truncated: { type: boolean }
        results:
          type: array
          items:
            type: object
            properties:
              npn: { type: string }
              name: { type: string }
              ein: { type: string }
              city: { type: string }

    PriceList:
      type: object
      properties:
        currency: { type: string, enum: [USD] }
        unit: { type: string, enum: [request] }
        free_calls_per_day: { type: integer, example: 25 }
        prices:
          type: object
          additionalProperties: { type: number }
        free_endpoints:
          type: array
          items: { type: string }

    HistoricalWindow:
      type: object
      description: A termination or change is a window, not a timestamp.
      properties:
        uncertainty_days:
          type: number
          description: Width of the observation window in days. Do not round to a date.

    ChangeHistory:
      type: object
      properties:
        npn: { type: string }
        count: { type: integer }
        truncated: { type: boolean }
        note: { type: string }
        events:
          type: array
          items:
            type: object
            properties:
              source_id: { type: string }
              event_type: { type: string }
              occurred_after: { type: string }
              observed_by: { type: string }
              uncertainty_days: { type: number }
              field: { type: string }
              old_value: {}
              new_value: {}
              carrier: { type: string }
              naic_id: { type: string }
              relation: { type: string }

    Terminations:
      type: object
      properties:
        count: { type: integer }
        truncated: { type: boolean }
        note: { type: string }
        terminations:
          type: array
          items:
            type: object
            properties:
              agency_npn: { type: string }
              agency_name: { type: string }
              agency_ein: { type: string }
              carrier: { type: string }
              naic_id: { type: string }
              city: { type: string }
              appointed: { type: string }
              begin_provenance: { type: string }
              ended_after: { type: string }
              ended_by: { type: string }
              uncertainty_days: { type: number }
              tenure_days: { type: integer }

    CarrierAttrition:
      type: object
      properties:
        naic_id: { type: string }
        appointments_held: { type: integer }
        appointments_lost_in_window: { type: integer }
        appointments_gained_in_window: { type: integer }
        net: { type: integer }
        lost:
          type: array
          items:
            type: object
            description: Termination rows; each carries uncertainty_days.

    AtRisk:
      type: object
      properties:
        count: { type: integer }
        truncated: { type: boolean }
        note: { type: string }
        agencies:
          type: array
          items:
            type: object
            properties:
              npn: { type: string }
              name: { type: string }
              carriers_held: { type: integer }
              carriers_lost: { type: integer }
              last_loss_by: { type: string }
              lost_drlp_by: { type: string }
              drlp_vacant: { type: boolean }
              attrition_ratio: { type: number }

    ControlChanges:
      type: object
      description: |
        Laptop API honesty — these rows currently omit uncertainty_days.
        ended_after / ended_by are present.
      properties:
        count: { type: integer }
        truncated: { type: boolean }
        changes:
          type: array
          items:
            type: object
            properties:
              npn: { type: string }
              agency: { type: string }
              party: { type: string }
              relation: { type: string }
              status: { type: string }
              began: { type: string }
              ended_after: { type: string }
              ended_by: { type: string }
