openapi: 3.1.0
info:
  title: ScopeVeil Gateway API
  description: |
    OpenAI-compatible LLM gateway. Point your existing OpenAI SDK at
    `https://gateway.scopeveil.com` with a `scv_live_*` API key as the
    Bearer token. Cost is calculated server-side from upstream token counts
    plus a per-tier markup, charged immediately against your prepaid
    balance. Top up and manage keys at https://scopeveil.com/dashboard.

    ## Quick start

    ```ts
    import OpenAI from 'openai';
    import { ScopeVeil } from '@scopeveil/sdk';

    const openai = new OpenAI({
      apiKey: process.env.SCOPEVEIL_API_KEY,
      baseURL: ScopeVeil.gatewayBaseURL(),
    });

    const r = await openai.chat.completions.create({
      model: 'anthropic/claude-sonnet-4-6',
      messages: [{ role: 'user', content: 'hi' }],
    });
    ```

    Model identifiers are prefixed with the provider: `openai/gpt-4o`,
    `anthropic/claude-sonnet-4-6`, `google/gemini-2.0-flash`. Without a
    prefix the model is treated as OpenAI.
  version: "1"
  contact:
    name: ScopeVeil
    url: https://scopeveil.com/contact
  license:
    name: Proprietary
servers:
  - url: https://gateway.scopeveil.com
    description: Production gateway

tags:
  - name: Chat
    description: OpenAI-compatible chat completions.
  - name: Models
    description: Discover the model catalog.
  - name: Balance
    description: Read your prepaid credit balance.
  - name: Health
    description: Liveness probe.

paths:
  /health:
    get:
      tags: [Health]
      summary: Health check
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }
                  service: { type: string, example: gateway }

  /v1/chat/completions:
    post:
      tags: [Chat]
      summary: Create a chat completion
      description: |
        OpenAI-compatible request shape. Routes to the upstream provider
        encoded in `model` (`provider/model`). Returns the upstream response
        verbatim, plus our metering headers:

        - `X-Scopeveil-Cost-Usd` — actual charge in USD (e.g. `0.000412`).
        - `X-Scopeveil-Provider` — resolved upstream provider.
        - `X-Scopeveil-Request-Id` — opaque ID for support tickets.

        Streaming (`stream: true`) returns SSE; partial responses are billed
        only for the tokens actually delivered.

        Failed requests where no upstream tokens were charged are not billed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, messages]
              properties:
                model:
                  type: string
                  description: |
                    Format `provider/model` (e.g. `anthropic/claude-sonnet-4-6`).
                    Without a prefix, OpenAI is assumed.
                  pattern: "^[a-z0-9._-]+(/[a-z0-9._:-]+)?$"
                  maxLength: 120
                  example: anthropic/claude-sonnet-4-6
                messages:
                  type: array
                  items: { $ref: "#/components/schemas/ChatMessage" }
                  minItems: 1
                temperature: { type: number, minimum: 0, maximum: 2 }
                top_p: { type: number, minimum: 0, maximum: 1 }
                max_tokens: { type: integer, minimum: 1, maximum: 200000 }
                stream: { type: boolean, default: false }
                user:
                  type: string
                  description: An opaque end-user identifier you choose (SHA-256 hash recommended).
                stop:
                  oneOf:
                    - { type: string }
                    - { type: array, items: { type: string }, maxItems: 4 }
            examples:
              openai:
                summary: OpenAI gpt-4o
                value:
                  model: openai/gpt-4o
                  messages:
                    - { role: user, content: "Summarize Hamlet in 3 lines." }
              anthropic:
                summary: Anthropic Claude
                value:
                  model: anthropic/claude-sonnet-4-6
                  messages:
                    - { role: user, content: "What is the airspeed velocity of an unladen swallow?" }
                  max_tokens: 256
              streaming:
                summary: Streaming SSE
                value:
                  model: openai/gpt-4o-mini
                  stream: true
                  messages:
                    - { role: user, content: "Write a haiku about caches." }
      responses:
        "200":
          description: Upstream response, OpenAI-compatible
          headers:
            X-Scopeveil-Cost-Usd:
              schema: { type: string }
              description: Actual charge in USD.
            X-Scopeveil-Provider:
              schema: { type: string }
              description: Resolved upstream provider.
            X-Scopeveil-Request-Id:
              schema: { type: string }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatCompletion" }
            text/event-stream:
              schema:
                type: string
                description: SSE stream when `stream: true`.
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "402": { $ref: "#/components/responses/InsufficientBalance" }
        "422": { $ref: "#/components/responses/ValidationError" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /v1/models:
    get:
      tags: [Models]
      summary: List available models
      description: OpenAI-compatible model list. Use these IDs in `model` of `/v1/chat/completions`.
      responses:
        "200":
          description: Models catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, enum: [list] }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Provider-prefixed model identifier.
                          example: anthropic/claude-sonnet-4-6
                        object: { type: string, enum: [model] }
                        owned_by: { type: string, example: anthropic }
                        created: { type: integer, example: 0 }
        "401": { $ref: "#/components/responses/Unauthenticated" }

  /v1/balance:
    get:
      tags: [Balance]
      summary: Prepaid balance of your organization
      description: |
        Same data the dashboard shows. Useful for programmatic budget gating
        in your application.
      responses:
        "200":
          description: Balance in USD
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_usd: { type: number, example: 125.50 }
                  on_hold_usd: { type: number, example: 0.05 }
                  available_usd: { type: number, example: 125.45 }
        "401": { $ref: "#/components/responses/Unauthenticated" }

components:
  securitySchemes:
    BearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: "scv_live_*"
      description: |
        API key generated in the dashboard ([API keys settings](/settings/api-keys)).
        Sent as `Authorization: Bearer scv_live_xxx...`. The OpenAI SDK does
        this automatically when you set `apiKey` in the constructor.

  responses:
    BadRequest:
      description: Malformed request body
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
              message: { type: string }
    Unauthenticated:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string, example: unauthorized }
              reason: { type: string, example: invalid_key }
    InsufficientBalance:
      description: |
        Your organization's prepaid balance is zero or below the estimated
        cost of this request. Top up at app.scopeveil.com/settings/credit.
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string, example: payment_required }
              message: { type: string }
              available_usd: { type: number }
    ValidationError:
      description: Invalid request shape
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string, example: invalid_request }
              issues:
                type: array
                items:
                  type: object
                  additionalProperties: true
    RateLimited:
      description: Too many requests
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string, example: rate_limited }
              retry_after_seconds: { type: integer }
    UpstreamError:
      description: Upstream provider returned an error
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string, example: upstream_error }
              provider: { type: string }
              status: { type: integer }
              message: { type: string }

  schemas:
    ChatMessage:
      type: object
      required: [role, content]
      properties:
        role: { type: string, enum: [system, user, assistant, tool] }
        content:
          oneOf:
            - type: string
            - type: array
              description: Multimodal parts (text + image, etc).
              items:
                type: object
                additionalProperties: true
        name: { type: string }
        tool_call_id: { type: string }

    ChatCompletion:
      type: object
      description: OpenAI ChatCompletion shape, forwarded from the upstream provider.
      properties:
        id: { type: string }
        object: { type: string, example: chat.completion }
        created: { type: integer }
        model: { type: string }
        choices:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              message: { $ref: "#/components/schemas/ChatMessage" }
              finish_reason: { type: string }
        usage:
          type: object
          properties:
            prompt_tokens: { type: integer }
            completion_tokens: { type: integer }
            total_tokens: { type: integer }

security:
  - BearerApiKey: []
