openapi: 3.0.3
info:
  title: SOAT AI Providers API
  version: 1.0.0
  description: API for managing LLM provider configurations (AI Providers resource)
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: AI Providers
    description: Manage AI providers
security:
  - bearerAuth: []
paths:
  /api/v1/ai-providers:
    get:
      tags:
        - AI Providers
      summary: List AI providers
      description: Returns a list of AI provider configurations for a project
      operationId: listAiProviders
      parameters:
        - name: project_id
          in: query
          description: Project ID (required if not using project key auth)
          schema:
            type: string
            example: proj_V1StGXR8Z5jdHi6B
        - name: limit
          in: query
          description: Number of results per page
          schema:
            type: integer
            default: 25
        - name: offset
          in: query
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of AI providers
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    provider:
                      type: string
                      enum: [openai, anthropic, google, xai, groq, ollama, azure, bedrock, gateway, custom]
                    default_model:
                      type: string
                    project_id:
                      x-soat-ref: projects
                      type: string
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '500':
          description: Internal server error
    post:
      tags:
        - AI Providers
      summary: Create an AI provider
      description: Creates a new LLM provider configuration
      operationId: createAiProvider
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - provider
                - default_model
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Project ID (required if not using project key auth)
                  example: proj_V1StGXR8Z5jdHi6B
                name:
                  type: string
                  description: Provider configuration name
                  example: OpenAI Production
                provider:
                  type: string
                  enum: [openai, anthropic, google, xai, groq, ollama, azure, bedrock, gateway, custom]
                  description: LLM provider
                  example: openai
                default_model:
                  type: string
                  description: Default model to use
                  example: gpt-4
                secret_id:
                  x-soat-ref: secrets
                  type: string
                  description: Secret ID containing API credentials
                  example: sec_V1StGXR8Z5jdHi6B
                base_url:
                  type: string
                  description: Custom base URL for the provider
                config:
                  type: object
                  description: Additional provider-specific configuration
      responses:
        '201':
          description: AI provider created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  provider:
                    type: string
                  default_model:
                    type: string
                  project_id:
                    x-soat-ref: projects
                    type: string
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
        '400':
          description: Bad request (invalid provider or missing fields)
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '500':
          description: Internal server error
  /api/v1/ai-providers/{ai_provider_id}:
    get:
      tags:
        - AI Providers
      summary: Get an AI provider
      description: Returns a specific AI provider configuration
      operationId: getAiProvider
      parameters:
        - name: ai_provider_id
          in: path
          required: true
          description: AI Provider ID
          schema:
            type: string
            example: aip_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: AI provider details
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  provider:
                    type: string
                  default_model:
                    type: string
                  project_id:
                    x-soat-ref: projects
                    type: string
                  secret_id:
                    x-soat-ref: secrets
                    type: string
                  base_url:
                    type: string
                  config:
                    type: object
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: AI provider not found
    patch:
      tags:
        - AI Providers
      summary: Update an AI provider
      description: Updates an AI provider configuration
      operationId: updateAiProvider
      parameters:
        - name: ai_provider_id
          in: path
          required: true
          description: AI Provider ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                provider:
                  type: string
                  enum: [openai, anthropic, google, xai, groq, ollama, azure, bedrock, gateway, custom]
                  description: LLM provider
                  example: openai
                default_model:
                  type: string
                secret_id:
                  x-soat-ref: secrets
                  type: string
                base_url:
                  type: string
                config:
                  type: object
      responses:
        '200':
          description: AI provider updated successfully
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: AI provider not found
    delete:
      tags:
        - AI Providers
      summary: Delete an AI provider
      description: Deletes an AI provider configuration
      operationId: deleteAiProvider
      parameters:
        - name: ai_provider_id
          in: path
          required: true
          description: AI Provider ID
          schema:
            type: string
      responses:
        '200':
          description: AI provider deleted successfully
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: AI provider not found
  /api/v1/ai-providers/{ai_provider_id}/prices:
    get:
      tags:
        - AI Providers
      summary: List per-provider price overrides
      description: >
        Returns the per-provider price overrides for this AI provider instance.
        An override prices this specific provider (e.g. an enterprise-negotiated
        rate or a gateway with markup) and wins over the global default at cost
        time. Authorized by the caller's access to the provider's project — so,
        unlike the global price book, a project's own overrides are visible here.
      operationId: getAiProviderPrices
      parameters:
        - name: ai_provider_id
          in: path
          required: true
          description: AI Provider ID
          schema:
            type: string
            example: aip_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: The provider's price overrides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProviderPricesResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: AI provider not found
    put:
      tags:
        - AI Providers
      summary: Upsert per-provider price overrides
      description: >
        Upserts price overrides for this AI provider instance, keyed on
        (model, effective_from). The provider slug is taken from the AI provider
        itself, so only the model, rates, and effective_from are supplied.
        Authorized by the caller's access to the provider's project.
        `effective_from` must be in the future — past prices are immutable, so
        ship corrections as new future-dated rows.
      operationId: updateAiProviderPrices
      parameters:
        - name: ai_provider_id
          in: path
          required: true
          description: AI Provider ID
          schema:
            type: string
            example: aip_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertProviderPricesRequest'
      responses:
        '200':
          description: The upserted price overrides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProviderPricesResponse'
        '400':
          description: Bad Request (e.g. non-future effective_from)
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: AI provider not found
components:
  schemas:
    ProviderPrice:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the price row
          example: price_V1StGXR8Z5jdHi6B
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          description: The AI provider instance this override prices
          example: aip_V1StGXR8Z5jdHi6B
        provider:
          type: string
          description: Provider slug (taken from the AI provider instance)
          example: openai
        model:
          type: string
          example: gpt-4o
        input_price_per_m:
          type: number
          description: USD per one million input (prompt) tokens
        output_price_per_m:
          type: number
          description: USD per one million output (completion) tokens
        cached_price_per_m:
          type: number
          nullable: true
          description: USD per one million cached input tokens; null falls back to the input price
        effective_from:
          type: string
          format: date-time
          description: The row with the latest effective_from <= now() prices a call
        created_at:
          type: string
          format: date-time

    ProviderPricesResponse:
      type: object
      properties:
        prices:
          type: array
          items:
            $ref: '#/components/schemas/ProviderPrice'

    UpsertProviderPricesRequest:
      type: object
      required:
        - prices
      properties:
        prices:
          type: array
          items:
            type: object
            required:
              - model
              - input_price_per_m
              - output_price_per_m
              - effective_from
            properties:
              model:
                type: string
                example: gpt-4o
              input_price_per_m:
                type: number
              output_price_per_m:
                type: number
              cached_price_per_m:
                type: number
                nullable: true
              effective_from:
                type: string
                format: date-time
                description: Must be in the future; past prices are immutable

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
