openapi: 3.1.0
info:
  title: Platform.audn.ai API
  description: |
    API documentation for Platform.audn.ai — the OpenAI-compatible API platform for the audn.ai model roster.

    ## Authentication

    All API requests require authentication using an API key. Include your API key in the `Authorization` header:

    ```
    Authorization: Bearer sk_live_your_api_key_here
    ```

    API keys can be created and managed in the [dashboard](/api-keys).

    ## Limits

    **One request-rate limit applies, from 2026-09-14: 500 requests per 60 seconds per client
    IP address across `/api/v1/chat/completions` and `/api/v1/messages`**, enforced at the
    network edge (details below). Beyond that, credit-billed traffic has no usage quotas — no
    hourly, daily, weekly or monthly token or request caps, no tokens-per-minute limit, and no
    plan-level context cap. The one usage budget is the **K3 budget** of the $999 Necromicon
    Unlimited plan and NECROMICON cohort seats, which since 2026-09-28 counts only `omen` (below).
    Responses carry no
    `X-RateLimit-*` headers. Every model is
    available on every plan, each at its own full context window — up to 1,048,576 tokens on
    `necromicon` — though the 4.5 MB request-size ceiling below is the practical input bound
    on the largest models.

    Every limit we enforce is listed here. If a limit is not on this page, we do not enforce
    it. In full:

    - **Credits.** Prepaid. Before each request we tokenize your input and price the worst case
      (estimated input plus your `max_tokens` at the model's rates). If your balance cannot
      cover it the request is refused with `402 insufficient_credits` and nothing is charged.
      Actual usage is deducted atomically on completion. The pre-request check reads your
      balance but does not hold it, so concurrent requests are each checked against the same
      balance.
    - **Request rate.** 500 requests per 60 seconds per client IP address across
      `/api/v1/chat/completions` and `/api/v1/messages`, counted at the network edge in a fixed
      window (kept per edge region, so it is a floor on what is allowed rather than an exact
      cap). Above it, requests from that address receive `429` for 60 seconds — a plain-text
      response produced before the request reaches this API, like the `413` below, so it does
      not arrive as JSON. Clients behind one shared address share the counter. It counts
      requests only; tokens, spend and concurrency are not limited by it. In effect from
      2026-09-14.
    - **Parallel requests.** Every organization may have 3 requests in flight at once, on every
      account, credit-billed included (since 2026-10-07). Each $999 entitlement the
      organization holds — a platform.audn.ai Unlimited plan or a NECROMICON cohort seat —
      allows 3: sessions are $333 each, so two entitlements allow 6. The count is per
      organization, shared by every API key and member across `/api/v1/chat/completions` and
      `/api/v1/messages`; it counts requests in flight, not tokens or spend. Above it a request
      is refused with `429` and code `too_many_parallel_sessions` in the JSON error shape, the
      `X-Pingu-Parallel-Limit` header names the organization's limit, and nothing is charged.
      Retry when one of your in-flight requests finishes. Credit-billed and need more than 3?
      Email support@audn.ai and we'll raise your organization's limit; on such an account the
      refusal carries `how_to_increase` and `contact: "email"`.
    - **Context window.** Input plus `max_tokens` must fit, or the request fails immediately
      with `400 context_length_exceeded`. Per-model, see Models and Pricing.
    - **Request size.** The request body must be at most 4.5 MB. A larger body is refused at
      the network edge with a plain-text `413 FUNCTION_PAYLOAD_TOO_LARGE` — the one error
      that does not arrive as JSON, because our hosting infrastructure produces it before the
      request reaches this API. In practice 4.5 MB holds roughly 750,000–1,000,000 input
      tokens depending on content (code and non-Latin text tokenize denser), so on the
      1,048,576-token models the body size, not the context window, is the practical input
      ceiling — plan on about 756,000 tokens for code-heavy payloads.
    - **Output ceiling.** A `max_tokens` above the model's ceiling is clamped, not rejected;
      where a model has a minimum, values below it are raised. Neither adjustment is reported,
      so read `usage` in the response rather than assuming your value was used.
    - **Timeout.** 720s on every model (since 2026-10-12; was 300s). Exceeding it returns `504`.
    - **$999 month — speed and concurrency (since 2026-10-02).** The platform.audn.ai Unlimited
      plan and a NECROMICON cohort seat (https://audn.ai/necromicon) run unlimited, unrestricted
      DeepSeek V4.1-Flash-1017 (`warlock`) in FAST mode from the day you pay, for a full month: up to
      777 tokens/sec (a peak on an autoscaling cluster — measured 775 on a long agentic profile,
      318 on short real-time requests), no metering, no token limits. Each plan or seat allows
      its organization **3 requests in flight at once** (two allow 6) under the parallel-request
      limit above. FAST is the only speed — there is no slower tier — and renewing keeps it past
      the month. FAST mode itself produces no `429`, no error and no truncated response. Charged immediately, strictly no refunds;
      card seats are one-off or monthly, crypto is one-off. Credit-billed traffic is unaffected.
    - **No usage budget on the $999 package (since 2026-10-02).** The platform.audn.ai Unlimited
      plan and a NECROMICON cohort seat cover `necromicon` and `warlock` only — both serve DeepSeek
      V4.1-Flash-1017 — with no per-token charge, no metering and no usage budget; covered requests
      are never refused for spend and carry no `X-K3-Budget-*` headers. `omen` and `omen-2.0`
      are NOT covered and bill from prepaid
      credits (top up to use them; an empty balance returns `402`). History: from 2026-09-14 each
      $999 payment carried a $1,000 K3-class budget (`429 insufficient_quota` /
      `k3_seat_budget_exhausted` above it); from 2026-09-28 it bound Omen only; it was retired
      2026-10-02. The `k3_seat_budget_exhausted` code is no longer produced.

    ### Shared capacity

    Models run on shared infrastructure that also serves other products, so under load you may
    see elevated latency, `504` timeouts, occasional retryable `429`s, or transparent failover
    to a fallback backend. **None of these depend on your own usage** — a JSON `429` without the
    `too_many_parallel_sessions` code signals contention, not a budget you exhausted, so retry
    with exponential backoff.

    ### Changes to these limits

    We reserve the right to change any limit described here, including introducing rate limits
    or usage quotas where there are none today. When we do, we will announce the change and
    list it in the change log at https://platform.audn.ai/models#limit-changes **before it
    takes effect**, so you can adjust ahead of time.

    **One exception: per-account enforcement against abuse.** We reserve the right to apply
    limits, throttling, or suspension to an individual account at any time and without prior
    notice where we identify abuse — for example attempts to exhaust shared capacity, evade
    billing, or degrade service for other customers. Such enforcement is targeted at the
    account concerned, is not announced in advance, and does not change the limits published
    for everyone else. We may also suspend accounts for non-payment.

    Accordingly, "if a limit is not listed, we do not enforce it" describes our general limits
    on all accounts; it does not restrict the abuse enforcement above.

    ## Models and Pricing

    Pricing is **per model**, not a flat platform rate. Context window, latency and price
    vary by an order of magnitude across the roster.

    | Model | Base model | Context | Typical latency | Region | Input / 1M | Cached input / 1M | Output / 1M |
    |-------|-----------|---------|-----------------|--------|------------|-------------------|-------------|
    | `pingu-unchained-10` | Qwen3.8-27B-AEON-Ultimate-Uncensored (BF16) | 262,144 | ~1s | us-east | $2.00 | — | $8.00 |
    | `kong` | Qwen3.8-27B Abliterated | 262,144 | ~1s | apac | $2.00 | — | $8.00 |
    | `necromicon` | DeepSeek V4.1-Flash-1017 | 1,048,576 | ~16s–5m | us-east | $4.50 | $0.40 | $4.50 |
    | `warlock` | DeepSeek V4.1-Flash-1017 | 1,048,576 | ~0.2s to first token | us-east | $4.50 | $0.40 | $4.50 |

    ### Cached input

    The K3 class — `necromicon` and `warlock` —
    bills input the backend served from its prefix cache at **$0.40 / 1M** instead of $4.50
    (since 2026-09-12). Nothing to enable: send the same prefix again and the discount applies
    on its own. `pricing.cached_input` on `GET /v1/models` carries the rate, and
    `usage.prompt_tokens_details.cached_tokens` reports how many prompt tokens qualified —
    a SUBSET of `prompt_tokens`, never an addition to it. On the Anthropic surface the same
    tokens arrive as `usage.cache_read_input_tokens`.

    No other model has a cache tier: their backends report no cache detail, so there is
    nothing to discount from. For those, `pricing.cached_input` is `null` — never a repeat
    of the input rate — and `cached_tokens` is `0`.

    `pingu-unchained-10` is the recommended default: sub-second latency at the cheapest tier
    of pricing, with a 262,144-token window. Reach for `necromicon` when you need the deepest
    reasoning or the largest window.

    ## Reasoning Output

    Every model except `pingu-unchained-10` reasons before it answers and returns that trace in
    the optional `reasoning_content` field on the assistant message. The final answer is always in
    `content` — never render `reasoning_content` as the answer. Reasoning tokens are generated
    tokens and are billed as output tokens.

    `pingu-unchained-10` runs with thinking disabled server-side; it answers
    directly and never returns `reasoning_content`.

    ## Timeouts

    `necromicon` (~16s) is slow by design. Configure a generous client
    timeout — the OpenAI SDKs default to far less than it needs. Requests that exceed
    the gateway ceiling return `504`.

    Streaming behaviour differs by model. `necromicon` and `warlock` stream token by token, so
    the first chunk arrives early even
    though the full answer takes minutes. On the others the response is buffered and re-emitted
    as SSE, so the first chunk arrives only once generation has completed — silence until then
    is expected, not a stalled connection.

    ## Claude Code (Anthropic-compatible endpoint)

    `POST /v1/messages` implements the Anthropic Messages API against the same roster, keys,
    prices and credit rules. Point Claude Code (or any Anthropic SDK) at the platform:

    ```
    export ANTHROPIC_BASE_URL="https://platform.audn.ai/api"
    export ANTHROPIC_AUTH_TOKEN="sk_live_YOUR_PLATFORM_KEY"
    export ANTHROPIC_API_KEY=""
    claude --model necromicon
    ```

    Always authenticate with your own `sk_live_` platform key. Every model is served on
    this endpoint, by platform id or gateway id.

    ## Function Calling

    Tools are defined with the standard OpenAI `tools` array and tool calls are returned in the
    standard `message.tool_calls` field — no custom parsing is required. `pingu-unchained-10`
    carries the `function_calling` capability and is the recommended model for tool use.

    ## Deprecated Model IDs

    The retired `pingu-unchained-1`, `pingu-unchained-2`, `pingu-unchained-3`,
    `pingu-unchained-4`, `pingu-unchained-3-mcp`, `pingu-unchained-mcp` and `pingu-unchained`
    IDs are **deprecated** but still resolve, so existing integrations keep working. They are
    all served by `pingu-unchained-10` and billed at its pricing. They are aliases only: they
    are never returned by `GET /v1/models` and will be removed in a future release. Migrate to
    `pingu-unchained-10`.

  version: 2.0.0
  x-logo:
    url: https://platform.audn.ai/logo.png
  contact:
    name: AUDN Support
    url: https://audn.ai/support
  license:
    name: Proprietary
    url: https://audn.ai/terms
  x-tagGroups:
    - name: Core
      tags:
        - Chat
        - Models
  x-readme:
    explorer-enabled: true
    samples-languages:
      - curl
      - python
      - javascript

servers:
  - url: https://platform.audn.ai/api
    description: Production server
  - url: http://localhost:3000/api
    description: Development server

tags:
  - name: Chat
    description: Chat completion endpoints
  - name: Models
    description: Model information endpoints

paths:
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      summary: Create chat completion
      description: |
        Creates a completion for the provided messages using the specified model.
        Supports both streaming and non-streaming responses.

        Responses from `kong`, `necromicon` and `warlock` may include an
        additional `reasoning_content` field on the assistant message.

        `necromicon` typically takes 15 seconds or more — set a client
        timeout of at least 180 seconds when calling it.
      tags:
        - Chat
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
            examples:
              basic:
                summary: Basic chat request
                value:
                  model: pingu-unchained-10
                  messages:
                    - role: user
                      content: Hello, how are you?
              with_system:
                summary: With system message
                value:
                  model: pingu-unchained-10
                  messages:
                    - role: system
                      content: You are a helpful assistant.
                    - role: user
                      content: What is the capital of France?
                  temperature: 0.7
                  max_tokens: 1024
              streaming:
                summary: Streaming request
                value:
                  model: pingu-unchained-10
                  messages:
                    - role: user
                      content: Write a short poem about AI.
                  stream: true
              reasoning_model:
                summary: Reasoning model request
                description: |
                  `necromicon` returns its reasoning trace in `message.reasoning_content`
                  alongside the answer in `message.content`. Expect ~16s of latency.
                value:
                  model: necromicon
                  messages:
                    - role: user
                      content: Explain quantum entanglement, step by step.
                  max_tokens: 4096
                  temperature: 0
              long_context:
                summary: Long-context request
                description: |
                  `pingu-unchained-10` has the largest window in the roster at 262,144 tokens,
                  which makes it the right choice for whole-document prompts.
                value:
                  model: pingu-unchained-10
                  messages:
                    - role: system
                      content: Answer only from the supplied document.
                    - role: user
                      content: "<a very long document>\n\nSummarise the risk section."
                  max_tokens: 8192
              tool_use:
                summary: Function calling request
                value:
                  model: pingu-unchained-10
                  messages:
                    - role: user
                      content: What's the weather in Tokyo?
                  tools:
                    - type: function
                      function:
                        name: get_weather
                        description: Get current weather for a location
                        parameters:
                          type: object
                          properties:
                            location:
                              type: string
                          required:
                            - location
                  tool_choice: auto
      responses:
        '200':
          description: Successful response
          headers:
            X-Request-Id:
              description: Unique request identifier
              schema:
                type: string
            X-Input-Tokens:
              description: Number of input tokens
              schema:
                type: integer
            X-Output-Tokens:
              description: Number of output tokens
              schema:
                type: integer
            X-Cost-USD:
              description: Cost in USD, priced at the requested model's per-model rate
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: |
                  Server-sent events stream of `chat.completion.chunk` objects.
                  On models that stream natively, tokens are forwarded as the backend
                  produces them; on the rest the gateway buffers the model response before
                  re-emitting it, so the first chunk arrives after generation completes.

                  If the upstream fails after the stream has started, the last chunk carries
                  a top-level `error` object (`message`, `type: "upstream_error"`, `details`)
                  with `finish_reason: "error"`, followed by `data: [DONE]`. A stream that
                  produces no output is never billed, and its `stream_options.include_usage`
                  frame reports zeros.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/ModelNotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
        '504':
          $ref: '#/components/responses/UpstreamTimeout'

  /v1/models:
    get:
      operationId: listModels
      summary: List available models
      description: |
        Returns the list of available models. If an API key is provided, the response is
        filtered to the models that key is permitted to call.

        Deprecated `pingu-unchained-*` aliases still resolve on chat completion requests
        but are never returned by this endpoint.
      tags:
        - Models
      security:
        - BearerAuth: []
        - {}
      responses:
        '200':
          description: List of models
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelListResponse'
              example:
                object: list
                data:
                  - id: pingu-unchained-10
                    object: model
                    created: 1704067200
                    owned_by: audn
                    name: Pingu Unchained 10
                    description: >-
                      The latest Pingu Unchained, served on vLLM in BF16. A 262K context
                      window and sub-second latency make it the default choice for long
                      documents and high-volume workloads.
                    capabilities:
                      - chat
                      - function_calling
                      - long_context
                    context_length: 262144
                    pricing:
                      input: "$2.00 / 1M tokens"
                      output: "$8.00 / 1M tokens"
                      cached_input: null
                  - id: kong
                    object: model
                    created: 1704067200
                    owned_by: audn
                    name: Kong
                    description: >-
                      A Qwen3.8-27B abliteration served out of Singapore. Sub-second responses
                      on short prompts and the cheapest model we offer.
                    capabilities:
                      - chat
                      - reasoning
                    context_length: 262144
                    pricing:
                      input: "$2.00 / 1M tokens"
                      output: "$8.00 / 1M tokens"
                      cached_input: null
                  - id: necromicon
                    object: model
                    created: 1704067200
                    owned_by: audn
                    name: Necromicon
                    description: >-
                      Our DeepSeek V4.1-Flash-1017, tuned for thorough step-by-step reasoning
                      before answering. Slowest model we serve.
                    capabilities:
                      - chat
                      - reasoning
                    context_length: 1048576
                    pricing:
                      input: "$4.50 / 1M tokens"
                      output: "$4.50 / 1M tokens"
                      cached_input: 0.4
                  - id: warlock
                    object: model
                    created: 1704067200
                    owned_by: audn
                    name: WARLOCK
                    description: >-
                      DeepSeek V4.1-Flash-1017 served on self-hosted vLLM. Streams token by
                      token with the fastest time-to-first-token in the roster and returns
                      its reasoning trace alongside the answer. Beta.
                    capabilities:
                      - chat
                      - reasoning
                      - long_context
                    context_length: 1048576
                    pricing:
                      input: "$4.50 / 1M tokens"
                      output: "$4.50 / 1M tokens"
                      cached_input: 0.4
        '401':
          $ref: '#/components/responses/Unauthorized'

  /messages:
    post:
      summary: Create message (Anthropic-compatible)
      description: |
        The Anthropic Messages API for Claude Code and the Anthropic SDKs. Accepts the
        standard Anthropic request shape (`model`, `messages`, `system`, `max_tokens`,
        `stream`, `tools`, …) and returns Anthropic-shaped responses and SSE events
        (`message_start`, `content_block_delta`, `message_delta`, …), with reasoning as
        `thinking` blocks.

        Authentication: your platform API key, either as `Authorization: Bearer sk_live_…`
        or as `x-api-key`. Billing, context windows, per-model prices and the prepaid-credit
        rules are identical to `/v1/chat/completions`; an exhausted balance returns
        `402` with an Anthropic-shaped `billing_error`. Model ids are the platform ids
        (gateway-decorated ids are also accepted).
      tags:
        - Chat
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, messages, max_tokens]
              description: Standard Anthropic Messages request, passed through verbatim.
      responses:
        '200':
          description: Anthropic-shaped message, or an SSE stream when `stream` is true.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /messages/count_tokens:
    post:
      summary: Count tokens (Anthropic-compatible)
      description: |
        Anthropic's token-counting endpoint. Authenticated with a platform API key, never
        billed. Returns `{"input_tokens": N}`.
      tags:
        - Chat
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Token count for the given request.

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        API key authentication. Use your API key, which starts with sk_live_.

        Example: Bearer sk_live_abc123def456...

  schemas:
    ChatCompletionRequest:
      type: object
      required:
        - model
        - messages
      properties:
        model:
          type: string
          description: |
            ID of the model to use.

            Deprecated `pingu-unchained-1`, `pingu-unchained-2`, `pingu-unchained-3`,
            `pingu-unchained-4`, `pingu-unchained-3-mcp`, `pingu-unchained-mcp` and
            `pingu-unchained` are also accepted and resolve to `pingu-unchained-10`.
          enum:
            - pingu-unchained-10
            - kong
            - necromicon
            - warlock
            - omen
            - omen-2.0
          example: pingu-unchained-10
        messages:
          type: array
          description: List of messages in the conversation
          minItems: 1
          items:
            $ref: '#/components/schemas/Message'
        temperature:
          type: number
          description: |
            Sampling temperature (0-2). Higher values make output more random.
          minimum: 0
          maximum: 2
          example: 0.7
        max_tokens:
          type: integer
          description: |
            Maximum number of output tokens. Clamped into the model's supported range:

            | Model | Default | Maximum | Minimum enforced |
            |-------|---------|---------|------------------|
            | `pingu-unchained-10` | 8192 | 16384 | — |
            | `kong` | 4096 | 8192 | 4096 |
            | `necromicon` | 16000 | 32768 | 16000 |
            | `warlock` | 16000 | 32768 | 16000 |

            `kong` spends part of its budget on a thinking pass, so values below 4096 are
            raised to 4096 — otherwise it truncates mid-thought and returns empty content.
            `necromicon` and `warlock` are floored at 16000 by their gateway; lowering
            `max_tokens` does not shorten their reasoning (use `reasoning_effort`), and the
            720 s request timeout, not the 32768 ceiling, bounds a very long answer.

            The prompt plus `max_tokens` must fit inside the model's context window.
          minimum: 1
          maximum: 32768
          example: 4096
        stream:
          type: boolean
          description: |
            Whether to stream the response as server-sent events. `necromicon` and
            `warlock` stream
            incrementally. On `pingu-unchained-10` and `kong` the gateway
            buffers the model output and re-emits it, so the first chunk arrives after
            generation completes — silence until then is expected.
          default: false
        reasoning_effort:
          type: string
          enum: [low, high, max]
          description: |
            How deeply the model reasons before the answer is written, on models that
            advertise it (`reasoning_options` on `GET /v1/models`; `null` there means the
            model has no effort control and the field is ignored).

            Also accepted as the nested `reasoning: { effort: "low" }`.

            Three tiers on the DeepSeek V4.1-Flash-1017 models (`necromicon`, `warlock`, the chains): `low`
            (brief; the default when omitted), `high` (deliberate but bounded; both usually
            finish in 700–2,000 output tokens) and `max` (open-ended; on hard prompts it
            can need 45,000+ output tokens before any answer, and below that budget returns
            reasoning only with empty `content` and `finish_reason: "length"`). Reasoning
            shares `max_tokens` with the answer and bills as output tokens, so `max` is the
            slow and costly choice — and lowering `max_tokens` does not cap it.

            Never rejected: `medium` maps to `high`, `xhigh` to `max`, `minimal` to `low`,
            and an unrecognised value — or any value on a model without effort control —
            is dropped.
        top_p:
          type: number
          description: Nucleus sampling probability
          minimum: 0
          maximum: 1
        frequency_penalty:
          type: number
          description: Penalty for token frequency (-2 to 2)
          minimum: -2
          maximum: 2
        presence_penalty:
          type: number
          description: Penalty for token presence (-2 to 2)
          minimum: -2
          maximum: 2
        stop:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          description: Stop sequences
        tools:
          type: array
          description: |
            A list of tools the model may call, in the standard OpenAI format.
            Supported on `pingu-unchained-10`, which carries the `function_calling`
            capability. `kong` and `necromicon` are tuned for chat and
            reasoning rather than tool use.
          items:
            $ref: '#/components/schemas/Tool'
        tool_choice:
          description: |
            Controls which (if any) tool is called by the model.
            - "none": Model will not call any tool
            - "auto": Model can pick between generating a message or calling a tool
            - "required": Model must call one or more tools
            - Object with specific tool: Forces model to call that tool
          oneOf:
            - type: string
              enum: [none, auto, required]
            - type: object
              properties:
                type:
                  type: string
                  enum: [function]
                function:
                  type: object
                  properties:
                    name:
                      type: string
                  required: [name]
        parallel_tool_calls:
          type: boolean
          description: Whether to enable parallel function calling during tool use
          default: true

    Tool:
      type: object
      required:
        - type
        - function
      properties:
        type:
          type: string
          enum: [function]
          description: The type of the tool (currently only "function" is supported)
        function:
          $ref: '#/components/schemas/FunctionDefinition'

    FunctionDefinition:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: The name of the function to be called
        description:
          type: string
          description: A description of what the function does
        parameters:
          type: object
          description: The parameters the function accepts, described as a JSON Schema object

    ToolCall:
      type: object
      properties:
        id:
          type: string
          description: Identifier for this tool call, echoed back on the matching tool message
        type:
          type: string
          enum: [function]
        function:
          type: object
          properties:
            name:
              type: string
              description: Name of the function to call
            arguments:
              type: string
              description: Function arguments as a JSON-encoded string

    Message:
      type: object
      required:
        - role
        - content
      properties:
        role:
          type: string
          enum:
            - system
            - developer
            - user
            - assistant
            - tool
          description: |
            The role of the message author:
            - system: High-level instructions for the model
            - developer: Developer-provided instructions (takes precedence over user messages)
            - user: Input from the end user
            - assistant: Response from the model
            - tool: Result of a tool call
        content:
          type: string
          description: The content of the message
        tool_call_id:
          type: string
          description: Tool call ID (required for tool role messages)
        tool_calls:
          type: array
          description: Tool calls made by the assistant
          items:
            $ref: '#/components/schemas/ToolCall'

    AssistantMessage:
      type: object
      description: |
        The assistant message returned in a completion. Identical to `Message` with the
        addition of the optional `reasoning_content` field.
      required:
        - role
      properties:
        role:
          type: string
          enum:
            - assistant
        content:
          type: string
          description: |
            The model's answer. Empty when the model returned tool calls instead of prose.
        reasoning_content:
          type: string
          nullable: true
          description: |
            The model's reasoning trace, when it produced one.

            Present only on `kong`, `necromicon` and `warlock`. It is optional and may be
            absent or null on any request; `pingu-unchained-10` runs with thinking disabled
            server-side and never returns it.

            This is **not** the answer — always read `content` for that. Reasoning tokens are
            generated tokens and are billed as output tokens.
        tool_calls:
          type: array
          description: Tool calls made by the assistant, in the standard OpenAI format
          items:
            $ref: '#/components/schemas/ToolCall'

    ChatCompletionResponse:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the completion
        object:
          type: string
          enum:
            - chat.completion
        created:
          type: integer
          description: Unix timestamp of when the completion was created
        model:
          type: string
          description: |
            The model that served the request. When a deprecated alias was requested, this
            reports the model it resolved to.
          example: pingu-unchained-10
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                $ref: '#/components/schemas/AssistantMessage'
              finish_reason:
                type: string
                enum:
                  - stop
                  - length
                  - tool_calls
                  - content_filter
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
              description: Number of tokens in the prompt
            completion_tokens:
              type: integer
              description: |
                Number of generated tokens. Includes reasoning tokens and tool-call
                arguments, both of which are billed as output.
            total_tokens:
              type: integer
              description: Total tokens used
            prompt_tokens_details:
              type: object
              description: |
                Breakdown of `prompt_tokens`. `cached_tokens` is the portion the backend
                served from its prefix cache — a SUBSET of `prompt_tokens`, never an
                addition to it. Billing-relevant on models with a `cached_input` rate
                (the K3 class: `necromicon` and
                `warlock`), where those tokens are charged at the lower rate; on every other
                model it is 0 and nothing is discounted.
              properties:
                cached_tokens:
                  type: integer
                  description: Prompt tokens served from the backend's prefix cache.
                audio_tokens:
                  type: integer
                  description: Always 0 — no model on the roster accepts audio input.
      example:
        id: chatcmpl-abc123
        object: chat.completion
        created: 1704067200
        model: necromicon
        choices:
          - index: 0
            message:
              role: assistant
              content: The answer is 42.
              reasoning_content: Let me work through this step by step...
            finish_reason: stop
        usage:
          prompt_tokens: 24
          completion_tokens: 310
          total_tokens: 334

    ModelListResponse:
      type: object
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            $ref: '#/components/schemas/Model'

    Model:
      type: object
      properties:
        id:
          type: string
          description: Model identifier
        object:
          type: string
          enum:
            - model
        created:
          type: integer
          description: Unix timestamp of model creation
        owned_by:
          type: string
          description: Organization that owns the model
        name:
          type: string
          description: Human-readable model name
        description:
          type: string
          description: Model description
        capabilities:
          type: array
          items:
            type: string
            enum:
              - chat
              - reasoning
              - function_calling
              - long_context
          description: List of model capabilities
        context_length:
          type: integer
          description: >-
            Context window to size prompts against, in tokens. Where a model accepts a
            longer prompt than it writes its answer from, this is the smaller figure —
            see x_behavior.max_accepted_context for what is actually admitted.
        reasoning_options:
          type: array
          nullable: true
          description: >-
            Reasoning controls the model exposes, or null when it has none. Null means
            send no reasoning_effort (a client should show "Auto"); it is not the same
            as an empty list.
          items:
            type: object
            properties:
              type:
                type: string
                enum: [effort]
              values:
                type: array
                items:
                  type: string
                  enum: [low, high, max]
        x_behavior:
          type: object
          description: >-
            Behaviour metadata, keyed to match what the upstream gateway publishes so a
            client can read either hop the same way.
          properties:
            context_window:
              type: integer
              description: >-
                Window to compact against — the same figure as context_length. Agent
                clients read this key first.
            max_accepted_context:
              type: integer
              description: >-
                Input ceiling the API admits before returning 400 context_length_exceeded.
                Equal to context_window unless the model answers from a smaller window
                than it accepts.
            supported_reasoning_efforts:
              type: array
              nullable: true
              items:
                type: string
              description: Accepted reasoning_effort values, or null when the model has none.
            default_reasoning_effort:
              type: string
              nullable: true
              description: >-
                Effort injected when the caller sends none. Null means nothing is
                injected and the model's own default applies.
            reasoning_channel:
              type: boolean
              description: >-
                Whether a reasoning trace comes back in reasoning_content. Independent of
                effort support — a model can return a trace it cannot size.
            recommended_max_tokens:
              type: integer
              description: The default applied when max_tokens is omitted.
            max_output_tokens:
              type: integer
              description: Output ceiling. Larger requests are clamped, not rejected.
            min_output_tokens:
              type: integer
              nullable: true
              description: >-
                Floor applied to max_tokens, or null when none applies. Values below it are
                raised rather than rejected.
            timeout_seconds:
              type: integer
              description: Seconds before the request returns 504.
            region:
              type: string
            notes:
              type: string
              nullable: true
              description: Behaviour worth knowing that has no field of its own, or null.
        pricing:
          type: object
          properties:
            input:
              type: string
              description: Input token pricing
            output:
              type: string
              description: Output token pricing
            cached_input:
              type: number
              nullable: true
              description: |
                USD per 1M input tokens served from the backend's prefix cache, or null
                when the model has no cache tier. Null is deliberately NOT the same as
                repeating the input rate: it means no discount exists, rather than a
                discount that happens to be zero.

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
              description: Error message
            type:
              type: string
              description: Error type
            code:
              type: string
              description: Error code
            request_id:
              type: string
              description: Request ID for debugging

  responses:
    BadRequest:
      description: |
        Invalid request parameters, or the prompt exceeds the model's context window.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Invalid request: model and messages are required"

    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Missing API key. Include it in the Authorization header as: Bearer sk_..."

    Forbidden:
      description: API key or plan tier does not have access to this model
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "API key does not have access to model 'necromicon'"

    InsufficientCredits:
      description: Organization has insufficient credits for the estimated cost of the request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Insufficient credits. Please add credits to continue."

    ModelNotFound:
      description: Requested model not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Model 'invalid-model' not found. Available models: pingu-unchained-10, kong, necromicon, warlock, omen, omen-2.0"

    RateLimitExceeded:
      description: |
        Three sources, told apart by the body. A JSON body with code
        `too_many_parallel_sessions` means your organization already has as many requests in
        flight as its parallel-request limit allows (3, plus 3 per $999 entitlement; the
        `X-Pingu-Parallel-Limit` header names it) — retry when one finishes. Any other JSON body
        in the `Error` shape means serving infrastructure is at capacity — never a quota you
        exhausted; this API applies no usage quotas — so the same request succeeds once
        capacity frees up. A plain-text body means the
        per-address request-rate limit (500 requests per 60 seconds per client IP across the
        two inference endpoints, from 2026-09-14) was exceeded; it is produced at the network
        edge before the request reaches this API and clears 60 seconds after the last
        over-limit window. Retry either with exponential backoff, honouring `Retry-After` when
        present.
      headers:
        Retry-After:
          description: Seconds to wait before retrying, when the gateway supplies one
          schema:
            type: integer
        X-Pingu-Parallel-Limit:
          description: The organization's parallel-request limit, on a `too_many_parallel_sessions` refusal
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Upstream is at capacity. Please retry."

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Internal server error"
              request_id: "req_abc123"

    UpstreamTimeout:
      description: |
        The model did not respond within its gateway ceiling — 720s on every model
        (since 2026-10-12). Most likely on `necromicon`, which
        routinely takes 15 seconds or more and can run to minutes.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: "Necromicon did not respond within 720s."
              request_id: "req_abc123"
