openapi: 3.1.0
info:
  title: models.apresai.dev
  summary: Multi-provider LLM availability tracker
  description: |
    Static JSON endpoints reporting which large language models are currently
    available across AWS Bedrock, Google Vertex AI Model Garden, and OpenRouter.

    The data is collected by an AWS Lambda function 4× daily (at 00, 06, 12, 18
    UTC) from each provider's canonical model API, normalized to a common
    schema, and published as static JSON to CloudFront.

    All endpoints are unauthenticated and CORS-enabled
    (`Access-Control-Allow-Origin: *`). CloudFront cache TTL is 5 minutes.

    Schema version 1. Bumped on breaking changes.
  version: "1.0.0"
  license:
    name: Open data — no auth, free to use
  contact:
    name: models.apresai.dev
    url: https://models.apresai.dev

servers:
  - url: https://models.apresai.dev
    description: Production

tags:
  - name: catalog
    description: Full and filtered listings
  - name: meta
    description: Run metadata and diff

paths:
  /api/models.json:
    get:
      summary: Full normalized catalog
      description: |
        Returns every (provider, model) listing as a flat array. Each entry has
        a `canonicalId` you can use to join across providers (e.g., to ask
        "where is `claude-opus-4-7` available?").
      tags: [catalog]
      operationId: getModels
      responses:
        "200":
          description: Array of every tracked listing.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Model' }

  /api/models-by-provider.json:
    get:
      summary: Catalog grouped by provider
      description: Same data as `/api/models.json` pre-grouped by provider.
      tags: [catalog]
      operationId: getModelsByProvider
      responses:
        "200":
          description: Object keyed by provider name.
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items: { $ref: '#/components/schemas/Model' }
                properties:
                  bedrock:
                    type: array
                    items: { $ref: '#/components/schemas/Model' }
                  vertex:
                    type: array
                    items: { $ref: '#/components/schemas/Model' }
                  openrouter:
                    type: array
                    items: { $ref: '#/components/schemas/Model' }

  /api/models-by-family.json:
    get:
      summary: Catalog grouped by detected family
      description: |
        Same data pre-grouped by family (claude, gemini, qwen, deepseek, kimi,
        mistral, llama, gpt-oss, …). Models that don't match a known family
        appear under the "other" key.
      tags: [catalog]
      operationId: getModelsByFamily
      responses:
        "200":
          description: Object keyed by family name.
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items: { $ref: '#/components/schemas/Model' }

  /api/diff-latest.json:
    get:
      summary: Models added or removed since the previous run
      description: |
        Computed by joining the current run's listings against the prior run on
        `(provider, providerModelId)`. Empty arrays on the very first run.
      tags: [meta]
      operationId: getLatestDiff
      responses:
        "200":
          description: Diff between the prior run and the current one.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Diff' }

  /api/meta.json:
    get:
      summary: Collection metadata
      description: |
        Run timestamp, per-provider counts, and the data schema version.
        Useful for clients that want to display a "last updated" indicator
        without fetching the full catalog.
      tags: [meta]
      operationId: getMeta
      responses:
        "200":
          description: Run metadata.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Meta' }

