> ## Documentation Index
> Fetch the complete documentation index at: https://docs.compute.prentis.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat completions

> OpenAI-compatible chat completion. Point any OpenAI SDK at this base URL with a Prentis key and nothing else in your code changes.

## Streaming

With `stream: true` the response is `text/event-stream`: one `data:` event per chunk,
terminated by `data: [DONE]`. The last data event carries `usage` **whether or not** you
sent `stream_options.include_usage` — you never have to choose between streaming and
knowing what you were billed.

If a stream fails partway, chunks already sent are not withdrawn. You get one more event
carrying an error object, then `[DONE]`. Treat a stream that ended without `[DONE]` as
incomplete.

## Token accounting

The `usage` object is the number you are billed on, not an estimate of it. When a stream is
cancelled after the model has begun producing output, those tokens are still billed — see
[Errors](/errors) for the two codes where that happens.


## OpenAPI

````yaml openapi/prentis.openapi.yaml POST /chat/completions
openapi: 3.1.0
info:
  title: Prentis Inference API
  version: 0.2.0
  summary: One inference API, reachable with an OpenAI client or an Anthropic client.
  description: >
    An OpenAI-compatible inference API. Point any OpenAI SDK at this base URL
    with a

    Prentis key and nothing else in your code changes.


    Anthropic clients are served too: the same host answers `/v1/messages`, and

    `x-api-key` is accepted alongside `Authorization: Bearer`.


    Parameters this API does not implement are ignored rather than rejected,
    except the

    few marked **not supported** below, which return `INVALID_REQUEST` so that a
    silently

    wrong answer is never returned in place of an error.
servers:
  - url: https://compute.prentis.ai/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: chat
  - name: completions
  - name: messages
  - name: models
