openapi: 3.1.0
info:
  title: 6io API
  version: "1.0"
  description: |
    OpenAI-compatible API with a separate balance in credits.
    Only what already works is described here: chat completions for the `op` and `vn` groups, the model catalog,
    the balance and request status. Guides: see the Markdown pages next to this file.

    Amounts are decimal strings with up to 12 decimal places.
servers:
  - url: https://api.6io.io/v1
    description: Production (the address is set when the API opens)
  - url: http://localhost:8081/v1
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Models
    description: Model catalog and prices
  - name: Chat
    description: Chat completions, including streaming
  - name: Account
    description: Balance and state of your requests
  - name: Jobs
    description: Background jobs (/v1/jobs)
  - name: fal
    description: Image, video and audio models from the fal catalog, run as a queue (/native/fal)
  - name: sixio
    description: Image and video models of 6io as tasks (/native/sixio)
  - name: Webhooks
    description: Notifications about the end of a queue request (webhook_url, fal_webhook)
paths:
  /models:
    get:
      tags: [Models]
      operationId: listModels
      summary: List models and prices
      description: |
        Models that can be used now, in the OpenAI list format. `pricing` is the price for the client in credits,
        the price multiplier and markup are already included. Only models with a known price are listed. Free models (price 0) are listed too; they accept requests only
        while the API balance is above zero.
      responses:
        "200":
          description: Model list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"
        "401":
          $ref: "#/components/responses/Error"
        "429":
          $ref: "#/components/responses/Error"
  /models/op/{author}/{model}/providers:
    get:
      tags: [Models]
      operationId: listModelProviders
      summary: Providers of an OpenRouter model, with prices
      description: |
        For models of the `op` group: the model card and the list of its providers (the same data as the Models tab of the
        console). Providers are sorted by price; `default` marks the one a request without a `provider` field goes to first
        (the cheapest with precision fp8 or better). `tag` is what goes into `provider.order`, `only` and `ignore` of a chat
        request. Prices are in credits per million tokens. Only `op/` models have providers; other groups answer 404.
      parameters:
        - name: author
          in: path
          required: true
          description: The part of the model name after `op/` and before the second slash, for example `meta-llama`
          schema: { type: string }
        - name: model
          in: path
          required: true
          description: The last part of the model name, for example `llama-3.3-70b-instruct`
          schema: { type: string }
      responses:
        "200":
          description: The model and its providers
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, example: op/meta-llama/llama-3.3-70b-instruct }
                  name: { type: string }
                  description: { type: string }
                  context: { type: integer }
                  max_output: { type: integer }
                  modality: { type: string }
                  pricing: { $ref: "#/components/schemas/ChatPricing" }
                  providers:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string, example: DeepInfra }
                        tag: { type: string, example: deepinfra/turbo }
                        quantization: { type: string, example: fp8 }
                        context: { type: integer }
                        max_output: { type: integer }
                        uptime_30m: { type: [number, "null"], description: Availability over the last 30 minutes, percent }
                        default: { type: boolean }
                        pricing: { $ref: "#/components/schemas/ChatPricing" }
        "401":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "429":
          $ref: "#/components/responses/Error"
        "503":
          $ref: "#/components/responses/Error"
  /chat/completions:
    post:
      tags: [Chat]
      operationId: createChatCompletion
      summary: Create a chat completion
      description: |
        The body is passed to the provider as it is, except `model`: the `op/` or `vn/` prefix is removed.
        Unknown fields are kept. With `stream: true` the answer is a server-sent event stream.

        For `op/` models without a `provider` field the request goes to the cheapest provider with fp8 precision
        or higher (`provider: {"sort": "price", "quantizations": ["fp8", "mxfp8", "fp16", "bf16", "fp32"]}`; closed
        models get only `sort`). Your own `provider` object (OpenRouter format: `order`, `only`, `ignore`,
        `quantizations`, `sort`, `allow_fallbacks`) wins over these defaults.

        Web search (`plugins`, `web_search_options`, the `:online` suffix) and non-text input are rejected
        with `400 unsupported_parameter` before any charge.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Any unique string up to 255 characters. A repeat of the same request returns 409 and starts no new generation.
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRequest"
      responses:
        "200":
          description: The completion. The `X-Sixio-Request-Id` header holds the request number.
          headers:
            X-Sixio-Request-Id:
              schema: { type: string }
              description: Number of the request, a string like `req_…`; use it in GET /requests/{id}
          content:
            application/json:
              schema:
                type: object
                description: Chat completion object in the OpenAI format; `model` holds the public name.
                additionalProperties: true
            text/event-stream:
              schema:
                type: string
                description: "SSE chunks in the OpenAI format; the last chunk carries `usage`, then `data: [DONE]`."
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
        "402":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "409":
          description: A request with this Idempotency-Key was already accepted (`idempotent_request_exists`).
          headers:
            X-Sixio-Request-Id: { schema: { type: string } }
            Location: { schema: { type: string } }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
        "429":
          description: A limit was reached. `Retry-After` is the interval to retry after, in seconds.
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          $ref: "#/components/responses/Error"
  /balance:
    get:
      tags: [Account]
      operationId: getBalance
      summary: Balance
      responses:
        "200":
          description: Balance in credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Balance"
        "401":
          $ref: "#/components/responses/Error"
  /requests/{id}:
    get:
      tags: [Account]
      operationId: getRequest
      summary: State and charge of a request
      parameters:
        - name: id
          in: path
          required: true
          description: "The request number, a string like `req_…` (not a number): the `X-Sixio-Request-Id` header or the `id` field of the response"
          schema: { type: string, pattern: "^req_[A-Za-z0-9_-]{22}$", example: req_8fK2xQ1mZpVwT9sJdL4nAg }
      responses:
        "200":
          description: The request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestInfo"
        "401":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /jobs:
    post:
      tags: [Jobs]
      operationId: createJob
      summary: Start a background job
      description: |
        Runs an operation from the registry in the background and answers 202 at once. The only operation
        now is `chat.completions`: `input` is the chat body without `model`; `stream` is not supported.
        The job number equals X-Sixio-Request-Id. When there is no free slot the answer is 429 at once.
        Add `webhook_url` to be notified when the job ends instead of polling (see the Webhooks guide).
      parameters:
        - { name: Idempotency-Key, in: header, required: false, schema: { type: string, maxLength: 255 } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, operation, input]
              properties:
                model: { type: string, example: "op/openai/gpt-4o-mini" }
                operation: { type: string, enum: [chat.completions] }
                input: { type: object, additionalProperties: true }
                webhook_url: { $ref: "#/components/schemas/WebhookUrl" }
      responses:
        "202":
          description: Accepted
          headers:
            Location: { schema: { type: string }, description: Link to the job state }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Job" }
        "400": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /jobs/{id}:
    get:
      tags: [Jobs]
      operationId: getJob
      summary: Job state and money
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: State
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Job" }
        "404": { $ref: "#/components/responses/Error" }
  /jobs/{id}/result:
    get:
      tags: [Jobs]
      operationId: getJobResult
      summary: Finished result (for chat.completions - the ordinary chat answer)
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: "The result in the operation's format" }
        "202": { description: "Still running; Retry-After and a link to the state" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { description: "job_failed or job_outcome_unknown" }
        "410": { description: "result_expired" }
  /native/fal/models:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [fal]
      operationId: falListModels
      summary: Search the fal model catalog
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: category, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer } }
        - { name: cursor, in: query, schema: { type: string } }
      responses:
        "200": { description: "Models with public ids (fal/<endpoint id>)" }
        "401": { $ref: "#/components/responses/Error" }
  /native/fal/price/{endpoint_id}:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [fal]
      operationId: falPrice
      summary: Price of one billing unit in credits
      parameters:
        - { name: endpoint_id, in: path, required: true, allowReserved: true, description: "Model id at fal; contains slashes, for example fal-ai/flux/schnell", schema: { type: string } }
      responses:
        "200": { description: "unit and price_per_unit (credits)" }
        "404": { $ref: "#/components/responses/Error" }
  /native/fal/queue/{endpoint_id}:
    servers: [{ url: "https://api.6io.io" }]
    post:
      tags: [fal]
      operationId: falSubmit
      summary: Submit a job to the fal queue
      description: |
        The body goes to fal as it is. Of the query parameters only `fal_webhook` is accepted: the address of a
        notification about the end of the job (see the Webhooks guide); other parameters are refused.
      parameters:
        - { name: endpoint_id, in: path, required: true, allowReserved: true, description: "Model id at fal, for example fal-ai/flux/schnell", schema: { type: string } }
        - { name: fal_webhook, in: query, required: false, description: "Address for the notification about the end of the job (https, a public address, URL-encoded)", schema: { type: string, maxLength: 2048 } }
        - { name: Idempotency-Key, in: header, required: false, schema: { type: string, maxLength: 255 } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        "200":
          description: Job accepted; request_id and links are ours
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FalJob" }
        "400": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /native/fal/requests/{id}/status:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [fal]
      operationId: falStatus
      summary: Job status
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: "status is IN_QUEUE, IN_PROGRESS or COMPLETED; error is present if the job failed" }
        "404": { $ref: "#/components/responses/Error" }
  /native/fal/requests/{id}:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [fal]
      operationId: falResult
      summary: Job result (file links point to our server and expire)
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: "The model's result in fal's format" }
        "400": { description: "Not ready yet (fal's own answer)" }
        "404": { $ref: "#/components/responses/Error" }
  /native/fal/requests/{id}/cancel:
    servers: [{ url: "https://api.6io.io" }]
    put:
      tags: [fal]
      operationId: falCancel
      summary: Cancel a job
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: "Removed from the queue; the reserve is returned" }
        "202": { description: "Signal sent to a running job; it may still finish and be billed" }
        "400": { description: "Already completed" }
        "404": { $ref: "#/components/responses/Error" }
  /native/sixio/models:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [sixio]
      operationId: sixioListModels
      summary: Models, example input and the price in advance
      responses:
        "200": { description: "models[]: id (sixio/<slug>), kind, example_input (the 6io form fields of the model), price (unit, from, to, options[]; credits, a guide)" }
        "401": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /native/sixio/tasks:
    servers: [{ url: "https://api.6io.io" }]
    post:
      tags: [sixio]
      operationId: sixioSubmit
      summary: Submit a task
      description: |
        Only model, input and webhook_url are accepted; input holds the 6io form fields of the model (camelCase, files as public
        http(s) links; the fields of each model are shown in the console). The price is the 6io price for this input
        with the group markup, reserved at submission: a finished task charges exactly it, a failed one returns it.
        Add webhook_url to be notified when the task ends instead of polling (see the Webhooks guide). Where the file sets the price (reference videos of
        Seedance and MiniMax H3, the video of happyhorse/video-edit and Kling Motion, the audio of Kling Avatar), the
        server downloads the file from the link before reserving money, keeps a copy for at most 15 minutes and deletes
        it when the task ends; a link that cannot be downloaded gives 422 invalid_input, too many copies at once 429.
      parameters:
        - { name: Idempotency-Key, in: header, required: false, schema: { type: string, maxLength: 255 } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, input]
              properties:
                model: { type: string, example: "sixio/flux-3/klein-text-to-image" }
                input: { type: object, additionalProperties: true, example: { prompt: "a red circle on a white background", aspectRatio: "1:1", resolution: "1K" } }
                webhook_url: { $ref: "#/components/schemas/WebhookUrl" }
      responses:
        "200":
          description: Task accepted; id and status_url are ours
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SixioTask" }
        "400": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /native/sixio/tasks/{id}:
    servers: [{ url: "https://api.6io.io" }]
    get:
      tags: [sixio]
      operationId: sixioTask
      summary: Task state and result (file links point to our server and expire)
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: "State at the moment of the request: queued, running, succeeded (result.urls), failed (error.message), unknown"
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SixioTask" }
        "404": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
