openapi: 3.0.3
info:
  title: carbon-llm API
  version: "1.0.0"
  description: |
    Public REST API to **estimate** or **track** LLM inference CO₂e from model identifier and token counts.
    No prompt text is sent or stored by these endpoints — metadata only.

    - **POST /estimate** — unauthenticated coefficient-based estimate (nothing persisted).
    - **POST /track** — Bearer-authenticated; queues a usage event for your ISV account and tenant.
    - **GET /session/{developer_id}/today** — optional MCP / IDE session rollup (Bearer).

    Methodology version is included on track responses where applicable.

  license:
    name: Proprietary
    url: https://carbon-llm.com/docs

servers:
  - url: https://carbon-llm.com/api/v1
    description: Production
  - url: http://localhost:3000/api/v1
    description: Local development

tags:
  - name: estimate
    description: Token-based (and optional hardware) CO₂ estimate — not billed, not stored
  - name: track
    description: Record a usage event against a tenant (requires API key)
  - name: session
    description: Developer session stats for IDE / MCP integrations

paths:
  /estimate:
    post:
      tags: [estimate]
      summary: Estimate CO₂ for a completion
      description: |
        Returns an immediate coefficient-based estimate. Optional `method: hardware` uses duration and grid region instead of token coefficients for the headline `estimated_co2_grams`.
      operationId: postEstimate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EstimateRequest"
            examples:
              token:
                summary: Token-only (default)
                value:
                  model: gpt-4o
                  prompt_tokens: 100
                  completion_tokens: 50
              hardware:
                summary: Hardware path
                value:
                  model: gpt-4o
                  prompt_tokens: 100
                  completion_tokens: 50
                  method: hardware
                  duration_ms: 120000
                  datacenter_country: FI
      responses:
        "200":
          description: Estimate result
          headers:
            X-Response-Time:
              schema:
                type: string
                example: "< 20ms"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EstimateResponse"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                validation:
                  value:
                    error:
                      code: validation_error
                      message: Invalid body
                      details: {}
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
    options:
      tags: [estimate]
      summary: CORS preflight
      operationId: optionsEstimate
      responses:
        "200":
          description: OK

  /track:
    post:
      tags: [track]
      summary: Queue a tracked usage event
      description: |
        Accepts the event, returns **202** with a carbon snapshot matching what will be stored.
        Persistence runs asynchronously; refresh the dashboard if the row is not visible immediately.
      operationId: postTrack
      security:
        - BearerAuth: []
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
          example: "Bearer isv_test_sk_…"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackRequest"
            example:
              tenant_id: client-acme
              model: gpt-4o
              prompt_tokens: 100
              completion_tokens: 50
      responses:
        "202":
          description: Accepted — event queued
          headers:
            X-Event-Id:
              schema:
                type: string
              description: Same as `event_id` in body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackAccepted"
        "400":
          description: Invalid JSON body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "402":
          description: Live key quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentRequiredError"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "503":
          description: Service unavailable (database not configured)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
    options:
      tags: [track]
      summary: CORS preflight
      operationId: optionsTrack
      responses:
        "200":
          description: OK

  /session/{developer_id}/today:
    get:
      tags: [session]
      summary: Today’s IDE session stats for a developer id
      description: Aggregates Claude Code / MCP-style events for the current UTC day.
      operationId: getSessionDeveloperToday
      security:
        - BearerAuth: []
      parameters:
        - name: developer_id
          in: path
          required: true
          description: URL-encoded developer identifier
          schema:
            type: string
      responses:
        "200":
          description: Aggregated stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeveloperTodayStats"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "503":
          description: Service unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
    options:
      tags: [session]
      summary: CORS preflight
      operationId: optionsSessionToday
      responses:
        "200":
          description: OK

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        ISV API key from the dashboard. Prefix `isv_test_sk_` (test, not billed against live quota) or `isv_live_sk_` (live).

  schemas:
    ApiError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              examples: [validation_error, unauthorized, internal_error, service_unavailable]
            message:
              type: string
            details:
              type: object
              additionalProperties: true
            billing_url:
              type: string
              format: uri

    PaymentRequiredError:
      allOf:
        - $ref: "#/components/schemas/ApiError"
      example:
        error:
          code: payment_required
          message: Free tier quota exceeded for live key.
          billing_url: /dashboard/billing

    EstimateRequest:
      type: object
      required: [model, prompt_tokens, completion_tokens]
      properties:
        model:
          type: string
          description: Model identifier (same string you report to providers)
        prompt_tokens:
          type: integer
          minimum: 0
        completion_tokens:
          type: integer
          minimum: 0
        method:
          type: string
          enum: [token, hardware]
          default: token
        duration_ms:
          type: integer
          minimum: 1
          description: Required when method is `hardware`
        datacenter_country:
          type: string
          minLength: 2
          maxLength: 2
          description: ISO 3166-1 alpha-2 country code for hardware grid factor

    EstimateResponse:
      type: object
      required:
        - estimated_co2_grams
        - confidence
        - source
        - billed_event
        - total_tokens
        - co2_per_1k_tokens_used
        - calculation_details
      properties:
        estimated_co2_grams:
          type: number
          format: float
        confidence:
          $ref: "#/components/schemas/CoefficientConfidence"
        source:
          type: string
        billed_event:
          type: boolean
        total_tokens:
          type: integer
        co2_per_1k_tokens_used:
          type: number
          format: float
        warning:
          type: string
          description: Present when the model is unknown and a default coefficient was used
        calculation_details:
          type: object
          properties:
            formula:
              type: string
            total_tokens:
              type: integer
            coefficient_per_1k:
              type: number
            method:
              type: string
              enum: [token, hardware]
        hardware_context:
          type: object
          description: Present when method is hardware
        comparison:
          type: object
          description: Token vs hardware comparison when hardware path is used
        green_datacenter_tip:
          type: string

    CoefficientConfidence:
      type: string
      enum: [measured, benchmarked, estimated]

    TrackRequest:
      type: object
      required: [tenant_id, model, prompt_tokens, completion_tokens]
      properties:
        tenant_id:
          type: string
          description: Stable external id for the end customer (your tenant key)
        model:
          type: string
        prompt_tokens:
          type: integer
          minimum: 0
        completion_tokens:
          type: integer
          minimum: 0

    TrackAccepted:
      type: object
      required:
        - queued
        - event_id
        - is_test
        - methodology_version
        - estimated_co2_grams
        - total_tokens
        - co2_per_1k_tokens_used
        - confidence
        - source
        - model_found
      properties:
        queued:
          type: boolean
          example: true
        event_id:
          type: string
          pattern: "^evt_"
        is_test:
          type: boolean
        methodology_version:
          type: string
          example: "2026.03.1"
        estimated_co2_grams:
          type: number
        total_tokens:
          type: integer
        co2_per_1k_tokens_used:
          type: number
        confidence:
          $ref: "#/components/schemas/CoefficientConfidence"
        source:
          type: string
        model_found:
          type: boolean
        free_events_remaining:
          type: integer
          description: Omitted for test keys

    DeveloperTodayStats:
      type: object
      required:
        - developer_id
        - today_co2_grams
        - today_prompts
        - today_tokens
        - top_model
        - sessions
        - equivalent
      properties:
        developer_id:
          type: string
        today_co2_grams:
          type: number
        today_prompts:
          type: integer
        today_tokens:
          type: integer
        top_model:
          type: string
          nullable: true
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/SessionTodayRow"
        equivalent:
          type: string
          description: Human-readable impact analogy

    SessionTodayRow:
      type: object
      properties:
        id:
          type: string
        session_id:
          type: string
        developer_id:
          type: string
        total_prompt_tokens:
          type: integer
        total_completion_tokens:
          type: integer
        total_co2_grams:
          type: number
        model:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
        last_activity:
          type: string
          format: date-time
