openapi: 3.1.0
info:
  title: SolvingHealth Public Data API
  version: 1.0.0
  summary: Read-only gateway over federal health datasets, with provenance on every response.
  description: |
    One base URL over the public federal health datasets that actually answer:
    the CMS NPPES NPI registry, openFDA (510(k) clearances, device recalls, MAUDE
    adverse events, drug labeling), ClinicalTrials.gov API v2, and the CMS
    Provider Data Catalog.

    Every successful response carries `source`, `source_url` and `retrieved_at`,
    so any fact returned here can be re-derived from the government source
    without trusting this service.

    No key. No writes. No PHI. Results are cached briefly at the edge.

    Endpoints that cannot be served honestly return 501 with the reason rather
    than a synthesized answer — see `/exclusions/search`.
  license:
    name: Public federal data; this gateway adds no rights.
  contact:
    url: https://solvinghealth.com/developers
servers:
  - url: https://solvinghealth.com/api/v1
tags:
  - name: providers
  - name: devices
  - name: drugs
  - name: research
  - name: facilities
  - name: meta
paths:
  /:
    get:
      tags: [meta]
      operationId: getIndex
      summary: Service index — every endpoint, whether it is live, and its upstream dataset.
      responses:
        '200': { description: Service index, content: { application/json: { schema: { type: object } } } }
  /health:
    get:
      tags: [meta]
      operationId: getHealth
      summary: Liveness plus an upstream reachability probe.
      responses:
        '200': { description: Health, content: { application/json: { schema: { type: object } } } }
  /providers/search:
    get:
      tags: [providers]
      operationId: searchProviders
      summary: Search the NPPES NPI registry.
      description: |
        NPPES rejects `state` on its own as too broad. Pair it with at least one of
        `taxonomy`, `city`, `last_name`, `first_name` or `organization` — each of which
        also works with no `state` at all. A state-only request returns 400 `invalid_query`
        with the combination to use.
      parameters:
        - { name: state, in: query, schema: { type: string }, description: "Two-letter state code. Not valid on its own — pair it with another field.", example: CO }
        - { name: city, in: query, schema: { type: string } }
        - { name: taxonomy, in: query, schema: { type: string }, description: "Specialty as NPPES words it, e.g. Orthopaedic Surgery" }
        - { name: first_name, in: query, schema: { type: string } }
        - { name: last_name, in: query, schema: { type: string } }
        - { name: organization, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Matching providers, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
        '400': { description: Invalid query, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '502': { description: Upstream unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /providers/{npi}:
    get:
      tags: [providers]
      operationId: getProvider
      summary: One provider by 10-digit NPI.
      parameters:
        - { name: npi, in: path, required: true, schema: { type: string, pattern: '^\d{10}$' } }
      responses:
        '200': { description: The provider, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
        '400': { description: Not a 10-digit NPI, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '404': { description: No NPPES record, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /clearances/search:
    get:
      tags: [devices]
      operationId: searchClearances
      summary: FDA 510(k) device clearances.
      parameters:
        - { name: q, in: query, schema: { type: string }, description: Device name to match }
        - { name: applicant, in: query, schema: { type: string } }
        - { name: since, in: query, schema: { type: string }, description: Earliest decision date, YYYYMMDD }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Clearances, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /devices/recalls:
    get:
      tags: [devices]
      operationId: searchDeviceRecalls
      summary: FDA device recall records.
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Recalls, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /devices/events:
    get:
      tags: [devices]
      operationId: searchAdverseEvents
      summary: FDA MAUDE device adverse event reports.
      parameters:
        - { name: q, in: query, schema: { type: string }, description: Generic device name }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Adverse events, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /labels/search:
    get:
      tags: [drugs]
      operationId: searchDrugLabels
      summary: FDA structured drug labeling (SPL).
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: brand, in: query, schema: { type: string } }
        - { name: generic, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Labels, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /trials/search:
    get:
      tags: [research]
      operationId: searchTrials
      summary: Registered studies from ClinicalTrials.gov API v2.
      parameters:
        - { name: condition, in: query, schema: { type: string } }
        - { name: intervention, in: query, schema: { type: string } }
        - { name: location, in: query, schema: { type: string }, description: "Where the study runs: a state, city or country, e.g. Colorado" }
        - { name: status, in: query, schema: { type: string }, description: "e.g. RECRUITING, COMPLETED" }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Studies, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /hospitals/search:
    get:
      tags: [facilities]
      operationId: searchHospitals
      summary: CMS Hospital General Information.
      parameters:
        - { name: state, in: query, schema: { type: string } }
        - { name: city, in: query, schema: { type: string } }
        - { name: name, in: query, schema: { type: string }, description: Partial facility name }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 10 } }
      responses:
        '200': { description: Hospitals, content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } } }
  /exclusions/search:
    get:
      tags: [providers]
      operationId: searchExclusions
      summary: Not implemented — screen at the official OIG source.
      description: |
        The OIG LEIE publishes a bulk CSV, not a queryable API, and that download is not
        reachable from this runtime. Rather than serve a stale or synthesized copy, this
        endpoint returns 501 and points at https://exclusions.oig.hhs.gov/.
      responses:
        '501': { description: Deliberately not implemented, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
components:
  schemas:
    Envelope:
      type: object
      required: [ok, api_version, source, source_url, retrieved_at, count, results]
      properties:
        ok: { type: boolean, const: true }
        api_version: { type: string }
        source: { type: string, description: Human-readable name of the upstream dataset. }
        source_url: { type: string, format: uri, description: The exact upstream URL queried — re-run it yourself. }
        retrieved_at: { type: string, format: date-time }
        count: { type: integer }
        upstream_total: { type: [integer, 'null'], description: Total matches upstream, when the source reports one. }
        results: { type: array, items: { type: object } }
        disclaimer: { type: string }
    Error:
      type: object
      required: [ok, error]
      properties:
        ok: { type: boolean, const: false }
        api_version: { type: string }
        error: { type: string, description: "Stable machine code, e.g. invalid_npi, not_found, upstream_unavailable, not_implemented." }
        detail: { type: string }
        source_url: { type: string }
        official_source: { type: string }
