openapi: 3.1.0 info: title: siema_ai API version: "1.0.0" description: | The siema_ai API serves Polish-first open-weight models (Bielik, Qwen, BGE-M3) behind an OpenAI-compatible interface. Point an OpenAI SDK at `https://api.siema-ai.pl/v1` with an `sk-siema-` API key and the chat completions, embeddings and models endpoints work unchanged. Billing is prepaid in PLN. Prices are published per model in micro-PLN (1 PLN = 1 000 000 micro-PLN) per million tokens; `GET /v1/models` is the source of truth for the price in force right now. No prompt, completion or embedding input is ever stored, logged or traced. contact: name: siema_ai support email: support@siema-ai.pl url: https://docs.siema-ai.pl servers: - url: https://api.siema-ai.pl description: Production (EU) tags: - name: Chat description: Chat completions, streaming and non-streaming. - name: Embeddings description: Text embeddings with BGE-M3 (1024 dimensions). - name: Models description: Model aliases, capabilities, limits and current prices. security: - ApiKeyAuth: [] paths: /v1/chat/completions: post: operationId: createChatCompletion tags: [Chat] summary: Create a chat completion description: | Generates a model response for a conversation. Set `stream: true` to receive server-sent events. Any request field not listed in the schema (for example `tools`, `tool_choice`, `response_format`, `seed`, `logprobs`, `presence_penalty`, `best_of`) is forwarded to the model server unmodified. The gateway rewrites `model` to the upstream model name, clamps `max_tokens` and `max_completion_tokens` to the model's `max_output_tokens`, and always requests usage from the upstream so it can bill the request; the usage chunk is returned to you only when `stream_options.include_usage` is true. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChatCompletionRequest' examples: nonStreaming: summary: Non-streaming Polish request value: model: polish-pro messages: - role: system content: Jesteś pomocnym asystentem. Odpowiadaj po polsku. - role: user content: Streść w dwóch zdaniach, czym jest faktura VAT. max_tokens: 200 streaming: summary: Streaming with usage in the last chunk value: model: polish-pro stream: true stream_options: include_usage: true messages: - role: user content: Napisz krótki wiersz o Warszawie. responses: '200': description: | With `stream: false` the body is one `ChatCompletion` JSON object. With `stream: true` the body is `text/event-stream`: each event is `data: ` followed by a blank line, and the stream ends with `data: [DONE]`. When `stream_options.include_usage` is true, the last chunk before `[DONE]` has an **empty** `choices` array and a `usage` object. If the stream fails after the response has started (HTTP 200 was already sent), it ends without `data: [DONE]` and without any in-band error event; treat a stream that ends without `[DONE]`, or whose last choice never carried a `finish_reason`, as incomplete. Example streaming transcript: ``` data: {"id":"chatcmpl-0192a1b2c3d4","object":"chat.completion.chunk","created":1758700000,"model":"polish-pro","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} data: {"id":"chatcmpl-0192a1b2c3d4","object":"chat.completion.chunk","created":1758700000,"model":"polish-pro","choices":[{"index":0,"delta":{"content":"Warszawo, "},"finish_reason":null}]} data: {"id":"chatcmpl-0192a1b2c3d4","object":"chat.completion.chunk","created":1758700000,"model":"polish-pro","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: {"id":"chatcmpl-0192a1b2c3d4","object":"chat.completion.chunk","created":1758700000,"model":"polish-pro","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":40,"total_tokens":52}} data: [DONE] ``` headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ChatCompletion' example: id: chatcmpl-0192a1b2c3d4 object: chat.completion created: 1758700000 model: polish-pro choices: - index: 0 message: role: assistant content: Faktura VAT to dokument potwierdzający sprzedaż towaru lub usługi, zawierający kwotę netto, stawkę i kwotę podatku VAT oraz kwotę brutto. Wystawia ją sprzedawca będący podatnikiem VAT. finish_reason: stop usage: prompt_tokens: 41 completion_tokens: 58 total_tokens: 99 text/event-stream: schema: $ref: '#/components/schemas/ChatCompletionChunk' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientQuota' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/ServerError' '502': $ref: '#/components/responses/UpstreamError' '503': $ref: '#/components/responses/ServerError' /v1/embeddings: post: operationId: createEmbedding tags: [Embeddings] summary: Create embeddings description: | Returns dense vectors for one string or an array of strings using the `embed` alias (BGE-M3, 1024 dimensions, multilingual). Only input tokens are billed. Fields not in the schema are forwarded unmodified. There is no fixed cap on the number of inputs per request; the request body itself is capped (20 MiB by default), which returns 400 `invalid_request`. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EmbeddingRequest' example: model: embed input: - Umowa o pracę na czas nieokreślony - Umowa zlecenie responses: '200': description: Embedding vectors in input order. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/EmbeddingResponse' example: object: list model: embed data: - object: embedding index: 0 embedding: [0.0123, -0.0456, 0.0789] - object: embedding index: 1 embedding: [0.0231, -0.0112, 0.0655] usage: prompt_tokens: 14 total_tokens: 14 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientQuota' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/ServerError' '502': $ref: '#/components/responses/UpstreamError' '503': $ref: '#/components/responses/ServerError' /v1/models: get: operationId: listModels tags: [Models] summary: List models description: | Lists every enabled model alias with its capabilities, limits and the price in force right now. This endpoint runs behind authentication only (no balance check, no rate limit) and is the source of truth for pricing; the docs only quote it. Prices and limits shown in this document's examples are illustrative placeholders pending the launch benchmark. responses: '200': description: Enabled models. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ModelList' example: object: list data: - id: polish-pro object: model created: 1758000000 owned_by: siema capabilities: [chat] context_length: 8192 max_output_tokens: 2048 pricing: input_micro_pln_per_1m: 1500000 output_micro_pln_per_1m: 3000000 currency: PLN - id: embed object: model created: 1758000000 owned_by: siema capabilities: [embeddings] context_length: 8192 max_output_tokens: 0 pricing: input_micro_pln_per_1m: 200000 output_micro_pln_per_1m: 0 currency: PLN '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/ServerError' /v1/models/{id}: get: operationId: retrieveModel tags: [Models] summary: Retrieve a model description: Retrieves the model with the given alias. Runs behind authentication only, like `GET /v1/models`. parameters: - name: id in: path required: true description: Model alias, for example `polish-pro`. schema: type: string example: polish-pro responses: '200': description: The model. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/Model' example: id: polish-pro object: model created: 1758000000 owned_by: siema capabilities: [chat] context_length: 8192 max_output_tokens: 2048 pricing: input_micro_pln_per_1m: 1500000 output_micro_pln_per_1m: 3000000 currency: PLN '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/ServerError' components: securitySchemes: ApiKeyAuth: type: http scheme: bearer bearerFormat: sk-siema-<40 characters> description: | Create keys in the console (Keys page). Send as `Authorization: Bearer sk-siema-...` (the `Bearer` scheme is case-insensitive). A malformed key is rejected with 401 before any lookup. Keys are org-scoped and shown once. Revoking a key deletes the gateway's cache entry within one second; the next request with that key gets 401. headers: X-Request-Id: description: | Unique id of this request, minted by the gateway. Include it in support requests; it also appears in the error body's `request_id` field. A client-sent `X-Request-Id` header is never used: it is always ignored, not echoed back. required: true schema: type: string example: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b Retry-After: description: | Seconds to wait before retrying. On `rate_limit_exceeded` this is the time remaining in the per-key window; on `concurrency_limit_exceeded` it is always 1. schema: type: integer minimum: 1 example: 12 responses: BadRequest: description: Malformed JSON, missing `messages`, a parameter outside its allowed range, or a request body over the size limit (`invalid_json`, `invalid_request`, `context_length_exceeded`). headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: message: messages must be a non-empty array type: invalid_request_error code: invalid_request param: messages request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b Unauthorized: description: Missing, malformed, unknown or revoked API key (`invalid_api_key`). headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: message: invalid or revoked API key type: authentication_error code: invalid_api_key param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b InsufficientQuota: description: | The organisation's prepaid balance is not positive (`insufficient_quota`). Top up in the console. This is **402**, not 429 as at OpenAI, so that SDKs do not retry a request that cannot succeed. Checked before the rate limit. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: message: prepaid balance is exhausted; top up in the console type: insufficient_quota code: insufficient_quota param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b Forbidden: description: The organisation is suspended (`org_suspended`). headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: message: this organization is suspended type: permission_error code: org_suspended param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b NotFound: description: Unknown model alias (`model_not_found`), or an endpoint siema_ai does not offer (`unsupported_endpoint`). headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: model: summary: Unknown model alias value: error: message: model "gpt-4o" does not exist type: invalid_request_error code: model_not_found param: model request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b endpoint: summary: Endpoint not offered value: error: message: /v1/images/generations is not supported by this API type: invalid_request_error code: unsupported_endpoint param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b RateLimited: description: Per-key requests-per-minute (`rate_limit_exceeded`) or per-organisation concurrency (`concurrency_limit_exceeded`) limit exceeded. 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' examples: perKey: summary: Requests-per-minute exceeded value: error: message: requests per minute limit exceeded for this API key type: rate_limit_error code: rate_limit_exceeded param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b concurrency: summary: Per-organisation concurrency exceeded value: error: message: too many concurrent requests for this organization type: rate_limit_error code: concurrency_limit_exceeded param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b ServerError: description: | Either an unexpected gateway failure (`internal_error`, HTTP 500) or temporarily no capacity for the requested model (`no_capacity`, HTTP 503, for example when its routing table has no deployments). Safe to retry with backoff. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: internal: summary: Unexpected gateway failure value: error: message: authentication backend unavailable type: server_error code: internal_error param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b noCapacity: summary: No deployment available value: error: message: no deployment available for model "advanced" type: server_error code: no_capacity param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b UpstreamError: description: | The model server could not complete the request (`upstream_unavailable`) or timed out (`upstream_timeout`). A connection failure or a missing first byte within 180 s is retried once on another deployment of the same model; a model-server 4xx other than 400, an idle gap over 60 s once a non-streaming reply has started, and the 600 s total budget are not retried. A streaming reply's HTTP status is already 200 once it starts, so an idle gap there ends the stream without `data: [DONE]` instead of a 502. If the model server produced no output before the failure, nothing is billed; a non-streaming request that fails after the model server started sending its reply is billed the estimated input tokens, and so is a client disconnect at any point after the request reached the model server. Safe to retry with backoff. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: message: could not reach the model server type: upstream_error code: upstream_unavailable param: null request_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b schemas: ContentPart: type: object description: | One part of a multimodal message content array, forwarded to the model server unmodified and unvalidated by the gateway. The model server expects `type` to be `text` or `image_url`. Image parts (`image_url`) are only understood by a model whose `capabilities` include `vision`; none of the V1 aliases do today, so a model server that cannot accept images rejects the request with a 400 that the gateway surfaces as `invalid_request`. properties: type: type: string text: type: string image_url: type: object properties: url: type: string description: HTTPS URL or `data:image/...;base64,...`. additionalProperties: true ChatMessage: type: object required: [role] properties: role: type: string enum: [system, user, assistant, tool] content: description: A string, an array of content parts for multimodal input, or null (an assistant message that carries only tool_calls). oneOf: - type: string - type: array items: $ref: '#/components/schemas/ContentPart' - type: 'null' name: type: string tool_call_id: type: string tool_calls: type: array items: type: object additionalProperties: true ChatCompletionRequest: type: object required: [model, messages] description: | Any additional OpenAI parameter not listed below (for example `tools`, `tool_choice`, `response_format`, `seed`, `logprobs`, `presence_penalty`, `best_of`) is forwarded to the model server unmodified. properties: model: type: string description: A model alias from `GET /v1/models`, for example `polish-fast`, `polish-pro`, `advanced`. example: polish-pro messages: type: array minItems: 1 items: $ref: '#/components/schemas/ChatMessage' stream: type: boolean default: false stream_options: type: object properties: include_usage: type: boolean description: When true, a final chunk with empty `choices` and a `usage` object is sent before `[DONE]`. max_tokens: type: integer minimum: 1 description: "Upper bound on generated tokens. Values above the model's `max_output_tokens` are clamped silently; a clamped answer ends with `finish_reason: \"length\"`." max_completion_tokens: type: integer minimum: 1 description: Alias for `max_tokens`; also clamped to the model's `max_output_tokens`. temperature: type: number description: Forwarded to the model server unchanged, which validates it. OpenAI's range (0-2) is guidance, not enforced by siema_ai. top_p: type: number description: Forwarded to the model server unchanged, which validates it. OpenAI's range (0-1) is guidance, not enforced by siema_ai. stop: description: Forwarded to the model server unchanged, which validates it. siema_ai applies no limit on the number of sequences. oneOf: - type: string - type: array items: type: string n: type: integer description: Forwarded unmodified, with no limit applied by siema_ai. The model server may return more than one choice; every choice's output tokens are billed. user: type: string description: Forwarded unmodified. Not stored by siema_ai. additionalProperties: true Usage: type: object required: [prompt_tokens, total_tokens] description: Token accounting relayed from the model server. `completion_tokens` is absent on embeddings usage. properties: prompt_tokens: type: integer completion_tokens: type: integer total_tokens: type: integer ChatCompletionChoice: type: object required: [index, message] properties: index: type: integer message: type: object properties: role: type: string content: type: [string, "null"] tool_calls: type: array items: type: object additionalProperties: true finish_reason: type: [string, "null"] description: "One of `stop`, `length`, `tool_calls`, `content_filter`. `length` means `max_tokens` (possibly clamped) was reached." ChatCompletion: type: object required: [id, object, created, model, choices] properties: id: type: string object: type: string enum: [chat.completion] created: type: integer description: Unix seconds. model: type: string description: Always the requested alias, never the upstream model name. choices: type: array items: $ref: '#/components/schemas/ChatCompletionChoice' usage: $ref: '#/components/schemas/Usage' ChunkDelta: type: object description: Partial update to a streamed choice's message. Only the fields that changed since the previous chunk are present. properties: role: type: string content: type: [string, "null"] additionalProperties: true ChatCompletionChunkChoice: type: object properties: index: type: integer delta: $ref: '#/components/schemas/ChunkDelta' finish_reason: type: [string, "null"] additionalProperties: true ChatCompletionChunk: type: object required: [id, object, created, model, choices] properties: id: type: string object: type: string enum: [chat.completion.chunk] created: type: integer model: type: string choices: type: array description: Empty in the final usage chunk (sent only when `stream_options.include_usage` is true). Guard `choices[0]` accesses. items: $ref: '#/components/schemas/ChatCompletionChunkChoice' usage: description: Present only on the final chunk when `stream_options.include_usage` is true. $ref: '#/components/schemas/Usage' EmbeddingRequest: type: object required: [model, input] properties: model: type: string example: embed input: oneOf: - type: string - type: array minItems: 1 items: type: string encoding_format: type: string enum: [float, base64] description: When `base64`, each embedding is a base64 string of little-endian float32 values instead of an array of numbers. user: type: string additionalProperties: true Embedding: type: object required: [object, index, embedding] properties: object: type: string enum: [embedding] index: type: integer embedding: description: 1024 floats for `embed`, as an array of numbers, or as a base64 string of little-endian float32 values when `encoding_format` is `base64`. oneOf: - type: array items: type: number - type: string EmbeddingResponse: type: object required: [object, data, model] properties: object: type: string enum: [list] model: type: string data: type: array items: $ref: '#/components/schemas/Embedding' usage: $ref: '#/components/schemas/Usage' Pricing: type: object description: | Prices in micro-PLN (1 PLN = 1 000 000 micro-PLN) per one million tokens, effective right now. All prices are illustrative placeholders until the launch benchmark; `GET /v1/models` is the source of truth. required: [input_micro_pln_per_1m, output_micro_pln_per_1m, currency] properties: input_micro_pln_per_1m: type: integer format: int64 output_micro_pln_per_1m: type: integer format: int64 description: 0 for embedding models. currency: type: string enum: [PLN] Model: type: object required: [id, object, created, owned_by, capabilities, context_length, max_output_tokens, pricing] properties: id: type: string description: The alias to pass as `model`. example: polish-pro object: type: string enum: [model] created: type: integer owned_by: type: string enum: [siema] capabilities: type: array items: type: string enum: [chat, embeddings, vision] context_length: type: integer description: Maximum prompt plus completion tokens. max_output_tokens: type: integer description: Ceiling applied to `max_tokens`. 0 for embedding models. pricing: $ref: '#/components/schemas/Pricing' ModelList: type: object required: [object, data] properties: object: type: string enum: [list] data: type: array items: $ref: '#/components/schemas/Model' Error: type: object required: [message, type, code, param] properties: message: type: string description: Human-readable, safe to show to developers. Never contains prompt, completion or embedding text. type: type: string enum: - invalid_request_error - authentication_error - permission_error - insufficient_quota - rate_limit_error - server_error - upstream_error code: type: string description: Stable machine-readable code. enum: - invalid_json - invalid_request - context_length_exceeded - invalid_api_key - org_suspended - model_not_found - unsupported_endpoint - insufficient_quota - rate_limit_exceeded - concurrency_limit_exceeded - no_capacity - internal_error - upstream_unavailable - upstream_timeout param: type: [string, "null"] description: The offending request field, or null. request_id: type: string description: Same value as the response's `X-Request-Id` header. ErrorResponse: type: object required: [error] properties: error: $ref: '#/components/schemas/Error'