openapi: 3.0.3
info:
  title: naturali.ai — AI Providers API
  version: 1.0.0
  description: >
    BYOK (bring your own key) providers, scoped to a project (API.md §4 —
    Models). Registering a provider stores the credentials as a write-only
    secret and creates the backing provider record on the runtime, which is the
    only place a provider is stored — `kind` included. Credentials are never
    returned in any response (M2, A4). naturali-managed
    providers use the same resource with kind=managed.
  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: Providers
    description: Register and manage a project's AI providers (BYOK / managed).
security:
  - bearerAuth: []
paths:
  /v1/projects/{project_id}/providers:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      tags: [Providers]
      summary: List providers
      description: Lists the AI providers registered in the project.
      operationId: listProviders
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: A page of providers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProviderList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      tags: [Providers]
      summary: Register a provider (managed or BYOK)
      description: >
        Register a managed provider (naturali-keyed, priced on the runtime) or a
        BYOK provider (your credentials, stored write-only and never priced).
        See ProviderCreate for the fields each mode takes.
      operationId: createProvider
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProviderCreate'
      responses:
        '201':
          description: Provider registered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Provider'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/providers/{provider_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/ProviderId'
    get:
      tags: [Providers]
      summary: Get a provider
      operationId: getProvider
      responses:
        '200':
          description: Provider details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Provider'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Providers]
      summary: Update a provider
      description: >
        Change the model, name or base URL, or rotate the credentials
        (api_key). At least one field is required.
      operationId: updateProvider
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProviderUpdate'
      responses:
        '200':
          description: Provider updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Provider'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    delete:
      tags: [Providers]
      summary: Delete a provider
      description: >
        Deletes the backing provider record on the runtime and its secret.
        Returns 409 if the provider is still referenced by live resources
        (agents) — detach those first. `force=true` clears only soft dependents
        (price overrides, usage history); live references always block deletion.
      operationId: deleteProvider
      parameters:
        - $ref: '#/components/parameters/Force'
      responses:
        '204':
          description: Provider deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
  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
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Client-supplied key to make this mutating POST idempotent.
      schema:
        type: string
    Force:
      name: force
      in: query
      required: false
      description: >
        When true, also clear the provider's soft dependents (price overrides,
        usage history) on the runtime. Live references (agents) always block
        deletion.
      schema:
        type: boolean
        default: false
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    ProviderId:
      name: provider_id
      in: path
      required: true
      description: Provider public ID (aip_ prefix).
      schema:
        type: string
        example: aip_V1StGXR8Z5jdHi6B
  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'
    Conflict:
      description: The request conflicts with the resource's current state.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UpstreamUnavailable:
      description: The upstream runtime could not complete the operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    Provider:
      type: object
      properties:
        id:
          type: string
          description: Public provider ID (aip_ prefix) — the provider id on the runtime.
          example: aip_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          example: anthropic (byok)
        provider:
          type: string
          description: The provider slug.
          enum:
            [
              openai,
              anthropic,
              google,
              xai,
              groq,
              ollama,
              azure,
              bedrock,
              gateway,
              custom,
            ]
          example: bedrock
        default_model:
          type: string
          example: anthropic.claude-haiku-4-5-20251001-v1:0
        kind:
          type: string
          enum: [managed, byok]
          example: byok
        has_secret:
          type: boolean
          description: Whether credentials are on file (their value is never returned).
          example: true
        base_url:
          type: string
          nullable: true
          description: Custom base URL, when set.
          example: null
        created_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
      required:
        - id
        - project_id
        - name
        - provider
        - default_model
        - kind
        - has_secret
        - created_at
        - updated_at
    ProviderCreate:
      type: object
      description: >
        Two modes, selected by `kind` (default `byok`).

        `managed` (naturali-keyed, metered at cost on the runtime): supply only `model`
        (a catalog model id offered as managed, e.g. amazon.nova-lite-v1:0) and
        an optional `name`. No credential — managed providers use naturali's own
        AWS access, and are metered at raw AWS cost (customer markup is applied
        later by the naturali cost API).

        `byok`: supply `provider`, `default_model` and `api_key` (+ optional
        `name`, `base_url`). The credential is stored write-only and never
        priced.
      properties:
        kind:
          type: string
          enum: [byok, managed]
          default: byok
          example: managed
        model:
          type: string
          description: Managed only — a catalog model id offered as managed.
          example: amazon.nova-lite-v1:0
        provider:
          type: string
          description: BYOK only — the provider slug.
          enum:
            [
              openai,
              anthropic,
              google,
              xai,
              groq,
              ollama,
              azure,
              bedrock,
              gateway,
              custom,
            ]
          example: bedrock
        default_model:
          type: string
          description: BYOK only — model string the runtime sends (see /v1/models provider_model).
          example: us.anthropic.claude-haiku-4-5-20251001-v1:0
        api_key:
          type: string
          description: >
            BYOK only — the credential value, stored write-only. A provider API
            key, or for bedrock IAM credentials a JSON object string
            ({"accessKeyId":"…","secretAccessKey":"…"}).
          example: sk-ant-...
        name:
          type: string
          description: Human-readable label.
          example: my-nova
        base_url:
          type: string
          description: BYOK only — custom base URL (gateways / self-hosted).
          example: https://gateway.example.com
    ProviderUpdate:
      type: object
      description: At least one field must be present.
      minProperties: 1
      properties:
        name:
          type: string
          example: renamed
        default_model:
          type: string
          example: us.anthropic.claude-opus-4-5-20251101-v1:0
        base_url:
          type: string
          example: https://gateway.example.com
        api_key:
          type: string
          description: Rotate the stored credentials in place.
          example: sk-ant-new-...
    ProviderList:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Provider'
        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
          required: [code, message]
          properties:
            code:
              type: string
              example: upstream_unavailable
            message:
              type: string
              example: Could not create the provider on the upstream runtime.
            details:
              type: object
              additionalProperties: true
              description: Optional structured context for the error.
