openapi: 3.1.0
info:
  title: Dr.Gero API
  version: 1.0.0
  description: >-
    Draft OpenAPI specification for Dr.Gero leaderboard, inference, trace,
    push-dataset, model, and workspace-administration APIs.
servers:
  - url: https://dr-gero-frontend-99142474693.europe-west1.run.app
    description: Current Cloud Run frontend/API base
security:
  - DrGeroToken: []
paths:
  /healthz:
    get:
      summary: Health check
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  service: { type: string }
  /api/leaderboards:
    get:
      summary: List leaderboards
      security:
        - DrGeroToken: [leaderboards:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: Leaderboard list
          content:
            application/json:
              schema:
                type: object
                properties:
                  leaderboards:
                    type: array
                    items: { $ref: '#/components/schemas/Leaderboard' }
    post:
      summary: Create a leaderboard
      security:
        - DrGeroToken: [leaderboards:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateLeaderboardRequest' }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  leaderboard: { $ref: '#/components/schemas/Leaderboard' }
                  challenge: { type: object }
  /api/leaderboards/{leaderboard_id}:
    parameters:
      - $ref: '#/components/parameters/LeaderboardId'
    get:
      summary: Get leaderboard detail
      security:
        - DrGeroToken: [leaderboards:read]
      responses:
        '200':
          description: Detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  leaderboard: { $ref: '#/components/schemas/Leaderboard' }
                  challenge: { type: object }
                  models:
                    type: array
                    items: { $ref: '#/components/schemas/LeaderboardModel' }
                  runs:
                    type: array
                    items: { type: object }
    patch:
      summary: Update a leaderboard
      security:
        - DrGeroToken: [leaderboards:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateLeaderboardRequest' }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    delete:
      summary: Delete a leaderboard
      security:
        - DrGeroToken: [leaderboards:write]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/leaderboards/{leaderboard_id}/models:
    parameters:
      - $ref: '#/components/parameters/LeaderboardId'
    get:
      summary: List candidate models
      security:
        - DrGeroToken: [leaderboards:read]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    post:
      summary: Add candidate model
      security:
        - DrGeroToken: [leaderboards:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AddLeaderboardModelRequest' }
      responses:
        '201': { $ref: '#/components/responses/JsonObject' }
  /api/leaderboards/{leaderboard_id}/models/auto-select:
    post:
      summary: Auto-select OpenRouter candidate models
      description: Requires Supabase user session and workspace OpenRouter integration.
      security:
        - SupabaseSession: []
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                number_of_models: { type: integer, minimum: 2 }
                limit_cost: { type: boolean }
                input_price_per_m_tokens: { type: number }
                output_price_per_m_tokens: { type: number }
                limit_latency: { type: boolean }
                latency_p95_seconds: { type: number }
                latency_p99_seconds: { type: number }
                only_open_source: { type: boolean }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/leaderboards/{leaderboard_id}/models/{leaderboard_model_id}:
    parameters:
      - $ref: '#/components/parameters/LeaderboardId'
      - name: leaderboard_model_id
        in: path
        required: true
        schema: { type: string, format: uuid }
    delete:
      summary: Remove candidate model
      security:
        - DrGeroToken: [leaderboards:write]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/leaderboards/{leaderboard_id}/run:
    post:
      summary: Start leaderboard run
      security:
        - DrGeroToken: [leaderboards:run]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RunLeaderboardRequest' }
      responses:
        '202': { $ref: '#/components/responses/JsonObject' }
  /api/leaderboards/{leaderboard_id}/improve-dataset:
    post:
      summary: Run dataset-improvement evaluation
      security:
        - DrGeroToken: [leaderboards:run]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '202': { $ref: '#/components/responses/JsonObject' }
  /v1/leaderboard/{leaderboard_id}/inference:
    post:
      summary: Run inference through selected leaderboard model
      security:
        - DrGeroToken: [leaderboards:inference]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/InferenceRequest' }
      responses:
        '200':
          description: Provider response
          headers:
            X-Dr.Gero-Trace-Id:
              schema: { type: string }
            X-Dr.Gero-Leaderboard-Model-Id:
              schema: { type: string }
            X-Dr.Gero-Model-Source:
              schema: { type: string }
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
  /v1/leaderboard/{leaderboard_id}/traces:
    get:
      summary: Export leaderboard traces
      security:
        - DrGeroToken: [leaderboards:read]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
        - name: source
          in: query
          schema:
            type: string
            enum: [run, inference, dataset, manual]
        - name: run_id
          in: query
          schema: { type: string, format: uuid }
        - name: format
          in: query
          schema:
            type: string
            enum: [json, jsonl, ndjson]
            default: jsonl
        - name: batch_limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
      responses:
        '200':
          description: JSON or JSONL traces
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TraceListResponse' }
            application/jsonl:
              schema:
                type: string
  /v1/leaderboard/{leaderboard_id}/dataset/push:
    post:
      summary: Push rows into a PUSH dataset
      security:
        - DrGeroToken: [leaderboards:write]
        - PushToken: []
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/PushDatasetRow'
                - type: array
                  items: { $ref: '#/components/schemas/PushDatasetRow' }
                - type: object
                  properties:
                    rows:
                      type: array
                      items: { $ref: '#/components/schemas/PushDatasetRow' }
                    events:
                      type: array
                      items: { $ref: '#/components/schemas/PushDatasetRow' }
                    samples:
                      type: array
                      items: { $ref: '#/components/schemas/PushDatasetRow' }
      responses:
        '202':
          description: Accepted/rejected push rows
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PushDatasetResponse' }
  /v1/leaderboard/{leaderboard_id}/dataset/status:
    get:
      summary: Get push dataset status
      security:
        - DrGeroToken: [leaderboards:read]
        - PushToken: []
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /v1/leaderboard/{leaderboard_id}/dataset:
    get:
      summary: Download consolidated JSONL dataset
      security:
        - DrGeroToken: [leaderboards:read]
        - PushToken: []
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '200':
          description: JSONL dataset
          content:
            application/jsonl:
              schema: { type: string }
  /v1/leaderboard/{leaderboard_id}/dataset/token:
    post:
      summary: Create push dataset token
      security:
        - DrGeroToken: [leaderboards:write]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                issue_token: { type: boolean }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /v1/leaderboard/{leaderboard_id}/dataset/tokens:
    delete:
      summary: Revoke push dataset tokens
      security:
        - DrGeroToken: [leaderboards:write]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /v1/leaderboard/{leaderboard_id}/dataset/consolidate:
    post:
      summary: Consolidate pending push dataset rows
      security:
        - DrGeroToken: [leaderboards:write]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /v1/leaderboard/{leaderboard_id}/dataset/auto-labels:
    post:
      summary: Generate dataset auto labels
      security:
        - DrGeroToken: [leaderboards:write]
      parameters:
        - $ref: '#/components/parameters/LeaderboardId'
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/models:
    get:
      summary: List Dr.Gero models
      security:
        - DrGeroToken: [models:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    post:
      summary: Create Dr.Gero model
      security:
        - DrGeroToken: [models:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateModelRequest' }
      responses:
        '201': { $ref: '#/components/responses/JsonObject' }
  /api/models/{model_id}:
    parameters:
      - name: model_id
        in: path
        required: true
        schema: { type: string, format: uuid }
    get:
      summary: Get Dr.Gero model
      security:
        - DrGeroToken: [models:read]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    patch:
      summary: Update Dr.Gero model
      security:
        - DrGeroToken: [models:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    delete:
      summary: Delete Dr.Gero model
      security:
        - DrGeroToken: [models:write]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/models/{model_id}/leaderboards:
    get:
      summary: List leaderboards assigned to a model
      security:
        - DrGeroToken: [models:read]
      parameters:
        - name: model_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/models/{model_id}/fine-tune/runs:
    get:
      summary: List fine-tune runs
      security:
        - DrGeroToken: [models:read]
      parameters:
        - name: model_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/models/{model_id}/fine-tune/run:
    post:
      summary: Start fine-tune run
      security:
        - DrGeroToken: [models:fine-tune]
      parameters:
        - name: model_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        '202': { $ref: '#/components/responses/JsonObject' }
  /api/models/{model_id}/fine-tune/sync:
    post:
      summary: Sync fine-tune state
      security:
        - DrGeroToken: [models:fine-tune]
      parameters:
        - name: model_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/base-models:
    get:
      summary: List supported base models
      security:
        - DrGeroToken: [models:read]
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/datasets/huggingface/check:
    post:
      summary: Validate Hugging Face dataset URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dataset_url]
              properties:
                dataset_url: { type: string }
                business_id: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/tokens:
    get:
      summary: List API tokens for workspace
      description: Requires Supabase user session.
      security:
        - SupabaseSession: []
      parameters:
        - name: business_id
          in: query
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    post:
      summary: Create API token
      description: Requires Supabase user session. Secret is returned once.
      security:
        - SupabaseSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateTokenRequest' }
      responses:
        '201': { $ref: '#/components/responses/JsonObject' }
  /api/tokens/{token_id}/revoke:
    post:
      summary: Revoke API token
      security:
        - SupabaseSession: []
      parameters:
        - name: token_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/tokens/{token_id}:
    delete:
      summary: Delete API token
      security:
        - SupabaseSession: []
      parameters:
        - name: token_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/integrations/validate:
    post:
      summary: Validate provider integration token
      security:
        - SupabaseSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, token]
              properties:
                provider:
                  type: string
                  enum: [openrouter, huggingface]
                token: { type: string }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
  /api/invite-user:
    get:
      summary: List workspace members and invites
      security:
        - SupabaseSession: []
      parameters:
        - name: business_id
          in: query
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    post:
      summary: Invite workspace user
      security:
        - SupabaseSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
                role: { type: string, enum: [member, admin], default: member }
                business_id: { type: string, format: uuid }
                redirect_to: { type: string, format: uri }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
    delete:
      summary: Remove workspace member or revoke invite
      security:
        - SupabaseSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [business_id]
              properties:
                business_id: { type: string, format: uuid }
                user_id: { type: string, format: uuid }
                invite_id: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/JsonObject' }
components:
  securitySchemes:
    DrGeroToken:
      type: http
      scheme: bearer
      bearerFormat: drgero
      description: Dr.Gero API token from Settings -> Tokens.
    PushToken:
      type: apiKey
      in: header
      name: X-Dr.Gero-Push-Token
      description: hpd_ push dataset token.
    SupabaseSession:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Signed-in Supabase access token used by the UI.
  parameters:
    LeaderboardId:
      name: leaderboard_id
      in: path
      required: true
      schema: { type: string, format: uuid }
    Limit:
      name: limit
      in: query
      required: false
      schema: { type: integer, minimum: 1, maximum: 1000, default: 100 }
    Offset:
      name: offset
      in: query
      required: false
      schema: { type: integer, minimum: 0, default: 0 }
  responses:
    JsonObject:
      description: JSON object response
      content:
        application/json:
          schema: { type: object, additionalProperties: true }
  schemas:
    Leaderboard:
      type: object
      additionalProperties: true
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        description: { type: string }
        dataset_type: { type: string, enum: [GET, PUSH] }
        dataset_url: { type: string }
        eval_type: { type: string, enum: [exact, judge, human] }
        chosen_model_strategy: { type: string, enum: [ranking_winner, manual] }
        chosen_leaderboard_model_id: { type: string, format: uuid }
        schedule: { type: object, additionalProperties: true }
    LeaderboardModel:
      type: object
      additionalProperties: true
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        platform: { type: string, enum: [OpenRouter, Custom, HuggingFace, Dr.Gero] }
        model_url: { type: string }
        model_id: { type: string, format: uuid }
    CreateLeaderboardRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        system_prompt: { type: string }
        model_prompt: { type: string }
        dataset_type: { type: string, enum: [GET, PUSH], default: GET }
        dataset_url: { type: string }
        eval_type: { type: string, enum: [exact, judge, human] }
        judge_provider: { type: string }
        judge_model: { type: string }
        auto_limit_size: { type: boolean }
        max_samples_to_gather: { type: integer }
        daily_event_limit: { type: integer }
        monthly_event_limit: { type: integer }
        consolidate_every_events: { type: integer }
        consolidate_every_hours: { type: integer }
        dedupe: { type: boolean }
        end_date: { type: string, format: date-time }
    UpdateLeaderboardRequest:
      type: object
      additionalProperties: true
      properties:
        name: { type: string }
        description: { type: string }
        status: { type: string }
        system_prompt: { type: string }
        dataset_type: { type: string, enum: [GET, PUSH] }
        dataset_url: { type: string }
        eval_type: { type: string, enum: [exact, judge, human] }
        chosen_model_strategy: { type: string, enum: [ranking_winner, manual] }
        chosen_leaderboard_model_id: { type: string, format: uuid }
        schedule: { type: object, additionalProperties: true }
    AddLeaderboardModelRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        model_name: { type: string }
        platform: { type: string, enum: [OpenRouter, Custom, HuggingFace, Dr.Gero] }
        url: { type: string }
        model_url: { type: string }
        model_id: { type: string, format: uuid }
        token: { type: string }
        auth_type: { type: string }
        auth_header_name: { type: string }
    RunLeaderboardRequest:
      type: object
      properties:
        model_ids:
          type: array
          items: { type: string, format: uuid }
        run_source:
          type: string
          enum: [manual, schedule]
          default: manual
        improve_dataset: { type: boolean }
    InferenceRequest:
      type: object
      additionalProperties: true
      properties:
        input: { type: string }
        prompt: { type: string }
        question: { type: string }
        query: { type: string }
        messages:
          type: array
          items:
            type: object
            required: [role, content]
            properties:
              role: { type: string }
              content: { type: string }
        temperature: { type: number }
        max_tokens: { type: integer }
        stream:
          type: boolean
          description: Streaming is not supported.
    TraceListResponse:
      type: object
      properties:
        leaderboard_id: { type: string, format: uuid }
        traces_url: { type: string }
        count: { type: integer }
        limit: { type: integer }
        offset: { type: integer }
        batch_count: { type: integer }
        scanned_rows: { type: integer }
        rows:
          type: array
          items: { type: object, additionalProperties: true }
    PushDatasetRow:
      type: object
      additionalProperties: true
      properties:
        id: { type: string }
        input: { type: string }
        prompt: { type: string }
        question: { type: string }
        query: { type: string }
        output: { type: string }
        expected: { type: string }
        response: { type: string }
        completion: { type: string }
        answer: { type: string }
        rubric: { type: string }
        messages:
          type: array
          items: { type: object, additionalProperties: true }
        metadata: { type: object, additionalProperties: true }
        tags:
          type: array
          items: { type: string }
        trace_id: { type: string }
        session_id: { type: string }
        user_id: { type: string }
        latency_ms: { type: number }
        cost_usd: { type: number }
    PushDatasetResponse:
      type: object
      properties:
        accepted_rows: { type: integer }
        rejected_rows: { type: integer }
        status: { type: string }
        batch_id: { type: string }
        pending_events: { type: integer }
        limits: { type: object, additionalProperties: true }
        consolidation: { type: object, additionalProperties: true }
    CreateModelRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        model_name: { type: string }
        description: { type: string }
        leaderboard_ids:
          type: array
          items: { type: string, format: uuid }
        creation_mode: { type: string }
        selected_base_model_version_id: { type: string, format: uuid }
        selected_base_model_resolution_id: { type: string, format: uuid }
        auto_update_model: { type: boolean }
        continuous_self_learning: { type: boolean }
        hypertuning_parameters: { type: boolean }
    CreateTokenRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        business_id: { type: string, format: uuid }
        scopes:
          type: array
          items: { type: string }
        expires_in:
          type: string
          enum: [none, 1h, 1d, 7d, 30d, 90d, 180d, 1y]
        budget_limit: { type: number, nullable: true }
        budget_reset_interval:
          type: string
          nullable: true
          enum: [daily, weekly, monthly]
