openapi: 3.1.0
info:
  title: Joule Cloud API
  version: 1.0.0
  description: |
    OpenAI-compatible Inference, S3-compatible Object Store, plus workloads, receipts,
    and usage. Energy lands in `X-Energy-Joules` on every response.

    See [docs.greenjoules.cloud](https://docs.greenjoules.cloud) for guides + concepts.
  contact:
    name: Joule Cloud Support
    email: hello@greenjoules.cloud
    url: https://docs.greenjoules.cloud
  license:
    name: Proprietary
    url: https://docs.greenjoules.cloud/legal/terms

servers:
  - url: https://api.greenjoules.cloud/v1
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Inference
    description: OpenAI-compatible chat, embeddings, audio, images.
  - name: ObjectStore
    description: S3-compatible object storage.
  - name: Workloads
    description: Container workloads on the mesh.
  - name: Receipts
    description: Signed per-call energy receipts.
  - name: Usage
    description: Account balance and aggregate usage.

paths:
  /chat/completions:
    post:
      tags: [Inference]
      summary: Create a chat completion
      description: |
        OpenAI-shaped chat completion. Use `model: "auto"` for the cheapest capable
        silicon, or pin a model name.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
      responses:
        "200":
          description: A chat completion.
          headers:
            X-Energy-Joules:
              $ref: "#/components/headers/EnergyJoules"
            X-Carbon-mg:
              $ref: "#/components/headers/Carbon"
            X-Routed-To:
              $ref: "#/components/headers/RoutedTo"
            X-Tier:
              $ref: "#/components/headers/Tier"
            X-Receipt-Id:
              $ref: "#/components/headers/ReceiptId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCompletion"
        "402":
          $ref: "#/components/responses/InsufficientBalance"
        "429":
          $ref: "#/components/responses/EnergyBudgetExceeded"

  /embeddings:
    post:
      tags: [Inference]
      summary: Generate embeddings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingsRequest"
      responses:
        "200":
          description: Embeddings.
          headers:
            X-Energy-Joules:
              $ref: "#/components/headers/EnergyJoules"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingsResponse"

  /audio/transcriptions:
    post:
      tags: [Inference]
      summary: Whisper-compatible audio transcription
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                model:
                  type: string
                  default: whisper-large-v3
      responses:
        "200":
          description: Transcribed audio.

  /images/generations:
    post:
      tags: [Inference]
      summary: Generate images (FLUX / SDXL)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model:
                  type: string
                  example: flux-schnell
                prompt:
                  type: string
                n:
                  type: integer
                  default: 1
                size:
                  type: string
                  default: "1024x1024"
      responses:
        "200":
          description: Generated images.

  /models:
    get:
      tags: [Inference]
      summary: List available models
      responses:
        "200":
          description: Models.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"

  "/{bucket}/{key}":
    parameters:
      - in: path
        name: bucket
        required: true
        schema: { type: string }
      - in: path
        name: key
        required: true
        schema: { type: string }
    get:
      tags: [ObjectStore]
      summary: GetObject (S3-compatible)
      responses:
        "200":
          description: Object bytes.
          headers:
            X-Energy-Joules:
              $ref: "#/components/headers/EnergyJoules"
    put:
      tags: [ObjectStore]
      summary: PutObject (S3-compatible)
      requestBody:
        required: true
        content:
          "application/octet-stream":
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Object stored.
    delete:
      tags: [ObjectStore]
      summary: DeleteObject (S3-compatible)
      responses:
        "204":
          description: Deleted.

  /workloads:
    get:
      tags: [Workloads]
      summary: List workloads on the account
      responses:
        "200":
          description: Workloads.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Workload"
    post:
      tags: [Workloads]
      summary: Create a workload from an invisible.hcl payload
      requestBody:
        required: true
        content:
          "application/hcl":
            schema:
              type: string
      responses:
        "201":
          description: Workload created.

  "/workloads/{id}":
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string }
    get:
      tags: [Workloads]
      summary: Get a workload's status
      responses:
        "200":
          description: Workload.
    delete:
      tags: [Workloads]
      summary: Delete a workload
      responses:
        "204":
          description: Deleted.

  "/receipts/{id}":
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string }
    get:
      tags: [Receipts]
      summary: Get a signed receipt
      responses:
        "200":
          description: A signed receipt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Receipt"

  /usage/balance:
    get:
      tags: [Usage]
      summary: Account balance
      responses:
        "200":
          description: Balance.
          content:
            application/json:
              schema:
                type: object
                properties:
                  joules_remaining: { type: number }
                  usd_remaining:    { type: number }
                  projected_runway_days: { type: number }

  /usage/since:
    get:
      tags: [Usage]
      summary: Energy used since a given timestamp
      parameters:
        - in: query
          name: since
          required: true
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Usage rollup.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: jc_LIVE_xxxxx

  headers:
    EnergyJoules:
      description: Total joules consumed by this request, at the silicon.
      schema: { type: string, example: "0.31" }
    Carbon:
      description: CO₂eq for this request, milligrams.
      schema: { type: string, example: "0.07" }
    RoutedTo:
      description: provider/region/silicon-class the request landed on.
      schema: { type: string, example: "nebius/eu-helsinki/h100-sxm5" }
    Tier:
      description: Cost tier the router assigned (L0/L1/L2/L3).
      schema: { type: string, example: "L2" }
    ReceiptId:
      description: ID of the signed receipt for this request.
      schema: { type: string, example: "rcpt_018a3c..." }

  responses:
    InsufficientBalance:
      description: Insufficient account balance. Top up at portal.greenjoules.cloud.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    EnergyBudgetExceeded:
      description: The workload exceeded its `energy_budget`.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model:
          type: string
          example: auto
        messages:
          type: array
          items:
            $ref: "#/components/schemas/Message"
        stream: { type: boolean, default: false }
        max_tokens: { type: integer }
        temperature: { type: number }
        tools:
          type: array
          items: { type: object }

    Message:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant, tool]
        content:
          oneOf:
            - { type: string }
            - { type: array, items: { type: object } }

    ChatCompletion:
      type: object
      properties:
        id: { type: string }
        model: { type: string }
        choices:
          type: array
          items:
            type: object
            properties:
              message:
                $ref: "#/components/schemas/Message"
              finish_reason:
                type: string

    EmbeddingsRequest:
      type: object
      required: [model, input]
      properties:
        model:
          type: string
          example: text-embedding-3-small
        input:
          oneOf:
            - { type: string }
            - { type: array, items: { type: string } }

    EmbeddingsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              embedding:
                type: array
                items: { type: number }

    ModelList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              tier: { type: string }
              typical_joules_per_request: { type: number }

    Workload:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        status:
          type: string
          enum: [pending, running, suspended, deleted]
        region: { type: string }
        cpu: { type: string }
        memory: { type: string }
        energy_joules_24h: { type: number }

    Receipt:
      type: object
      properties:
        receipt_id: { type: string }
        workload:
          type: object
          properties:
            id: { type: string }
            kind: { type: string }
        silicon:
          type: object
          properties:
            vendor: { type: string }
            model: { type: string }
            operator: { type: string }
            region: { type: string }
        execution:
          type: object
          properties:
            start_unix_ns: { type: integer }
            duration_ms: { type: integer }
        energy:
          type: object
          properties:
            total_joules: { type: number }
            method: { type: string }
            method_confidence: { type: string }
        carbon:
          type: object
          properties:
            gco2eq_total: { type: number }
            grid_intensity_gco2_per_kwh: { type: number }
        verification:
          type: object
          properties:
            signature_algo: { type: string, example: ed25519 }
            signature: { type: string }

    Error:
      type: object
      properties:
        type: { type: string }
        message: { type: string }
        code: { type: integer }
