openapi: 3.1.0
info:
  title: Impossibl AI API
  version: 0.0.1
  description: |
    The Impossibl AI API is one API for all AI providers, OpenAI-compatible across OpenAI,
    Anthropic, Google, xAI, and more (`GET /v1/models` lists the full catalog),
    billed from a prepaid credit balance.

    Provider usage is billed at provider list prices with no usage markup.
    Prepaid credit purchases charge a separate fixed 5% platform fee before tax.

    **Agent quickstart**
    1. `POST /v1/accounts` — create a funded account; you get an API key and a
       signup-bonus balance in one call (no human step required).
    2. Call `POST /v1/chat/completions`, `POST /v1/responses`, or
       `POST /v1/audio/transcriptions` with
       `Authorization: Bearer <key>` and a `provider/model` id from `GET /v1/models`.
    3. Credits are deducted per request by token or audio-duration usage; check `GET /v1/account`.
servers:
  - url: https://api.impossibl.com
    description: Production
  - url: http://localhost:8787
    description: Local dev
security:
  - BearerKey: []
paths:
  /auth.md:
    get:
      operationId: getAuthMd
      summary: Get executable agent onboarding manifest
      security: []
      responses:
        '200':
          description: Markdown auth.md manifest
          content:
            text/markdown:
              schema: { type: string }
  /.well-known/auth.md:
    get:
      operationId: getWellKnownAuthMd
      summary: Get auth.md manifest from the well-known compatibility path
      security: []
      responses:
        '200':
          description: Markdown auth.md manifest
          content:
            text/markdown:
              schema: { type: string }
  /.well-known/oauth-protected-resource:
    get:
      operationId: getOAuthProtectedResourceMetadata
      summary: Get OAuth protected-resource metadata for agent discovery
      security: []
      responses:
        '200':
          description: Protected resource metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  resource: { type: string, format: uri }
                  resource_name: { type: string }
                  resource_logo_uri: { type: string, format: uri }
                  authorization_servers:
                    type: array
                    items: { type: string, format: uri }
                  scopes_supported:
                    type: array
                    items: { type: string }
                  bearer_methods_supported:
                    type: array
                    items: { type: string }
                  agent_auth_uri: { type: string, format: uri }
                  agent_auth:
                    type: object
                    properties:
                      skill: { type: string, format: uri }
                      register_uri: { type: string, format: uri }
                      identity_types_supported:
                        type: array
                        items: { type: string }
                      credential_types_supported:
                        type: array
                        items: { type: string }
                      credential_format: { type: string }
  /v1/models:
    get:
      operationId: listModels
      summary: List available models and pricing
      security: []
      responses:
        '200':
          description: Model list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, const: list }
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Model' }
  /v1/accounts:
    post:
      operationId: createAccount
      summary: Create a funded account (agent onboarding)
      description: Creates an account, grants the signup bonus, and mints a first API key. The raw key is returned exactly once.
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                email: { type: string, format: email }
      responses:
        '201':
          description: Account created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CreatedAccount' }
  /v1/account:
    get:
      operationId: getAccount
      summary: Get the authenticated account and balance
      responses:
        '200':
          description: Account + balance
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AccountBalance' }
  /v1/keys:
    get:
      operationId: listKeys
      summary: List API keys (metadata only)
      responses:
        '200': { description: Key list }
    post:
      operationId: createKey
      summary: Mint a new API key
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        '201': { description: Key created (raw key shown once) }
  /v1/keys/{id}:
    patch:
      operationId: renameKey
      summary: Rename an API key
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, minLength: 1, maxLength: 60 }
      responses:
        '200': { description: Key metadata with the new name }
        '400': { description: '`name` must be 1-60 characters' }
        '404': { description: Not found }
    delete:
      operationId: revokeKey
      summary: Revoke an API key
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200': { description: Revoked }
        '404': { description: Not found }
  /v1/provider-keys:
    get:
      operationId: listProviderKeys
      summary: List BYOK provider credentials (metadata only)
      description: See the [BYOK guide](/byok). Never returns the stored credential itself.
      responses:
        '200': { description: Credential list }
    post:
      operationId: createProviderKey
      summary: Add (or replace) a BYOK provider credential
      description: |
        Verified with one live call before being stored; a bad key or Azure config
        is rejected with the provider's own error and nothing is persisted.
        Resubmitting for the same `provider` replaces the existing credential.
        See the [BYOK guide](/byok) for the per-provider field requirements.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [provider]
              properties:
                provider:
                  type: string
                  enum: [openai, anthropic, google, xai, zai, bedrock, azure, azure-foundry]
                label: { type: string }
                apiKey: { type: string, description: 'All providers; for Bedrock this is a Bedrock API key (bearer token)' }
                resourceName: { type: string, description: Azure only }
                apiVersion: { type: string, description: Azure only }
                endpoint: { type: string, description: azure-foundry only }
                region: { type: string, description: 'Bedrock only: the AWS region the key was generated for' }
                modelDeployments:
                  type: object
                  description: 'azure and azure-foundry: gateway model id -> your deployment name'
                  additionalProperties: { type: string }
      responses:
        '201': { description: Credential stored (never echoes the secret) }
        '400': { description: 'Invalid shape, or verification against the live provider failed' }
  /v1/provider-keys/{id}:
    delete:
      operationId: revokeProviderKey
      summary: Revoke a BYOK provider credential
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200': { description: Revoked }
        '404': { description: Not found }
  /v1/usage:
    get:
      operationId: getUsage
      summary: Recent usage ledger
      responses:
        '200': { description: Usage entries }
  /v1/usage/stream:
    get:
      operationId: streamUsage
      summary: Live usage stream (SSE)
      description: >-
        Server-sent events. On connect, a `snapshot` event with the last 20 request
        log rows, the credit balance, the server-authoritative month-to-date totals
        (`monthToDate`: tokens, spentCredits, requests) and their read-time `cutoff`;
        then one `request` event per settled or failed request. Live `request` frames
        carry `balanceCredits`; a request replayed just after the snapshot omits it.
        Event data is JSON with `v: 1`. Heartbeat comments every 25s. Log rows only,
        payloads are never streamed.
      responses:
        '200': { description: A text/event-stream of snapshot and request events }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /v1/requests:
    get:
      operationId: listRequests
      summary: Recent request log
      description: Every gateway request including failures, newest first. Rows only, no payloads.
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
        - { name: since, in: query, schema: { type: string, format: date-time }, description: Only rows at or after this ISO timestamp (inclusive filter) }
        - { name: before_id, in: query, schema: { type: string }, description: 'Keyset load-older cursor: only rows strictly older than this row id. An id that does not resolve for the account returns an empty page' }
        - { name: status, in: query, schema: { type: string, enum: [ok, provider_error, insufficient_balance, rate_limited, invalid_request, timeout, client_aborted] } }
        - { name: model, in: query, schema: { type: string }, description: Matches the requested or served model id }
      responses:
        '200': { description: 'Request log rows, newest first' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /v1/requests/{id}:
    get:
      operationId: getRequest
      summary: One request with its captured payload
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        '200': { description: The request row plus its redacted request/response payload }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { description: Unknown request id }
  /agent/identity/claim:
    post:
      operationId: startAccountClaim
      summary: Start a claim ceremony (hand the account to a human)
      description: |
        Begins the WorkOS auth.md "user claimed flow". Authenticated by the agent's
        API key; the account claimed is that key's account. Returns an RFC 8628-shaped
        `user_code` + `verification_uri`. Show them to the human, who signs in with
        WorkOS as `email` and enters the code; poll POST /oauth2/token until claimed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email, description: The human who will claim the account; their verified email must match. }
      responses:
        '201':
          description: Claim ceremony initiated
          content:
            application/json:
              schema:
                type: object
                properties:
                  claim_attempt_id: { type: string }
                  status: { type: string, example: initiated }
                  expires_at: { type: string, format: date-time }
                  claim_attempt:
                    type: object
                    properties:
                      user_code: { type: string, example: '123456' }
                      expires_in: { type: integer, example: 900 }
                      interval: { type: integer, example: 5 }
                      verification_uri: { type: string }
                      verification_uri_complete: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /oauth2/token:
    post:
      operationId: pollAccountClaim
      summary: Poll a claim ceremony (claim grant)
      description: |
        Polls the claim ceremony with `grant_type=urn:workos:agent-auth:grant-type:claim`.
        Authenticate with the account API key (bearer, or the `claim_token` form field).
        Returns `authorization_pending` until the human finishes, `expired_token` if the
        code window closed, `access_denied` if the account was already claimed by someone
        else, or a `claimed` success once bound. A human who already has an account gets
        the agent account merged into it (balance and keys carry over), not a denial.
        The API key keeps working after a successful claim.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type]
              properties:
                grant_type: { type: string, example: 'urn:workos:agent-auth:grant-type:claim' }
                claim_token: { type: string, description: The account API key, if not sent as a bearer token. }
      responses:
        '200':
          description: Account claimed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: claimed }
                  account:
                    type: object
                    properties:
                      id: { type: string }
                      email: { type: string, nullable: true }
        '400':
          description: "Pending or terminal ceremony state: `authorization_pending`, `expired_token`, `access_denied`, or `unsupported_grant_type`."
        '401': { $ref: '#/components/responses/Unauthorized' }
  /v1/claims/complete:
    post:
      operationId: completeAccountClaim
      summary: Complete a claim (called by the frontend after WorkOS sign-in)
      description: |
        Binds the agent-created account to the signed-in human. Called by the Impossibl
        web app after WorkOS AuthKit sign-in, authenticated with the human's WorkOS
        access token (`Authorization: Bearer <workos access token>`), which the gateway
        verifies via JWKS. The signed-in verified email must match the email the agent
        named when starting the claim, and the account must not already be claimed.
      security:
        - BearerKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claim_attempt_token, user_code]
              properties:
                claim_attempt_token: { type: string }
                user_code: { type: string, example: '123456' }
      responses:
        '200':
          description: Account claimed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: claimed }
                  account:
                    type: object
                    properties:
                      id: { type: string }
                      email: { type: string, nullable: true }
        '400': { description: "code/email mismatch, unverified email, or an expired/invalid claim" }
        '401': { description: "missing or invalid WorkOS access token" }
        '409': { description: "the account is already claimed by another user" }
        '503': { description: "WorkOS not configured" }
  /v1/billing:
    get:
      operationId: getBilling
      summary: Current balance and top-up pricing
      responses:
        '200':
          description: Balance, fixed top-up fee, provider-usage markup, and Stripe status
          content:
            application/json:
              schema:
                type: object
                properties:
                  balance: { $ref: '#/components/schemas/Balance' }
                  min_topup_usd: { type: number, example: 5 }
                  platform_fee_rate: { type: number, const: 0.05, description: Fixed fee added to net prepaid credit purchases }
                  usage_price_markup: { type: number, example: 1, description: Current provider-usage multiplier; production uses 1 for no usage markup }
                  stripe_configured: { type: boolean }
  /v1/billing/invoice:
    post:
      operationId: createBillingInvoice
      summary: Buy net credits with a hosted Stripe Invoice
      description: A fixed 5% platform fee is added to the net credit amount before applicable tax. Provider usage remains billed at provider list prices with no usage markup.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount_usd: { type: number, minimum: 5, description: Desired net credit amount in USD; fractional dollars are rounded down }
      responses:
        '200':
          description: Hosted invoice URL and pre-tax billing breakdown
          content:
            application/json:
              schema:
                type: object
                properties:
                  invoice_url: { type: string }
                  invoice_id: { type: string }
                  credit_amount_cents: { type: integer, description: Net prepaid credits purchased, in cents, example: 1000 }
                  platform_fee_cents: { type: integer, description: Fixed 5% platform fee, in cents, example: 50 }
                  invoice_amount_cents: { type: integer, description: Credits plus platform fee before tax, in cents, example: 1050 }
        '503': { description: Billing not configured }
  /v1/billing/webhook:
    post:
      operationId: stripeBillingWebhook
      summary: Verify Stripe webhook and credit paid invoices
      description: "Handles invoice.paid. This is the wallet crediting source of truth; Supabase Stripe Sync Engine is only a mirror for analytics/reconciliation."
      responses:
        '200': { description: Webhook received }
        '400': { description: Missing/invalid Stripe signature }
        '503': { description: Billing not configured }
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      summary: Create a chat completion (OpenAI-compatible)
      description: "Drop-in compatible with OpenAI's Chat Completions API. Set `stream: true` for SSE."
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChatCompletionRequest' }
      responses:
        '200':
          description: |
            A `chat.completion` object. When `stream: true`, the body is instead an
            SSE stream of `chat.completion.chunk` objects terminated by `data: [DONE]`.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChatCompletion' }
            text/event-stream:
              schema: { $ref: '#/components/schemas/ChatCompletionChunk' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '404': { $ref: '#/components/responses/UnknownModel' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/ProviderNotConfigured' }
  /v1/images/generations:
    post:
      operationId: createImageGeneration
      summary: Generate images (OpenAI-compatible)
      description: |
        OpenAI-compatible image generation with `openai/gpt-image-2`. Returns
        base64-encoded image bytes (never URLs). Billed per token — text input
        plus image output tokens — from the prepaid balance; there is no BYOK
        path for image generation yet.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model:
                  type: string
                  enum: [openai/gpt-image-2]
                prompt:
                  type: string
                n:
                  type: integer
                  default: 1
                  description: Number of images to generate.
                size:
                  type: string
                  description: "`{width}x{height}` (e.g. `1024x1024`, `1536x1024`) or `auto`."
                quality:
                  type: string
                  enum: [auto, low, medium, high, standard, hd]
                background:
                  type: string
                  enum: [auto, transparent, opaque]
                output_format:
                  type: string
                  enum: [png, jpeg, webp]
                output_compression:
                  type: integer
                  minimum: 0
                  maximum: 100
                  description: JPEG/WebP only.
                user:
                  type: string
                  description: Stable per-user id forwarded to the provider for abuse monitoring.
      responses:
        '200':
          description: Generated images with token usage.
          content:
            application/json:
              schema:
                type: object
                properties:
                  created: { type: integer }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        b64_json: { type: string, description: Base64-encoded image bytes. }
                        revised_prompt: { type: string }
                  usage:
                    type: object
                    properties:
                      total_tokens: { type: integer }
                      input_tokens: { type: integer }
                      output_tokens: { type: integer }
                      input_tokens_details:
                        type: object
                        properties:
                          text_tokens: { type: integer }
                          image_tokens: { type: integer }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '404': { $ref: '#/components/responses/UnknownModel' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/ProviderNotConfigured' }
  /v1/audio/transcriptions:
    post:
      operationId: createAudioTranscription
      summary: Transcribe a completed audio file
      description: |
        OpenAI-compatible file transcription with `openai/gpt-transcribe`.
        Set `stream: true` to receive transcript events over SSE. Successful
        requests are billed at $0.0045 per audio minute.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [model, file]
              properties:
                model:
                  type: string
                  enum: [openai/gpt-transcribe]
                file:
                  type: string
                  format: binary
                  maxLength: 26214400
                  description: Completed audio file accepted by the OpenAI Transcriptions API.
                prompt:
                  type: string
                  description: Unstructured context about the recording.
                'keywords[]':
                  type: array
                  items: { type: string }
                  description: Literal terms expected in the recording. Repeat this multipart field for multiple terms.
                'languages[]':
                  type: array
                  items: { type: string }
                  description: Expected language codes. Repeat this multipart field for multiple languages.
                response_format:
                  type: string
                  enum: [json, text]
                  default: json
                stream:
                  type: boolean
                  default: false
      responses:
        '200':
          description: A completed transcript object, or transcript events when `stream` is true.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AudioTranscription' }
            text/plain:
              schema: { type: string }
            text/event-stream:
              schema: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '413': { $ref: '#/components/responses/PayloadTooLarge' }
        '404': { $ref: '#/components/responses/UnknownModel' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/ProviderNotConfigured' }
  /v1/responses:
    post:
      operationId: createResponse
      summary: Create a response (OpenAI Responses API)
      description: "Implements OpenAI's Responses API for text. Set `stream: true` for semantic-event SSE."
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ResponsesRequest' }
      responses:
        '200':
          description: |
            A `response` object. When `stream: true`, the body is instead an SSE
            stream of `response.*` semantic events ending with `response.completed`.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ResponseObject' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '404': { $ref: '#/components/responses/UnknownModel' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/ProviderNotConfigured' }
  /v1/messages:
    post:
      operationId: createMessage
      summary: Create a message (Anthropic Messages API)
      description: "Drop-in compatible with Anthropic's Messages API. Authenticate with `x-api-key` (or `Authorization: Bearer`). A bare model id like `claude-haiku-4-5` routes to Anthropic; use `provider/model` for others. Set `stream: true` for SSE."
      security:
        - BearerKey: []
        - ApiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/MessagesRequest' }
      responses:
        '200':
          description: |
            A `message` object. When `stream: true`, the body is an SSE stream of
            Anthropic events (`message_start`, `content_block_delta`, …, `message_stop`).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AnthropicMessage' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '404': { $ref: '#/components/responses/UnknownModel' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/ProviderNotConfigured' }
components:
  securitySchemes:
    BearerKey:
      type: http
      scheme: bearer
      description: Gateway API key (`imp-rt-...`) from POST /v1/accounts or /v1/keys.
    ApiKeyHeader:
      type: apiKey
      in: header
      name: x-api-key
      description: Gateway API key via the `x-api-key` header (what the Anthropic SDK sends).
  responses:
    BadRequest:
      description: Malformed request (bad JSON, or missing required fields).
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: '`model` and `messages` are required', type: invalid_request_error } }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'Invalid API key', type: authentication_error } }
    InsufficientCredits:
      description: The account's credit balance is empty. Top up to continue.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'Insufficient credits. Top up to continue.', type: insufficient_quota } }
    PayloadTooLarge:
      description: The uploaded audio file exceeds 25 MB.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'Maximum file size is 25 MB.', type: invalid_request_error, param: file, code: file_too_large } }
    UnknownModel:
      description: The requested `model` id is not in the registry (see GET /v1/models).
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'Unknown model: foo/bar', type: invalid_request_error } }
    UpstreamError:
      description: The upstream provider returned an error.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'permission-denied: ...', type: upstream_error } }
    ProviderNotConfigured:
      description: The provider for this model has no API key configured on the gateway.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { error: { message: 'Provider not configured: missing OPENAI_API_KEY', type: provider_not_configured } }
  schemas:
    Model:
      type: object
      properties:
        id: { type: string, example: anthropic/claude-sonnet-4-5 }
        object: { type: string, const: model }
        owned_by: { type: string }
        context_window: { type: integer }
        input_modalities:
          type: array
          items: { type: string, enum: [text, image, audio, video] }
          description: Doc-verified input modalities. Omitted means unverified, not text-only. Audio/video are enforced (400 on unsupported parts).
        output_modalities:
          type: array
          items: { type: string }
        endpoints:
          type: array
          items: { type: string }
        pricing:
          type: object
          properties:
            input_per_mtok_usd: { type: number }
            output_per_mtok_usd: { type: number }
            audio_input_per_minute_usd: { type: number }
            audio_input_per_mtok_usd: { type: number, description: Audio input rate where a model prices audio above text (Gemini Flash). }
    AudioTranscription:
      type: object
      required: [text, languages]
      properties:
        text: { type: string }
        languages:
          type: array
          items:
            type: object
            properties:
              code: { type: string }
        usage:
          type: object
          description: Present when OpenAI reports duration usage.
          properties:
            type: { type: string, const: duration }
            seconds: { type: number }
    CreatedAccount:
      type: object
      properties:
        account:
          type: object
          properties:
            id: { type: string }
            email: { type: string, nullable: true }
            created_at: { type: string, format: date-time }
        api_key: { type: string, description: Raw key, shown once }
        balance: { $ref: '#/components/schemas/Balance' }
    AccountBalance:
      type: object
      properties:
        account: { type: object }
        balance: { $ref: '#/components/schemas/Balance' }
    Balance:
      type: object
      properties:
        credits: { type: integer, description: micro-USD (1,000,000 = $1) }
        usd: { type: number }
    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model: { type: string, example: openai/gpt-4o-mini }
        messages:
          type: array
          items:
            type: object
            properties:
              role: { type: string, enum: [system, developer, user, assistant, tool] }
              content:
                description: A text string, or an array of content parts (text, image_url, input_audio, video_url) for multimodal input.
                oneOf:
                  - type: string
                  - type: array
                    items: { $ref: '#/components/schemas/ChatContentPart' }
                  - type: 'null'
              tool_call_id: { type: string, description: On `tool` messages, the id of the tool call this result is for. }
              tool_calls:
                type: array
                description: On `assistant` messages, tool calls being relayed back to the model.
                items: { $ref: '#/components/schemas/ChatToolCall' }
        stream: { type: boolean, default: false }
        stream_options:
          type: object
          properties:
            include_usage: { type: boolean }
        temperature: { type: number }
        top_p: { type: number }
        max_tokens: { type: integer }
        max_completion_tokens: { type: integer }
        tools:
          type: array
          description: Function tools the model may call (relay mode — the gateway does not execute them).
          items: { $ref: '#/components/schemas/ChatTool' }
        tool_choice:
          description: '`auto`, `none`, `required`, or `{ type: function, function: { name } }`.'
          oneOf:
            - { type: string, enum: [auto, none, required] }
            - type: object
              properties:
                type: { type: string, const: function }
                function:
                  type: object
                  properties: { name: { type: string } }
        parallel_tool_calls: { type: boolean }
        reasoning_effort:
          type: string
          enum: [none, minimal, low, medium, high, xhigh]
          description: Forwarded across native OpenAI, Azure OpenAI, and OpenAI-compatible routes.
    ChatContentPart:
      type: object
      properties:
        type: { type: string, enum: [text, image_url, input_audio, video_url] }
        text: { type: string, description: Present when `type` is `text`. }
        image_url:
          type: object
          description: Present when `type` is `image_url`.
          properties:
            url: { type: string, description: An http(s) URL or a `data:` base64 URI. }
            detail: { type: string, enum: [auto, low, high] }
        input_audio:
          type: object
          description: Present when `type` is `input_audio`. Only models advertising `audio` in `input_modalities` accept it.
          properties:
            data: { type: string, description: Base64-encoded audio (no data-URI prefix). }
            format: { type: string, description: '`wav` or `mp3` (`aiff`, `aac`, `ogg`, `flac` also pass through).' }
        video_url:
          type: object
          description: Present when `type` is `video_url`. Only models advertising `video` in `input_modalities` accept it.
          properties:
            url: { type: string, description: 'A `data:` base64 URI or an http(s) URL. YouTube URLs pass through to Gemini; other URLs are fetched by the gateway and sent inline (~20MB limit).' }
    ChatTool:
      type: object
      required: [type, function]
      properties:
        type: { type: string, const: function }
        function:
          type: object
          required: [name]
          properties:
            name: { type: string }
            description: { type: string }
            parameters: { type: object, description: JSON Schema for the tool input. }
    ChatToolCall:
      type: object
      description: A complete tool call (non-streaming). `function.arguments` is a JSON-encoded string.
      properties:
        id: { type: string }
        type: { type: string, const: function }
        function:
          type: object
          properties:
            name: { type: string }
            arguments: { type: string, description: JSON-encoded arguments string. }
    ResponsesRequest:
      type: object
      required: [model, input]
      properties:
        model: { type: string, example: anthropic/claude-sonnet-4-5 }
        input:
          oneOf:
            - type: string
            - type: array
              items:
                type: object
                description: A role message, or a `function_call` / `function_call_output` item on follow-up turns.
                properties:
                  type: { type: string, enum: [message, function_call, function_call_output] }
                  role: { type: string, enum: [system, developer, user, assistant] }
                  content:
                    description: A text string, or an array of input parts (input_text, input_image, input_audio).
                    oneOf:
                      - type: string
                      - type: array
                        items: { $ref: '#/components/schemas/ResponsesInputContentPart' }
                  call_id: { type: string, description: Correlates a function_call and its function_call_output. }
                  name: { type: string }
                  arguments: { type: string, description: JSON-encoded arguments string (on function_call). }
                  output: { type: string, description: Tool result text (on function_call_output). }
        instructions: { type: string }
        stream: { type: boolean, default: false }
        temperature: { type: number }
        top_p: { type: number }
        max_output_tokens: { type: integer }
        reasoning:
          type: object
          description: Stateless reasoning control. `summary` and stateful response chaining are not supported.
          properties:
            effort: { type: string, enum: [none, minimal, low, medium, high, xhigh], nullable: true }
        tools:
          type: array
          description: Function tools the model may call (relay mode). Function fields are flattened onto the tool.
          items:
            type: object
            required: [type, name]
            properties:
              type: { type: string, const: function }
              name: { type: string }
              description: { type: string }
              parameters: { type: object, description: JSON Schema for the tool input. }
              strict: { type: boolean }
        tool_choice:
          description: '`auto`, `none`, `required`, or `{ type: function, name }`.'
          oneOf:
            - { type: string, enum: [auto, none, required] }
            - type: object
              properties:
                type: { type: string, const: function }
                name: { type: string }
    ResponsesInputContentPart:
      type: object
      properties:
        type: { type: string, enum: [input_text, input_image, input_audio] }
        text: { type: string, description: Present when `type` is `input_text`. }
        image_url:
          description: 'Present when `type` is `input_image`: an http(s) URL or a `data:` URI, as a string or `{ url }`.'
          oneOf:
            - type: string
            - type: object
              properties: { url: { type: string } }
        input_audio:
          type: object
          description: Present when `type` is `input_audio`. Only models advertising `audio` in `input_modalities` accept it.
          properties:
            data: { type: string, description: Base64-encoded audio (no data-URI prefix). }
            format: { type: string, description: '`wav` or `mp3` (`aiff`, `aac`, `ogg`, `flac` also pass through).' }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message: { type: string }
            type:
              type: string
              description: One of authentication_error, invalid_request_error, insufficient_quota, upstream_error, provider_not_configured.
    Usage:
      type: object
      description: OpenAI-style token usage.
      properties:
        prompt_tokens: { type: integer }
        completion_tokens: { type: integer }
        total_tokens: { type: integer }
    ChatCompletion:
      type: object
      description: Non-streaming chat completion (OpenAI `chat.completion`).
      properties:
        id: { type: string, example: chatcmpl-... }
        object: { type: string, const: chat.completion }
        created: { type: integer, description: Unix seconds }
        model: { type: string, example: openai/gpt-4o-mini }
        choices:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              finish_reason: { type: string, enum: [stop, length, content_filter, tool_calls], nullable: true }
              message:
                type: object
                properties:
                  role: { type: string, const: assistant }
                  content: { type: string, nullable: true }
                  tool_calls:
                    type: array
                    description: Present when the model called tools (finish_reason `tool_calls`).
                    items: { $ref: '#/components/schemas/ChatToolCall' }
        usage: { $ref: '#/components/schemas/Usage' }
    ChatCompletionChunk:
      type: object
      description: "One SSE chunk (OpenAI `chat.completion.chunk`), sent as a `data:` line; the stream ends with a `[DONE]` sentinel."
      properties:
        id: { type: string }
        object: { type: string, const: chat.completion.chunk }
        created: { type: integer }
        model: { type: string }
        choices:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              finish_reason: { type: string, nullable: true }
              delta:
                type: object
                description: Incremental update — `role` on the first chunk, then `content` deltas, or `tool_calls` fragments.
                properties:
                  role: { type: string }
                  content: { type: string }
                  tool_calls:
                    type: array
                    description: Streaming tool-call fragments; `index` correlates fragments of the same call.
                    items:
                      type: object
                      properties:
                        index: { type: integer }
                        id: { type: string }
                        type: { type: string, const: function }
                        function:
                          type: object
                          properties:
                            name: { type: string }
                            arguments: { type: string }
        usage:
          description: Present only on the final chunk when `stream_options.include_usage` is true.
          oneOf:
            - $ref: '#/components/schemas/Usage'
            - type: 'null'
    MessagesRequest:
      type: object
      required: [model, max_tokens, messages]
      properties:
        model: { type: string, example: claude-haiku-4-5 }
        max_tokens: { type: integer, description: Required by the Anthropic API. }
        messages:
          type: array
          items:
            type: object
            properties:
              role: { type: string, enum: [user, assistant] }
              content:
                description: A text string, or an array of content blocks (text, image, tool_use, tool_result).
                oneOf:
                  - type: string
                  - type: array
                    items: { $ref: '#/components/schemas/AnthropicContentBlock' }
        system: { type: string, description: Top-level system prompt. }
        stream: { type: boolean, default: false }
        temperature: { type: number }
        top_p: { type: number }
        top_k: { type: integer }
        stop_sequences: { type: array, items: { type: string } }
        thinking:
          description: Anthropic thinking control, preserved on native Anthropic and Bedrock routes.
          oneOf:
            - type: object
              required: [type, budget_tokens]
              properties:
                type: { type: string, const: enabled }
                budget_tokens: { type: integer, minimum: 1024 }
            - type: object
              required: [type]
              properties:
                type: { type: string, const: adaptive }
                display: { type: string, enum: [omitted, summarized] }
            - type: object
              required: [type]
              properties:
                type: { type: string, const: disabled }
        output_config:
          type: object
          properties:
            effort: { type: string, enum: [low, medium, high, xhigh, max] }
        tools:
          type: array
          description: Tools the model may call (relay mode — the gateway does not execute them).
          items:
            type: object
            required: [name, input_schema]
            properties:
              name: { type: string }
              description: { type: string }
              input_schema: { type: object, description: JSON Schema for the tool input. }
        tool_choice:
          description: '`{ type: auto }`, `{ type: any }`, `{ type: tool, name }`, or `{ type: none }`.'
          type: object
          properties:
            type: { type: string, enum: [auto, any, tool, none] }
            name: { type: string }
    AnthropicContentBlock:
      type: object
      description: A content block on a message — text, image, tool_use, or tool_result.
      properties:
        type: { type: string, enum: [text, image, tool_use, tool_result] }
        text: { type: string, description: Present when `type` is `text`. }
        source:
          type: object
          description: Present when `type` is `image`.
          properties:
            type: { type: string, enum: [base64, url] }
            media_type: { type: string, description: 'For base64, e.g. `image/png`.' }
            data: { type: string, description: For base64, the raw base64 image data. }
            url: { type: string, description: For url, an http(s) URL. }
        id: { type: string, description: tool_use block id. }
        name: { type: string, description: tool_use tool name. }
        input: { type: object, description: tool_use parsed input. }
        tool_use_id: { type: string, description: tool_result — the tool_use id it answers. }
        content:
          description: tool_result content — a string or nested blocks.
          oneOf:
            - type: string
            - type: array
              items: { type: object }
        is_error: { type: boolean }
    AnthropicMessage:
      type: object
      description: Non-streaming Anthropic `message` response.
      properties:
        id: { type: string, example: msg_... }
        type: { type: string, const: message }
        role: { type: string, const: assistant }
        model: { type: string }
        content:
          type: array
          description: Text blocks and, when the model calls a tool, tool_use blocks.
          items:
            type: object
            properties:
              type: { type: string, enum: [text, tool_use] }
              text: { type: string, description: Present on `text` blocks. }
              id: { type: string, description: Present on `tool_use` blocks. }
              name: { type: string, description: Present on `tool_use` blocks. }
              input: { type: object, description: Parsed tool input on `tool_use` blocks. }
        stop_reason: { type: string, enum: [end_turn, max_tokens, stop_sequence, tool_use], nullable: true }
        stop_sequence: { type: string, nullable: true }
        usage:
          type: object
          properties:
            input_tokens: { type: integer }
            output_tokens: { type: integer }
    ResponseObject:
      type: object
      description: Responses API object (`response`).
      properties:
        id: { type: string, example: resp_... }
        object: { type: string, const: response }
        created_at: { type: integer }
        status: { type: string, enum: [in_progress, completed, failed] }
        model: { type: string }
        output_text: { type: string, description: Convenience aggregation of the assistant text. }
        output:
          type: array
          description: Output items — assistant `message` items and, on tool calls, `function_call` items.
          items:
            type: object
            properties:
              type: { type: string, enum: [message, function_call] }
              id: { type: string }
              status: { type: string }
              role: { type: string, const: assistant, description: Present on `message` items. }
              content:
                type: array
                description: Present on `message` items.
                items:
                  type: object
                  properties:
                    type: { type: string, const: output_text }
                    text: { type: string }
                    annotations: { type: array, items: { type: object } }
              call_id: { type: string, description: Present on `function_call` items. }
              name: { type: string, description: Present on `function_call` items. }
              arguments: { type: string, description: 'Present on `function_call` items: JSON-encoded arguments string.' }
        usage:
          type: object
          properties:
            input_tokens: { type: integer }
            output_tokens: { type: integer }
            total_tokens: { type: integer }
