openapi: 3.1.0
info:
  title: Leiolai API
  description: >-
    A Chat Completions-compatible API with one canonical model, leiolai-1,
    plus continuous generation with revocable lookahead, live context injection,
    and client-controlled output buffering. Both surfaces support adjustable
    reasoning effort, text, image, and inline text-file input, and up to 11
    million tokens of context. This specification defines the supported
    compatibility subset; it does not expose the OpenAI Responses API or every
    Chat Completions field.
  version: 2.0.0
servers:
  - url: https://api.leiolai.com/v1
security:
  - bearerAuth: []
paths:
  /chat/completions:
    post:
      operationId: createChatCompletion
      summary: Send a conversation and get one assistant response.
      parameters:
        - $ref: "#/components/parameters/ClientRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
      responses:
        "200":
          description: >-
            The assistant response. With stream true this is a text/event-stream
            of chat.completion.chunk frames ending with data: [DONE]; otherwise
            it is one chat.completion JSON object.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            X-Client-Request-Id:
              $ref: "#/components/headers/ClientRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCompletion"
            text/event-stream:
              schema:
                type: string
                description: >-
                  OpenAI Chat Completions server-sent events. Each data line
                  carries a ChatCompletionChunk JSON object. Content may be
                  held until final and can arrive in one content chunk. The
                  final frame is data: [DONE].
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/InvalidAPIKey"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/ContentFiltered"
        "404":
          $ref: "#/components/responses/ModelNotFound"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
        "413":
          $ref: "#/components/responses/RequestTooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /infinite/chat/completions:
    post:
      operationId: createContinuousCompletion
      summary: Open a continuous generation session.
      description: >-
        Opens a server-sent event stream. Delivered text is append-only in
        choices[].delta.content. Lookahead is a separate replace-semantics
        preview and never becomes delivered text until it appears in a delta.
      parameters:
        - $ref: "#/components/parameters/ClientRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContinuousCompletionRequest"
      responses:
        "200":
          description: >-
            A text/event-stream whose first data frame identifies the session.
            Later frames carry delivered deltas, replace-semantics lookahead,
            completed-message receipts, and finally data: [DONE]. A terminal
            failure appears as an Error object in a data frame before [DONE].
            Lines that begin with a colon are keepalives.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            X-Client-Request-Id:
              $ref: "#/components/headers/ClientRequestId"
          content:
            text/event-stream:
              schema:
                type: string
                description: >-
                  Continuous protocol 2 server-sent events. The first event is
                  {"session_id":"...","continuous_protocol":2}. Delivered
                  content uses choices[].delta.content. Lookahead uses
                  {"lookahead":"..."}. A completed user message uses
                  {"inject_ack":{"id":"...","seq":1,"anchor":0,
                  "text":"..."}}. The stream ends with data: [DONE].
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/InvalidAPIKey"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/ContentFiltered"
        "404":
          $ref: "#/components/responses/ModelNotFound"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
        "413":
          $ref: "#/components/responses/RequestTooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /infinite/inject:
    post:
      operationId: injectContinuousContext
      summary: Update a continuous generation session.
      description: >-
        Sends finalized or partial text, a live image, an output-buffer report,
        a consumer heartbeat, or a stop instruction. Serialize requests within
        each session so the server applies them in the order sent.
      parameters:
        - $ref: "#/components/parameters/ClientRequestId"
        - name: session
          in: query
          required: true
          description: The session_id from the opening stream's first event.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContinuousInjectRequest"
      responses:
        "200":
          description: >-
            The update was accepted. A finalized text injection returns an
            inject_ack object. The same receipt appears on the SSE stream before
            later delivered bytes can pass its UTF-8 byte anchor.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            X-Client-Request-Id:
              $ref: "#/components/headers/ClientRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContinuousInjectResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/InvalidAPIKey"
        "403":
          $ref: "#/components/responses/ContentFiltered"
        "404":
          $ref: "#/components/responses/SessionNotFound"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
        "413":
          $ref: "#/components/responses/RequestTooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /models:
    get:
      operationId: listModels
      summary: List the canonical model.
      parameters:
        - $ref: "#/components/parameters/ClientRequestId"
      responses:
        "200":
          description: The model list.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            X-Client-Request-Id:
              $ref: "#/components/headers/ClientRequestId"
          content:
            application/json:
              schema:
                type: object
                required: [object, data]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"
        "401":
          $ref: "#/components/responses/InvalidAPIKey"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your Leiolai API key. Send it on every request as
        Authorization: Bearer sb_...
  parameters:
    ClientRequestId:
      name: X-Client-Request-Id
      in: header
      required: false
      description: >-
        Optional caller-generated trace value. Valid values up to 512
        characters are echoed in the response.
      schema:
        type: string
        maxLength: 512
  headers:
    RequestId:
      description: Server-generated identifier for this request.
      schema:
        type: string
        pattern: "^req_"
    ClientRequestId:
      description: Echo of a valid X-Client-Request-Id request header.
      schema:
        type: string
  responses:
    BadRequest:
      description: The JSON body or a request field is invalid.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    InvalidAPIKey:
      description: No valid API key was provided.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    PaymentRequired:
      description: >-
        The available balance cannot cover the request, an API budget is
        required, or a configured API budget has been exhausted.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ContentFiltered:
      description: >-
        The content policy blocked input with content_policy_violation or
        stopped output with content_filter.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ModelNotFound:
      description: The requested model is not available.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    SessionNotFound:
      description: >-
        The continuous session ended, does not exist, or belongs to another API
        key.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    MethodNotAllowed:
      description: The HTTP method is not supported on this route.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RequestTooLarge:
      description: The request body exceeds the accepted size.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ServerError:
      description: Streaming could not be established.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    TooManyRequests:
      description: The caller exceeded the request rate limit.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
        Retry-After:
          description: Minimum seconds to wait before resending.
          required: true
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ServiceUnavailable:
      description: >-
        The service, billing records, spending records, or image understanding
        is temporarily unavailable. Retry shortly. A pre-stream service_busy
        response includes Retry-After when the server can calculate a delay.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        X-Client-Request-Id:
          $ref: "#/components/headers/ClientRequestId"
        Retry-After:
          description: Seconds to wait before resending when present.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    Model:
      type: object
      required: [id, object, owned_by]
      properties:
        id:
          type: string
          const: leiolai-1
        object:
          type: string
          const: model
        owned_by:
          type: string
          const: leiolai
    ChatCompletionRequest:
      type: object
      required: [model, messages, mode]
      additionalProperties: true
      description: >-
        The fields listed here are the supported Chat Completions subset. Other
        request fields are not part of this contract.
      properties:
        model:
          $ref: "#/components/schemas/ModelIdentifier"
        messages:
          type: array
          items:
            $ref: "#/components/schemas/Message"
        stream:
          type: boolean
          default: false
          description: >-
            When true, content is returned as server-sent event deltas. When
            omitted, the response is one JSON object.
        stream_options:
          $ref: "#/components/schemas/StreamOptions"
        reasoning_effort:
          type: string
          enum: [low, medium, high, xhigh]
          default: low
          description: Optional response depth. Defaults to low.
        max_tokens:
          type: integer
          minimum: 1
          description: >-
            Optional output cap, in tokens. If omitted, Leiolai derives a
            ceiling from the available balance and API budget.
        max_completion_tokens:
          type: integer
          minimum: 1
          description: Alias for max_tokens. Send only one of the two fields.
        mode:
          type: string
          enum: [private, non-private]
          description: >-
            Required. There is no default. Images and supported inline files
            work in both modes.
    ContinuousCompletionRequest:
      type: object
      required: [model, messages, mode, continuous_protocol]
      additionalProperties: true
      description: >-
        Opens continuous protocol 2. The fields listed here are the supported
        developer contract. The endpoint always returns server-sent events.
      properties:
        model:
          $ref: "#/components/schemas/ModelIdentifier"
        messages:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/Message"
        mode:
          type: string
          enum: [private, non-private]
          description: Required. There is no default.
        reasoning_effort:
          type: string
          enum: [low, medium, high, xhigh]
          default: low
        continuous_protocol:
          type: integer
          const: 2
          description: Required. Send 2. Omission or another value returns HTTP 400.
        lookahead:
          type: boolean
          default: false
          description: >-
            When true, the stream may include replaceable
            {"lookahead":"..."} preview events separate from delivered text.
            Treat each one as a full replacement for the previous preview.
        consumer_lease:
          type: boolean
          default: false
          description: >-
            When true, send consumer_heartbeat updates while the stream is
            live. This lets Leiolai close the session if the downstream
            consumer disconnects but a proxy keeps the HTTP connection open.
        realtime_audio:
          type: boolean
          default: false
          description: >-
            Set this when the client speaks delivered text as it arrives.
            Leiolai will not rewrite words the user may already have heard.
            Send buffer reports to pace delivery.
        max_tokens:
          type: integer
          minimum: 1
          description: >-
            Optional output-token ceiling for the session. If omitted, Leiolai
            derives one from the available balance and API budget.
        max_completion_tokens:
          type: integer
          minimum: 1
          description: Alias for max_tokens. Send only one, or send equal values.
    ContinuousInjectRequest:
      type: object
      minProperties: 1
      additionalProperties: false
      description: >-
        Send requests one at a time, in order, within each session. Do not send
        text and text_partial together. Send stop by itself.
      properties:
        text:
          type: string
          minLength: 1
          description: The user's completed message.
        text_partial:
          type: string
          minLength: 1
          description: >-
            The user's complete message so far. Each update replaces the
            previous value.
        image:
          type: string
          description: >-
            The latest image as a base64 data URI. It replaces the prior live
            image after screening. Remote image URLs are not accepted. A data
            URI may contain up to 8 MiB of decoded image data; the complete
            inject body may be up to 12 MiB.
        buffer:
          $ref: "#/components/schemas/ContinuousBuffer"
        consumer_heartbeat:
          type: boolean
          const: true
          description: >-
            Send only this field with true to confirm the downstream consumer
            is still connected. Use it only when the opening request set
            consumer_lease to true.
        stop:
          type: boolean
          const: true
          description: End the session. Send this field by itself.
    ContinuousBuffer:
      type: object
      required: [remaining_seconds, drain_rate]
      additionalProperties: false
      properties:
        remaining_seconds:
          type: number
          minimum: 0
          description: Seconds of consumable output currently queued.
        drain_rate:
          type: number
          minimum: 0
          maximum: 4
          description: >-
            Consumption speed relative to real time. Use 0 when paused, 1 at
            normal speed, and 2 at twice normal speed.
    ContinuousInjectionReceipt:
      type: object
      required: [id, seq, anchor, text]
      properties:
        id:
          type: string
        seq:
          type: integer
          minimum: 1
        anchor:
          type: integer
          minimum: 0
          description: >-
            UTF-8 byte offset in the append-only delivered answer. It may be
            ahead of the answer bytes received when the receipt arrives.
        text:
          type: string
    ContinuousInjectResponse:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean
          const: true
        stopped:
          type: boolean
        buffer_accepted:
          type: boolean
        inject_ack:
          $ref: "#/components/schemas/ContinuousInjectionReceipt"
    Message:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant]
        content:
          oneOf:
            - type: string
            - type: array
              items:
                $ref: "#/components/schemas/ContentPart"
          description: A plain string or an array of typed content parts.
    ModelIdentifier:
      type: string
      pattern: "^(?:.*/)?leiolai-1$"
      description: >-
        The canonical identifier is leiolai-1. Provider-qualified compatibility
        forms ending in /leiolai-1 resolve to the same model. Other non-empty
        identifiers return 404 model_not_found.
    StreamOptions:
      type: object
      properties:
        include_usage:
          type: boolean
          default: false
          description: >-
            When true with stream true, emit OpenAI's usage-only chunk with an
            empty choices array immediately before data: [DONE]. Earlier
            chunks carry usage null.
    ContentPart:
      oneOf:
        - $ref: "#/components/schemas/TextPart"
        - $ref: "#/components/schemas/ImagePart"
        - $ref: "#/components/schemas/FilePart"
    TextPart:
      type: object
      required: [type, text]
      additionalProperties: true
      properties:
        type:
          type: string
          const: text
        text:
          type: string
    ImagePart:
      type: object
      required: [type, image_url]
      additionalProperties: true
      properties:
        type:
          type: string
          const: image_url
        image_url:
          type: object
          required: [url]
          additionalProperties: true
          properties:
            url:
              type: string
              description: A base64 data URI. Remote image URLs are not accepted.
    FilePart:
      type: object
      required: [type, file]
      additionalProperties: true
      properties:
        type:
          type: string
          const: file
        file:
          type: object
          required: [filename, file_data]
          additionalProperties: true
          properties:
            filename:
              type: string
              maxLength: 200
            file_data:
              type: string
              description: >-
                Base64 or a base64 data URI containing 1 byte to 2 MiB of
                UTF-8 text, code, CSV, JSON, HTML, XML, YAML, SQL, or Markdown.
            file_id:
              type: string
              deprecated: true
              description: Not supported. Send inline file_data.
    ChatCompletion:
      type: object
      required: [id, object, created, model, choices]
      properties:
        id:
          type: string
        object:
          type: string
          const: chat.completion
        created:
          type: integer
        model:
          type: string
          const: leiolai-1
        choices:
          type: array
          items:
            type: object
            required: [index, message, finish_reason]
            properties:
              index:
                type: integer
              message:
                type: object
                required: [role, content]
                properties:
                  role:
                    type: string
                    const: assistant
                  content:
                    type: string
              finish_reason:
                $ref: "#/components/schemas/FinishReason"
        usage:
          $ref: "#/components/schemas/Usage"
    ChatCompletionChunk:
      type: object
      required: [id, object, created, model, choices]
      properties:
        id:
          type: string
        object:
          type: string
          const: chat.completion.chunk
        created:
          type: integer
        model:
          type: string
          const: leiolai-1
        choices:
          type: array
          items:
            type: object
            required: [index, delta, finish_reason]
            properties:
              index:
                type: integer
              delta:
                type: object
                properties:
                  role:
                    type: string
                    const: assistant
                  content:
                    type: string
              finish_reason:
                oneOf:
                  - $ref: "#/components/schemas/FinishReason"
                  - type: "null"
        usage:
          oneOf:
            - $ref: "#/components/schemas/Usage"
            - type: "null"
    FinishReason:
      type: string
      enum: [stop, length, content_filter]
    Usage:
      type: object
      required: [prompt_tokens, completion_tokens, total_tokens]
      properties:
        prompt_tokens:
          type: integer
        completion_tokens:
          type: integer
        total_tokens:
          type: integer
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message, type, param, code]
          properties:
            message:
              type: string
            type:
              type: string
              enum: [invalid_request_error, rate_limit_error, server_error]
            param:
              oneOf:
                - type: string
                - type: "null"
            code:
              oneOf:
                - type: string
                  enum:
                    - invalid_api_key
                    - insufficient_credits
                    - api_budget_exhausted
                    - api_budget_required
                    - api_budget_unverifiable
                    - invalid_value
                    - invalid_json
                    - request_too_large
                    - rate_limit_exceeded
                    - model_not_found
                    - session_not_found
                    - content_policy_violation
                    - content_filter
                    - vision_unavailable
                    - billing_unavailable
                    - service_busy
                    - service_unavailable
                    - streaming_unavailable
                    - method_not_allowed
                - type: "null"