webhooks:
  requestFinished:
    post:
      tags: [Webhooks]
      summary: A queue request ended
      description: |
        Sent to the address you gave in `webhook_url` (sixio tasks, background jobs) or `fal_webhook` (fal) when the
        request leaves "running". The format is Standard Webhooks: the signature is HMAC-SHA256 over
        `<webhook-id>.<webhook-timestamp>.<raw body>` with the key from your signing secret (`whsec_...`, base64 after
        the prefix), sent as `v1,<base64>`. The secret is in the console (Keys). `data` is the same state the status
        request returns; for fal it has the shape of a fal webhook. Answer with any 2xx within 10 seconds; anything
        else is retried 6 more times (after 5 s, 5 min, 30 min, 2 h, 5 h, 10 h), `410 Gone` stops the retries. The same
        `webhook-id` can arrive twice: handle it idempotently.
      parameters:
        - { name: webhook-id, in: header, required: true, schema: { type: string, example: msg_req_8fK2xQ1mZpVwT9sJdL4nAg } }
        - { name: webhook-timestamp, in: header, required: true, description: "Sending time, Unix seconds", schema: { type: string, example: "1614265330" } }
        - { name: webhook-signature, in: header, required: true, schema: { type: string, example: "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=" } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type: { type: string, enum: [request.finished] }
                timestamp: { type: string, format: date-time }
                data:
                  oneOf:
                    - { $ref: "#/components/schemas/SixioTask" }
                    - { $ref: "#/components/schemas/Job" }
                    - { $ref: "#/components/schemas/FalWebhookData" }
      responses:
        "200": { description: "Any 2xx code confirms the delivery" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key of the form `sixio_sk_…`, created in the API console (/console/, Keys tab)."
  responses:
    Error:
      description: Error in the OpenAI format
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message: { type: string }
            type: { type: string }
            code:
              type: string
              enum:
                - invalid_api_key
                - account_blocked
                - invalid_body
                - unsupported_parameter
                - invalid_idempotency_key
                - model_not_found
                - insufficient_funds
                - request_too_large
                - idempotent_request_exists
                - idempotency_key_reused
                - rate_limit_exceeded
                - concurrency_limit_exceeded
                - too_many_waiting_requests
                - too_many_unsettled_requests
                - service_unavailable
                - upstream_unavailable
                - invalid_endpoint
                - unsupported_model
                - not_found
    ModelList:
      type: object
      properties:
        object: { type: string, example: list }
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: string, example: op/openai/gpt-4o-mini }
              object: { type: string, example: model }
              created: { type: integer }
              owned_by: { type: string }
              pricing:
                type: object
                properties:
                  currency: { type: string, example: credits }
                  prompt_per_million: { type: string, example: "12.345600000000" }
                  completion_per_million: { type: string, example: "49.382400000000" }
                  request: { type: string, example: "0.000000000000" }
    ChatRequest:
      type: object
      required: [model, messages]
      additionalProperties: true
      properties:
        model:
          type: string
          description: Group prefix and model name, for example `op/openai/gpt-4o-mini`
          example: op/openai/gpt-4o-mini
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
            properties:
              role: { type: string }
              content: {}
        stream: { type: boolean }
        max_tokens:
          type: integer
          description: Also used to estimate the reserve; a default is used when it is not set.
    ChatPricing:
      type: object
      properties:
        currency: { type: string, example: credits }
        prompt_per_million: { type: string, example: "9.480000000000" }
        completion_per_million: { type: string, example: "30.810000000000" }
        request: { type: string, example: "0.000000000000" }
    WebhookUrl:
      type: string
      maxLength: 2048
      description: "Address for the notification about the end of the request: https, a public internet address, no login or password, not this API; redirects are not followed. Checked before money is reserved (400 invalid_webhook_url)."
      example: https://example.com/hooks/6io
    WebhookState:
      type: object
      description: Delivery state of the notification; present when the request has an address
      properties:
        status: { type: string, enum: [pending, delivered, failed] }
        attempts: { type: integer }
        last_code: { type: [integer, "null"], description: "Code of the receiver's last answer" }
    FalWebhookData:
      type: object
      description: "The notification body for fal, in the shape of a fal webhook; file links are ours"
      properties:
        request_id: { type: string, example: req_8fK2xQ1mZpVwT9sJdL4nAg }
        status: { type: string, enum: [OK, ERROR] }
        outcome: { type: string, enum: [succeeded, failed, unknown], description: "Our state word" }
        payload: { description: "The model result (null on ERROR or when it is larger than 256 KB)" }
        payload_error: { type: string }
        error: { description: "The reason when status is ERROR" }
        status_url: { type: string, format: uri }
        response_url: { type: string, format: uri }
    Balance:
      type: object
      properties:
        currency: { type: string, example: credits }
        balance: { type: string, example: "100.000000000000" }
        reserved: { type: string, example: "0.157000000000" }
        available: { type: string, example: "99.843000000000" }
    FalJob:
      type: object
      properties:
        request_id: { type: string, example: req_8fK2xQ1mZpVwT9sJdL4nAg }
        status: { type: string, enum: [IN_QUEUE, IN_PROGRESS, COMPLETED] }
        status_url: { type: string, format: uri }
        response_url: { type: string, format: uri }
        cancel_url: { type: string, format: uri }
        queue_position: { type: integer }
    SixioTask:
      type: object
      properties:
        id: { type: string, example: "812" }
        model: { type: string }
        object: { type: string, example: task }
        status: { type: string, enum: [queued, running, succeeded, failed, unknown] }
        created_at: { type: string, format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        billing:
          type: object
          properties:
            status: { type: string }
            charged_credits: { type: [string, "null"] }
        webhook: { $ref: "#/components/schemas/WebhookState" }
        status_url: { type: string, format: uri }
        result:
          type: object
          properties:
            urls: { type: array, items: { type: string, format: uri } }
        error:
          type: object
          properties:
            message: { type: string }
    Job:
      type: object
      properties:
        id: { type: string, example: "812" }
        object: { type: string, example: job }
        operation: { type: string, example: chat.completions }
        model: { type: string }
        status: { type: string, enum: [running, succeeded, failed, unknown] }
        created_at: { type: string, format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        billing:
          type: object
          properties:
            status: { type: string }
            charged_credits: { type: [string, "null"] }
        webhook: { $ref: "#/components/schemas/WebhookState" }
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
        status_url: { type: string, format: uri }
        result_url: { type: string, format: uri }
    RequestInfo:
      type: object
      properties:
        id: { type: string, description: "The request number, a string like req_…", example: req_8fK2xQ1mZpVwT9sJdL4nAg }
        model: { type: string }
        object: { type: string, example: request }
        status:
          type: string
          enum: [running, succeeded, failed, unknown]
        billing_status:
          type: string
          enum: [reserved, settled, released, billing_pending, billing_review]
        reserved: { type: string }
        charged_credits: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