components:
  schemas:
    Provider:
      type: string
      enum: [bedrock, vertex, openrouter]
      description: |
        - `bedrock` — AWS Bedrock (us-east-1)
        - `vertex` — Google Vertex AI Model Garden (us-central1)
        - `openrouter` — OpenRouter aggregator

    PricingStatus:
      type: string
      enum: [known, unknown]
      description: |
        `known` if the provider publishes per-token pricing for this model
        (Bedrock via the public AWS pricing offer files, OpenRouter inline).
        `unknown` for Vertex AI in all cases (Google does not publish a
        machine-readable Vertex price catalog) and for any Bedrock model whose
        SKU we couldn't match against the offer file.

    Model:
      type: object
      required: [canonicalId, provider, providerModelId, displayName, family, pricingStatus, available, firstSeenAt, lastSeenAt]
      properties:
        canonicalId:
          type: string
          description: |
            Stable cross-provider identifier. `claude-opus-4-7`,
            `gemini-2-5-pro`, `kimi-k2` etc. Decimals in version numbers are
            normalized to dashes (`4.7` → `4-7`) so OpenRouter and Bedrock
            converge on the same canonical key.
          examples: [claude-opus-4-7, kimi-k2, deepseek-v3]
        provider: { $ref: '#/components/schemas/Provider' }
        providerModelId:
          type: string
          description: The exact id you pass to the provider's API.
          examples:
            - us.anthropic.claude-opus-4-7
            - publishers/anthropic/models/claude-opus-4-7
            - anthropic/claude-opus-4.7
        displayName:
          type: string
          description: Provider-supplied human label.
        family:
          type: string
          description: |
            Detected family bucket. Common values: `claude`, `gemini`, `gemma`,
            `qwen`, `deepseek`, `kimi`, `mistral`, `llama`, `gpt`, `gpt-oss`,
            `o-series`, `nemotron`, `nova`, `titan`, `command`,
            `stable-diffusion`, `imagen`, `glm`, `jamba`, `grok`,
            `perplexity`, `phi`, `hermes`, `reka`, `embed-vertex`. Unmatched
            models go to `other`.
        publisher:
          type: string
          nullable: true
          description: Provider-supplied publisher name (Anthropic, Google, …).
        version:
          type: string
          nullable: true
        modality:
          type: array
          items:
            type: string
          description: |
            Input/output modalities. Strings of the form `in:text`, `out:text`,
            `in:image`, etc.
          examples: [["in:text", "in:image", "out:text"]]
        contextWindow:
          type: integer
          nullable: true
          description: Maximum context window in tokens.
          examples: [200000, 1000000]
        pricingStatus: { $ref: '#/components/schemas/PricingStatus' }
        pricingInputPer1M:
          type: number
          nullable: true
          description: USD per 1M input tokens, when known.
          examples: [3, 5]
        pricingOutputPer1M:
          type: number
          nullable: true
          description: USD per 1M output tokens, when known.
          examples: [15, 25]
        pricingCurrency:
          type: string
          nullable: true
          enum: [USD]
        available:
          type: boolean
          description: |
            `true` while the model was visible in the most recent collector
            run. Models that disappear get this flipped to `false` rather than
            being deleted, preserving lineage.
        region:
          type: string
          nullable: true
          description: Provider region where the listing was observed.
        firstSeenAt:
          type: string
          format: date-time
        lastSeenAt:
          type: string
          format: date-time

    DiffEntry:
      type: object
      required: [provider, providerModelId, canonicalId, family]
      properties:
        provider: { $ref: '#/components/schemas/Provider' }
        providerModelId: { type: string }
        canonicalId: { type: string }
        family: { type: string }
        displayName: { type: string }

    Diff:
      type: object
      required: [since, added, removed]
      properties:
        since:
          type: string
          format: date-time
          description: ISO timestamp of the previous run.
        added:
          type: array
          items: { $ref: '#/components/schemas/DiffEntry' }
        removed:
          type: array
          items: { $ref: '#/components/schemas/DiffEntry' }
        newFamilies:
          type: array
          items: { type: string }
          description: Family buckets that appeared this run for the first time.

    Counts:
      type: object
      required: [bedrock, vertex, openrouter, total]
      properties:
        bedrock: { type: integer }
        vertex: { type: integer }
        openrouter: { type: integer }
        total: { type: integer }

    Meta:
      type: object
      required: [lastRun, counts, schemaVersion]
      properties:
        lastRun:
          type: string
          format: date-time
        counts: { $ref: '#/components/schemas/Counts' }
        schemaVersion:
          type: string
          examples: ["1"]

externalDocs:
  description: HTML documentation
  url: https://models.apresai.dev/api/