paths:
  /chat/completions:
    post:
      tags:
        - chat
      summary: Create a chat completion
      description: >-
        OpenAI-compatible chat completion. Point any OpenAI SDK at this base URL
        with a Prentis key and nothing else in your code changes.
      operationId: createChatCompletion
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
      responses:
        '200':
          headers:
            x-request-id:
              $ref: '#/components/headers/x-request-id'
            x-provider-class:
              $ref: '#/components/headers/x-provider-class'
            x-config-version:
              $ref: '#/components/headers/x-config-version'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
              example:
                id: chatcmpl-01K5S8N4Q2VZ
                object: chat.completion
                created: 1789286400
                model: deepseek-v4.1-flash
                choices:
                  - index: 0
                    message:
                      role: assistant
                      content: >-
                        A distributed store can hold at most two of consistency,
                        availability and partition tolerance at once. Because
                        partitions do happen in practice, the real choice is
                        between answering with stale data and not answering at
                        all.
                    finish_reason: stop
                usage:
                  prompt_tokens: 28
                  completion_tokens: 47
                  total_tokens: 75
            text/event-stream:
              schema:
                type: string
              example: >-
                data:
                {"id":"chatcmpl-01K5S8N4Q2VZ","object":"chat.completion.chunk","model":"deepseek-v4.1-flash","choices":[{"index":0,"delta":{"role":"assistant","content":"A
                distributed"}}]}


                data:
                {"id":"chatcmpl-01K5S8N4Q2VZ","object":"chat.completion.chunk","model":"deepseek-v4.1-flash","choices":[{"index":0,"delta":{"content":"
                store can hold"}}]}


                data:
                {"id":"chatcmpl-01K5S8N4Q2VZ","object":"chat.completion.chunk","model":"deepseek-v4.1-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":28,"completion_tokens":47,"total_tokens":75}}


                data: [DONE]
          description: >-
            OK. With `stream: true` the same call answers `text/event-stream`
            instead, one chunk per event, terminated by `data: [DONE]`; the last
            data event carries `usage` whether or not you asked for it.
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '402':
          $ref: '#/components/responses/Error402'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '409':
          $ref: '#/components/responses/Error409'
        '413':
          $ref: '#/components/responses/Error413'
        '429':
          $ref: '#/components/responses/Error429'
        '500':
          $ref: '#/components/responses/Error500'
        '503':
          $ref: '#/components/responses/Error503'
        '504':
          $ref: '#/components/responses/Error504'
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl https://compute.prentis.ai/v1/chat/completions \
              -H "Authorization: Bearer $PRENTIS_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "model": "deepseek-v4.1-flash",
                "messages": [
                  {"role": "system", "content": "You are a concise assistant."},
                  {"role": "user", "content": "Summarise the CAP theorem in two sentences."}
                ],
                "max_tokens": 200,
                "temperature": 0.7
              }'
        - lang: python
          label: Python
          source: >-
            import os

            from openai import OpenAI


            client = OpenAI(base_url="https://compute.prentis.ai/v1",
            api_key=os.environ["PRENTIS_API_KEY"])


            resp = client.chat.completions.create(
                model="deepseek-v4.1-flash",
                messages=[
                    {"role": "system", "content": "You are a concise assistant."},
                    {"role": "user", "content": "Summarise the CAP theorem in two sentences."},
                ],
                max_tokens=200,
                temperature=0.7,
            )

            print(resp.choices[0].message.content)

            print(resp.usage.total_tokens, "tokens billed")
        - lang: typescript
          label: TypeScript
          source: |-
            import OpenAI from "openai";

            const client = new OpenAI({
              baseURL: "https://compute.prentis.ai/v1",
              apiKey: process.env.PRENTIS_API_KEY,
            });

            const resp = await client.chat.completions.create({
              model: "deepseek-v4.1-flash",
              messages: [
                { role: "system", content: "You are a concise assistant." },
                { role: "user", content: "Summarise the CAP theorem in two sentences." },
              ],
              max_tokens: 200,
              temperature: 0.7,
            });
            console.log(resp.choices[0]?.message.content);
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: >-
        Your own key for making a retry safe (up to 255 characters). It is
        accepted and echoed back today; de-duplication of replays arrives in a
        later release, so a retry is currently a second billable call.
  schemas:
    ChatCompletionRequest:
      type: object
      required:
        - model
        - messages
      additionalProperties: true
      properties:
        model:
          type: string
          maxLength: 256
          description: >-
            Which model answers the call. A bare slug for platform models
            (deepseek-v4.1-flash), or a full resource name for something your
            account owns (accounts/{account}/models/{model}, or a deployment).
            Never an upstream vendor's own name.
        messages:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/ChatMessage'
          description: >-
            The conversation so far, oldest first. Each turn carries a role
            (system, user, assistant or tool) and its content.
        stream:
          type: boolean
          default: false
          description: >-
            Return the answer as server-sent events as it is generated, instead
            of one response at the end. The last data event carries the usage
            numbers either way.
        stream_options:
          type: object
          properties:
            include_usage:
              type: boolean
              default: false
          description: >-
            Streaming options. include_usage is accepted for compatibility;
            usage arrives on the final event whether or not you ask for it.
        max_tokens:
          type: integer
          minimum: 1
          description: >-
            Ceiling on how many tokens may be generated. Omit it to use the
            model's own default.
        max_completion_tokens:
          type: integer
          minimum: 1
          description: >-
            Ceiling on generated tokens. Wins over max_tokens when both are
            sent.
        temperature:
          type: number
          minimum: 0
          maximum: 2
          default: 1
          description: >-
            How much randomness to allow when picking each token. Lower is more
            repeatable, higher is more varied.
        top_p:
          type: number
          minimum: 0
          maximum: 1
          default: 1
          description: >-
            Nucleus sampling: only consider the most likely tokens up to this
            share of the probability mass. Tune this or temperature, not both.
        stop:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
              maxItems: 4
          description: >-
            Up to 4 strings that end generation as soon as the model produces
            one. The matched string is not included in the output.
        seed:
          type: integer
          description: >-
            Best effort repeatability: the same seed with the same parameters
            returns the same answer on models that support it.
        frequency_penalty:
          type: number
          minimum: -2
          maximum: 2
          default: 0
          description: >-
            Pushes the model away from tokens it has already used often in this
            answer. Negative values do the opposite.
        presence_penalty:
          type: number
          minimum: -2
          maximum: 2
          default: 0
          description: >-
            Pushes the model away from tokens that have appeared at all in this
            answer, which tends to move it on to new ground.
        user:
          type: string
          maxLength: 256
          description: >-
            Your own identifier for the end user behind the call. It is echoed
            back in this request's log line so you can correlate the two; it is
            not kept on the usage record.
        tools:
          type: array
          items:
            $ref: '#/components/schemas/Tool'
          description: >-
            Function definitions the model may call. If none of the providers
            that can serve this model support tools, the call is rejected rather
            than answered as though you had not asked.
        tool_choice:
          oneOf:
            - type: string
              enum:
                - none
                - auto
                - required
            - type: object
              properties:
                type:
                  type: string
                  enum:
                    - function
                function:
                  type: object
                  required:
                    - name
                  properties:
                    name:
                      type: string
          description: >-
            Whether the model may, must, or must not call a tool: none, auto,
            required, or one named function.
        response_format:
          type: object
          properties:
            type:
              type: string
              enum:
                - text
                - json_object
                - json_schema
            json_schema:
              type: object
          description: >-
            Ask for free text, a JSON object, or JSON matching a schema.
            Rejected if no provider behind this model can honour it.
        service_tier:
          type: string
          enum:
            - standard
            - priority
            - fast
          default: standard
          description: Which service tier serves the call. Only standard is accepted today.
        'n':
          type: integer
          const: 1
          description: |-
            How many completions to return.

            **Only `1` is accepted.** Any other value returns `INVALID_REQUEST`.
        logprobs:
          type: boolean
          const: false
          description: |-
            Per-token log probabilities.

            **Only `false` is accepted.** `true` returns `INVALID_REQUEST`.
    ChatCompletionResponse:
      type: object
      required:
        - id
        - object
        - created
        - model
        - choices
        - usage
      properties:
        id:
          type: string
        object:
          type: string
          const: chat.completion
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            $ref: '#/components/schemas/Choice'
        usage:
          $ref: '#/components/schemas/Usage'
        system_fingerprint:
          type: string
    ChatMessage:
      type: object
      required:
        - role
      properties:
        role:
          type: string
          enum:
            - system
            - user
            - assistant
            - tool
        content:
          oneOf:
            - type: string
            - type: array
              items:
                $ref: '#/components/schemas/ContentPart'
            - type: 'null'
        name:
          type: string
        tool_calls:
          type: array
          items:
            $ref: '#/components/schemas/ToolCall'
        tool_call_id:
          type: string
    Tool:
      type: object
      required:
        - type
        - function
      properties:
        type:
          type: string
          enum:
            - function
        function:
          type: object
          required:
            - name
          properties:
            name:
              type: string
            parameters:
              type: object
            strict:
              type: boolean
    Choice:
      type: object
      required:
        - index
        - message
        - finish_reason
      properties:
        index:
          type: integer
          const: 0
        message:
          type: object
          required:
            - role
          properties:
            role:
              type: string
              enum:
                - assistant
            content:
              type:
                - string
                - 'null'
            tool_calls:
              type: array
              items:
                $ref: '#/components/schemas/ToolCall'
        finish_reason:
          type:
            - string
            - 'null'
          enum:
            - stop
            - length
            - tool_calls
            - content_filter
            - null
    Usage:
      type: object
      required:
        - prompt_tokens
        - completion_tokens
        - total_tokens
      properties:
        prompt_tokens:
          type: integer
          minimum: 0
        completion_tokens:
          type: integer
          minimum: 0
        total_tokens:
          type: integer
          minimum: 0
        prompt_tokens_details:
          type: object
          properties:
            cached_tokens:
              type: integer
              minimum: 0
        media:
          type: object
          properties:
            input_images:
              type: integer
              minimum: 0
            input_video_seconds:
              type: number
              minimum: 0
            input_audio_seconds:
              type: number
              minimum: 0
            output_images:
              type: integer
              minimum: 0
            output_video_seconds:
              type: number
              minimum: 0
            output_audio_seconds:
              type: number
              minimum: 0
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - type
            - code
          properties:
            message:
              type: string
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - rate_limit_error
                - server_error
                - service_unavailable
                - billing_error
            code:
              type: string
              enum:
                - INVALID_API_KEY
                - TENANT_SUSPENDED
                - MODEL_NOT_ALLOWED
                - AGREEMENT_REQUIRED
                - INVALID_REQUEST
                - MODEL_NOT_FOUND
                - MODEL_CAPABILITY_UNSUPPORTED
                - CONTEXT_LENGTH_EXCEEDED
                - IDEMPOTENCY_CONFLICT
                - MEDIA_TOO_LARGE
                - MEDIA_FETCH_FAILED
                - MEDIA_UNSUPPORTED
                - TENANT_RATE_LIMITED
                - TENANT_BUDGET_EXCEEDED
                - INSUFFICIENT_BALANCE
                - PAYMENT_REQUIRED
                - CONTENT_BLOCKED_INPUT
                - CONTENT_BLOCKED_OUTPUT
                - UPSTREAM_RATE_LIMITED
                - UPSTREAM_UNAVAILABLE
                - UPSTREAM_TIMEOUT
                - PARTIAL_RESPONSE_TIMEOUT
                - UPSTREAM_CONTENT_REJECTED
                - DEPLOYMENT_SCALING_UP
                - REQUEST_CANCELLED
                - REQUEST_DEADLINE_EXCEEDED
                - INTERNAL
                - STORAGE_UNAVAILABLE
            param:
              type:
                - string
                - 'null'
    ContentPart:
      oneOf:
        - type: object
          required:
            - type
            - text
          properties:
            type:
              type: string
              const: text
            text:
              type: string
        - type: object
          required:
            - type
            - image_url
          properties:
            type:
              type: string
              const: image_url
            image_url:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                detail:
                  type: string
                  enum:
                    - auto
                    - low
                    - high
                  default: auto
        - type: object
          required:
            - type
            - video_url
          properties:
            type:
              type: string
              const: video_url
            video_url:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                detail:
                  type: string
                  enum:
                    - auto
                    - low
                    - high
                  default: auto
                max_frames:
                  type: integer
                  minimum: 1
                spatial_limit:
                  type: integer
                  minimum: 1
                sample_fps:
                  type: number
                  exclusiveMinimum: 0
          description: '**Not supported.** Sending it returns `INVALID_REQUEST`.'
        - type: object
          required:
            - type
            - input_audio
          properties:
            type:
              type: string
              const: input_audio
            input_audio:
              type: object
              properties:
                data:
                  type: string
                url:
                  type: string
                format:
                  type: string
                  enum:
                    - wav
                    - mp3
                    - flac
                    - ogg
                    - webm
          description: '**Not supported.** Sending it returns `INVALID_REQUEST`.'
    ToolCall:
      type: object
      required:
        - id
        - type
        - function
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - function
        function:
          type: object
          required:
            - name
            - arguments
          properties:
            name:
              type: string
            arguments:
              type: string
  headers:
    x-request-id:
      schema:
        type: string
      required: true
      description: >-
        On every response, successes and errors alike. It is the handle that
        reaches the whole path your request took -- quote it when you ask us
        anything about a call.
    x-provider-class:
      schema:
        type: string
        enum:
          - primary
          - fallback
      description: >-
        primary or fallback: whether your call was served by the first choice or
        a standby. It never names who served it.
    x-config-version:
      schema:
        type: string
      description: >-
        Which version of the routing configuration was in effect for this call.
        Useful only when comparing two calls that behaved differently.
    Retry-After:
      schema:
        type: integer
        minimum: 0
      description: How many seconds to wait before retrying, on the responses that set it.
  responses:
    Error400:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `CONTENT_BLOCKED_INPUT`,
        `CONTENT_BLOCKED_OUTPUT`, `CONTEXT_LENGTH_EXCEEDED`, `INVALID_REQUEST`,
        `MEDIA_FETCH_FAILED`, `MEDIA_UNSUPPORTED`,
        `MODEL_CAPABILITY_UNSUPPORTED`, `UPSTREAM_CONTENT_REJECTED`. See the
        Errors page for what each one means, whether it is safe to retry, and
        whether it is billed.
    Error401:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INVALID_API_KEY`. See the Errors page for what
        each one means, whether it is safe to retry, and whether it is billed.
    Error402:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INSUFFICIENT_BALANCE`, `PAYMENT_REQUIRED`. See
        the Errors page for what each one means, whether it is safe to retry,
        and whether it is billed.
    Error403:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `AGREEMENT_REQUIRED`, `MODEL_NOT_ALLOWED`,
        `TENANT_SUSPENDED`. See the Errors page for what each one means, whether
        it is safe to retry, and whether it is billed.
    Error404:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `MODEL_NOT_FOUND`. See the Errors page for what
        each one means, whether it is safe to retry, and whether it is billed.
    Error409:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `IDEMPOTENCY_CONFLICT`. See the Errors page for
        what each one means, whether it is safe to retry, and whether it is
        billed.
    Error413:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `MEDIA_TOO_LARGE`. See the Errors page for what
        each one means, whether it is safe to retry, and whether it is billed.
    Error429:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `TENANT_BUDGET_EXCEEDED`, `TENANT_RATE_LIMITED`,
        `UPSTREAM_RATE_LIMITED`. See the Errors page for what each one means,
        whether it is safe to retry, and whether it is billed.
    Error500:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INTERNAL`. See the Errors page for what each
        one means, whether it is safe to retry, and whether it is billed.
    Error503:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `DEPLOYMENT_SCALING_UP`, `STORAGE_UNAVAILABLE`,
        `UPSTREAM_UNAVAILABLE`. See the Errors page for what each one means,
        whether it is safe to retry, and whether it is billed.
    Error504:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `PARTIAL_RESPONSE_TIMEOUT`,
        `REQUEST_DEADLINE_EXCEEDED`, `UPSTREAM_TIMEOUT`. See the Errors page for
        what each one means, whether it is safe to retry, and whether it is
        billed.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        `Authorization: Bearer <your API key>`. On `/v1/messages`, the
        Anthropic-style `x-api-key: <your API key>` header is accepted instead.

````