openapi: 3.0.3
info:
  title: naturali.ai — Models API
  version: 1.0.0
  description: >
    The naturali model catalog: every model naturali offers, with capability
    metadata, lifecycle status and naturali's price. The catalog is synced daily
    from the upstream runtime's live model listing and the provider's published
    prices, so it reflects what is served today rather than a snapshot.
    To generate on these models, create an AI provider with
    `provider: "naturali"` — it needs no credentials of yours and serves every
    model listed here.

    A model's `model` is the only model string this API asks you for: the same
    value goes in a provider's `default_model` and in an agent's `model`.
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: '{baseUrl}'
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Models
    description: >
      Browse the model catalog. The catalog is the same for every project.
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/models:
    get:
      tags: [Models]
      summary: List models
      description: >
        Lists the models naturali offers, sorted by name. Filter by vendor,
        input/output modality or lifecycle status.

        Every model listed here is available to any project with a
        `provider: "naturali"`
        [AI provider](/docs/api/ai-providers/create-ai-provider) — there is no
        second class to filter for. Models
        naturali cannot serve (unpriced, or producing something other than text)
        are not in this catalog; to generate on one of those, register your own
        credentials as an
        [AI provider](/docs/api/ai-providers/create-ai-provider) and ask that
        provider what it serves with
        [`GET /v1/projects/{project_id}/ai-providers/{ai_provider_id}/models`](/docs/api/ai-providers/list-ai-provider-models).
      operationId: listModels
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: vendor
          in: query
          required: false
          description: Filter by model maker (e.g. anthropic, amazon, meta).
          schema:
            type: string
            example: amazon
        - name: modality
          in: query
          required: false
          description: >
            Filter to models whose input or output modalities include this
            value (e.g. text, image, embedding, speech).
          schema:
            type: string
            example: text
        - name: status
          in: query
          required: false
          description: Filter by lifecycle status.
          schema:
            type: string
            enum: [available, deprecated]
            example: available
      responses:
        '200':
          description: A page of models.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/models/{model}:
    parameters:
      - $ref: '#/components/parameters/ModelName'
    get:
      tags: [Models]
      summary: Get a model
      description: >
        `{model}` is naturali's own name for the model, the same value
        [`GET /v1/models`](/docs/api/models/list-models) returns — a vendor's
        invocation string is a `404`. A `deprecated` model still reads here, so
        a project already generating on one can see what happened to it.
      operationId: getModel
      responses:
        '200':
          description: Model details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Model'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: >-
        A connected app's OAuth access token, issued by this API's authorization
        server (discovery: /.well-known/oauth-authorization-server). Its one scope
        carries every operation, confined to the projects the user chose when
        approving the app.
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      description: Maximum items per page — an integer from 1 to 100 (default 20).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque pagination cursor from a previous response's next_cursor.
      schema:
        type: string
    ModelName:
      name: model
      in: path
      required: true
      description: The model's name, as `GET /v1/models` returns it.
      schema:
        type: string
        example: nova-lite-v1
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: The resource does not exist (existence is not leaked).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    Model:
      type: object
      properties:
        model:
          type: string
          description: >
            The model's name, and the only model string this API accepts or
            returns. Use it verbatim as a provider's `default_model` and as an
            agent's `model`.


            It is naturali's own name, not the vendor's: it states a version
            explicitly and never changes, so a vendor relabelling its model
            cannot move a name you have already written down. A vendor's own
            invocation string is refused where this is expected — one model has
            one name.
          example: nova-lite-v1
        vendor:
          type: string
          description: The model maker.
          example: amazon
        input_modalities:
          type: array
          items:
            type: string
          example: [text, image, video]
          description: Accepted input modalities.
        output_modalities:
          type: array
          items:
            type: string
          example: [text]
        streaming:
          type: boolean
          example: true
        status:
          type: string
          enum: [available, deprecated]
          description: >
            Lifecycle status, from the provider's own listing at the last sync.
            A model the provider stops listing — or reports as legacy or
            deprecated — flips to `deprecated` and stays in the catalog, so a
            project already generating on it can see what happened to it. A
            `deprecated` model cannot be chosen as the `default_model` of a
            new `provider: "naturali"` provider.
          example: available
        pricing:
          type: object
          description: >
            naturali's price for generating on this model, in USD per 1K
            tokens. Always present: a model naturali cannot price is not in this
            catalog. Refreshed by the daily sync.
          properties:
            currency:
              type: string
              enum: [usd]
              example: usd
            input_per_1k_tokens:
              type: number
              description: USD per 1K input (uncached) tokens.
              example: 0.00006
            output_per_1k_tokens:
              type: number
              description: >
                USD per 1K output tokens. Reasoning ("thinking") tokens are
                output tokens: on a model that reasons by default they are
                counted in `output_tokens` and billed at this rate, and can be
                most of the output on a short answer. A rate alone therefore
                does not size a request on such a model.
              example: 0.00024
            cached_input_per_1k_tokens:
              type: number
              nullable: true
              description: >
                USD per 1K cache-read input tokens; null when the model does
                not price caching (cached tokens are then billed at the input
                rate).
              example: 0.000015
          required:
            - currency
            - input_per_1k_tokens
            - output_per_1k_tokens
            - cached_input_per_1k_tokens
        synced_at:
          type: string
          format: date-time
          description: When the catalog last confirmed this entry against the provider.
          example: '2026-08-18T06:00:00.000Z'
      required:
        - model
        - vendor
        - input_modalities
        - output_modalities
        - streaming
        - status
        - pricing
        - synced_at
    ModelList:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Model'
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page, or null at the end.
          example: null
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape, so `code` can
            be read without first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                Machine-readable error code: lower_snake for an error naturali
                raises (`access_denied`), UPPER_SNAKE for one the runtime reports
                (`RESOURCE_NOT_FOUND`).
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: >-
                Structured context for an error naturali raises, such as the
                `resource` and `limit` of a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
