# Build with SimpleLLM Connect your application to the SimpleLLM inference API using familiar OpenAI-compatible requests. Start with a single chat completion, add streaming or tools, then integrate usage and error handling before shipping. ## Start here New integration? Follow the [Quickstart](/docs/quickstart.html) to create a key, select a model, and receive your first response. Already using a coding tool? The [OpenCode](/docs/opencode.html) and [OpenClaw](/docs/openclaw.html) setup guides configure the provider for you. For [Zed](/docs/zed.html), copy the sample provider configuration and add your API key in the editor. The API base URL is `https://api.simplellm.eu/v1`. For chat, use `POST /v1/chat/completions`; the website and dashboard are not inference endpoints. ## Guides and API reference Guides explain how to build a working integration, including the parts your application must handle. The HTTP reference describes individual endpoints, request fields, response fields, content types, and errors. - [Chat completions](/docs/chat.html): messages, system instructions, and conversation history. - [Streaming](/docs/streaming.html): incremental output, usage events, cancellation, and partial results. - [Tool calling](/docs/tools.html): a complete function-call round trip with argument validation. - [Vision](/docs/vision.html): URL and inline image requests for compatible models. - [Reasoning and JSON](/docs/reasoning.html): output budgets and model-dependent structured responses. - [Speech](/docs/audio.html) and [images](/docs/images.html): separate media endpoints and response handling. ## Before going live Use [rate limits and bounded retries](/docs/limits.html), decide how to handle an interrupted response, and understand [token and cache billing](/docs/billing.html). Monitor [usage per API key](/docs/usage.html) and keep credentials out of browser code. > Model-specific capabilities are not interchangeable: an OpenAI-compatible request format does not make every model support images, tools, or JSON schemas. ## For coding assistants Every guide and reference page has a Markdown version linked at the top. [llms.txt](/llms.txt) indexes individual pages; [llms-full.txt](/llms-full.txt) contains the full text, and [OpenAPI JSON](/openapi.json) contains the HTTP contract. The HTML content is generated ahead of time and remains readable without JavaScript. Search, copying, and theme controls are optional conveniences. --- # Your first API request This guide sends one small request and prints the answer. You need an account, an API key, and credits when using a paid model. ## 1. Create an API key Open [API Keys in the dashboard](https://app.simplellm.eu/api-keys) and create a key. Copy the secret when it is displayed and keep it on your server. Set an environment variable in the terminal where you will run the examples: ~~~bash export SIMPLELLM_API_KEY='YOUR_API_KEY' export SIMPLELLM_MODEL='qwen3.8-flash-next' ~~~ ~~~powershell $env:SIMPLELLM_API_KEY = 'YOUR_API_KEY' $env:SIMPLELLM_MODEL = 'qwen3.8-flash-next' ~~~ `qwen3.8-flash-next` is an example ID verified in the public catalogue on 2026-09-14, not a permanent availability promise. Replace it with your selected chat model. ## 2. Check the model catalogue ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/models \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ Read each model's `id`. Prices and context windows are in the [model catalogue](/docs/models.html), not in the standard OpenAI model-list response. ## 3. Send a chat completion The cURL example requires cURL and jq; Python and TypeScript examples are on the [client examples page](/docs/sdks.html). ~~~bash jq -n --arg model "$SIMPLELLM_MODEL" '{ model: $model, messages: [ {role: "system", content: "Answer briefly and clearly."}, {role: "user", content: "What is an API?"} ], max_tokens: 1024, stream: false }' | curl --fail-with-body https://api.simplellm.eu/v1/chat/completions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @- ~~~ This is a billed inference request when the selected model has a price. ## 4. Read the answer and usage The response has a `choices` array. Read `choices[0].message.content` for the answer and `usage` for the reported token counts. This is an illustrative shape, not an actual request receipt: ~~~json { "id": "chatcmpl-example", "object": "chat.completion", "created": 1789380000, "model": "qwen3.8-flash-next", "choices": [{ "index": 0, "message": {"role": "assistant", "content": "An API is an interface that lets software communicate."}, "finish_reason": "stop" }], "usage": {"prompt_tokens": 28, "completion_tokens": 18, "total_tokens": 46} } ~~~ A reasoning model may use part of its output budget before returning visible text. If the answer is empty and the finish reason is `length`, inspect the budget and [reasoning guidance](/docs/reasoning.html). ## Next steps - [Stream the response](/docs/streaming.html) instead of waiting for the entire answer. - [Add conversation history](/docs/chat.html) for follow-up questions. - [Connect OpenCode](/docs/opencode.html) or [OpenClaw](/docs/openclaw.html). - [Handle errors](/docs/errors.html) before deploying an integration. --- # Authentication The public inference API uses a SimpleLLM API key in the HTTP Authorization header. A dashboard login session is not a replacement for that key. ## Create and use a key 1. Sign in to the [dashboard](https://app.simplellm.eu). 2. Open API Keys and create a named key for the application. 3. Copy the secret when shown and store it in your application's secret configuration. 4. Send the key with each request. ~~~http Authorization: Bearer YOUR_API_KEY Content-Type: application/json ~~~ The Content-Type above is for JSON requests. Audio uploads use multipart form data, and GET requests do not need a JSON body. ## Store credentials safely Use environment variables or a secret manager on your backend. Never embed a secret in a frontend bundle, public repository, URL query string, screenshot, or support report. ~~~bash export SIMPLELLM_API_KEY='YOUR_API_KEY' curl --fail-with-body https://api.simplellm.eu/v1/keys/current \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ This read-only endpoint returns metadata for the key used to authenticate; it does not return the secret again. ## Separate applications and environments Use separate keys for development and production and for different integrations. That makes key-specific usage easier to attribute and lets you revoke one integration's credential without changing all applications. Account-level limits still apply across keys. Creating more keys does not create more account capacity. ## Rotate a key Create a replacement key, update the application's secret, verify a small request, then revoke the old key in the dashboard. Do not revoke the old key before the application has switched unless it is compromised and immediate revocation is necessary. ## Authentication failures `401` indicates missing or invalid credentials. Check the full header, whitespace, the selected environment, and whether the key was revoked. A `402` is a credit problem, not a malformed authentication header. See [troubleshooting](/docs/errors.html) for other failures and [API key metadata](/docs/reference/getCurrentKey.html) for returned fields. --- # Models and pricing Select a model by its API ID, not its display label. The table below loads current model metadata from the public API, including context size and separate input, cached-input, and output prices; without JavaScript or if the refresh fails, it retains a clearly dated fallback. ## Discover model IDs ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/models \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ The OpenAI-compatible response contains `data[].id`, `object`, `created`, and `owned_by`. It does not include prices or a capability matrix. ## Fetch pricing metadata The public catalogue needs no API key: ~~~bash curl --fail-with-body https://simplellm.eu/api/models ~~~ | Field | Meaning | | --- | --- | | `id` | Model ID to use in a request | | `display_name` | Human-readable label; not necessarily the request ID | | `context_length` | Advertised context size in tokens, or null | | `price_sc_input` | SC per million uncached input tokens | | `price_sc_cached` | SC per million cached input tokens; null uses the input price | | `price_sc_output` | SC per million output tokens | | `enabled` / `status` | Catalogue state; filter out disabled or planned entries | | `throughput_tps` | Indicative throughput, or null; not guaranteed request speed | > The current V1 catalogue has a known alias-filtering limitation: a listed alias can refer to a disabled backend model. Listing is not proof of readiness; handle unavailable-model errors rather than assuming every listed ID can immediately serve traffic. ## Choose for your workload Check the input context, expected output budget, token prices, and the features your integration needs. A large context window alone does not establish support for images, tool calling, or structured output. Use a short representative request to validate model-specific behavior before a larger job. Do not benchmark production with bulk test traffic. ## Context and cost Conversation history is sent again with subsequent requests. Keep the system instructions and stable context ordered consistently when possible, but do not assume this guarantees a cache hit. The maximum usable input depends on the model's context and output budget. Advertised context is not a promise that the entire window can be filled with input while still generating an arbitrary amount of output. See [billing](/docs/billing.html) for cache accounting and a worked cost example. Audio and image models are configured separately and have their own guides. ## Model catalogue Static fallback fetched 2026-09-14 from the [public catalogue](https://simplellm.eu/api/models). The browser refreshes this table from the API on page load. Markdown and readers without JavaScript retain this dated fallback; fetch the public catalogue for current data. Only entries reported as enabled and available are included. Throughput is indicative, not an SLA. | Model ID | Context tokens | Input SC/M | Cache SC/M | Output SC/M | Approx tok/s | | --- | --- | --- | --- | --- | --- | | `DeepSeek-V4-Flash` | 1048576 | 32 | 8 | 48 | 65 | | `DeepSeek-V4-Pro` | 1048576 | 262 | 66 | 523 | 86 | | `gemma-4-E4B` | 131072 | 0 | 0 | 0 | 49 | | `GLM-5.1` | 202752 | 209 | 52 | 658 | 96 | | `GLM-5.2` | 1048576 | 195 | 50 | 585 | 81 | | `glm-5.3-flash` | 1048576 | 26 | 7 | 65 | 99 | | `gpt-oss-120b` | 131072 | 16 | 16 | 83 | 47 | | `gpt-oss-20b` | 131072 | 8 | 8 | 30 | 65 | | `Kimi-K2.6` | 262144 | 150 | 37 | 598 | 97 | | `Kimi-K2.7-Code` | 262144 | 163 | 40 | 585 | 170 | | `Kimi-K3` | 1048576 | 390 | 100 | 1950 | 47 | | `Meta-Llama-3_3-70B-Instruct` | 131072 | 139 | 139 | 139 | 26 | | `minimax-m3` | 1048576 | 52 | 13 | 260 | 52 | | `Mistral-Nemo-Instruct-2407` | 65536 | 27 | 27 | 27 | 30 | | `Mistral-Small-3.2-24B-Instruct-2506` | 131072 | 18 | 18 | 58 | 14 | | `Qwen3-Coder-30B-A3B-Instruct` | 262144 | 11 | 11 | 39 | 35 | | `Qwen3.5-397B-A17B` | 262144 | 85 | 85 | 510 | 124 | | `Qwen3.5-9B` | 262144 | 16 | 16 | 24 | 31 | | `Qwen3.6-27B` | 262144 | 55 | 55 | 385 | 39 | | `qwen3.8-2.4t-a95b` | 262144 | 325 | 82 | 780 | — | | `qwen3.8-flash-next` | 262144 | 26 | 7 | 65 | 57 | --- # Chat completions Send messages to `POST /v1/chat/completions`. Without streaming, the API returns a JSON completion after generation finishes. ## A complete request This example uses a model listed in the public catalogue on 2026-09-14. Replace the ID if your integration uses another model. ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/chat/completions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-flash-next", "messages": [ {"role": "system", "content": "You explain programming concepts concisely."}, {"role": "user", "content": "Explain the difference between a queue and a stack."} ], "temperature": 0.3, "max_tokens": 2048, "stream": false }' ~~~ ## Messages and conversation history Use a `system` message for persistent instructions, `user` for input, and `assistant` for prior model responses. Tool-enabled conversations also use `tool` messages. Chat completions do not supply a conversation-storage API: your application sends the relevant history with each request. A follow-up can look like this: ~~~json { "model": "qwen3.8-flash-next", "messages": [ {"role": "system", "content": "Use concise examples."}, {"role": "user", "content": "What is a stack?"}, {"role": "assistant", "content": "A stack removes the most recently added item first."}, {"role": "user", "content": "Show one in Python."} ], "max_tokens": 2048 } ~~~ Keep your own system instructions in subsequent requests. V1 can apply a configured model system prompt when no system message is supplied; send an explicit system message when your application needs to control it. ## Generation parameters | Parameter | Purpose | Integration guidance | | --- | --- | --- | | `model` | Select the model | Required; use an exact catalogue ID | | `messages` | Conversation input | Required, nonempty array | | `max_tokens` | Output budget | Positive integer; reasoning may consume it | | `temperature` | Sampling variability | Accepted range and behavior depend on the model | | `top_p` | Nucleus sampling | Tune one sampling control at a time | | `stream` | Incremental delivery | Defaults to false | | `stop` | Stop sequences | Use an array of strings in V1, not a single string | | `tools` / `tool_choice` | Function-call interface | Requires tool-capable model behavior | | `response_format` | Requested output structure | Forwarded option; support is model-dependent | Avoid assuming upstream-specific options work for every model. The [HTTP reference](/docs/reference/createChatCompletion.html) documents the request and response structure. ## Read the result Read `choices[0].message.content` for a regular text response. Preserve `tool_calls` when a tool-capable model asks your application to run a function. | Common finish reason | Interpretation | | --- | --- | | `stop` | Normal completion or a stop condition | | `length` | Output budget reached; result can be incomplete | | `tool_calls` | Model requested a tool; continue the tool loop | | null in stream chunks | Generation has not yet supplied a final reason | The exact finish reasons and optional response fields depend on the model and provider. Inspect `usage` separately from visible text; output tokens can include reasoning. ## Next steps [Stream output](/docs/streaming.html), [add tools](/docs/tools.html), or [send an image](/docs/vision.html). For a long-running application, implement [cancellation and retry handling](/docs/limits.html) before increasing concurrency. --- # Streaming and cancellation Streaming lets your application display output as it arrives. Set `stream: true`; the response uses Server-Sent Events instead of one JSON document. ## Request a stream ~~~bash curl --no-buffer --fail-with-body https://api.simplellm.eu/v1/chat/completions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-flash-next", "messages": [{"role": "user", "content": "Explain how an HTTP request is handled."}], "max_tokens": 2048, "stream": true, "stream_options": {"include_usage": true} }' ~~~ The usage option requests a final usage report where supported. It cannot guarantee a report after an interrupted connection. ## Understand the events This shortened example illustrates event framing rather than a complete provider-specific response: ~~~text data: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} data: {"choices":[{"index":0,"delta":{"content":"An HTTP request"},"finish_reason":null}]} data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: {"choices":[],"usage":{"prompt_tokens":20,"completion_tokens":40,"total_tokens":60}} data: [DONE] ~~~ - Append text from `choices[].delta.content`; do not replace the entire response with each delta. - An event can contain a role, reasoning, tool-call arguments, or usage instead of visible text. - Usage-only events may have an empty `choices` array. - A TCP/HTTP read can contain part of an event or several events; it is not a token boundary. - If writing an SSE parser, buffer incomplete lines, support CRLF, and terminate an event at a blank line; join multiple data lines before JSON parsing. Use an existing SDK's stream reader when possible. Do not count events or network chunks as exact tokens. ## Python stream with explicit cleanup Install the `openai` package and set `SIMPLELLM_API_KEY` and `SIMPLELLM_MODEL` as described in the [Quickstart](/docs/quickstart.html). ~~~python import os from openai import OpenAI client = OpenAI( base_url="https://api.simplellm.eu/v1", api_key=os.environ["SIMPLELLM_API_KEY"], max_retries=0, timeout=600.0, ) stream = client.chat.completions.create( model=os.environ["SIMPLELLM_MODEL"], messages=[{"role": "user", "content": "Explain request streaming."}], max_tokens=2048, stream=True, stream_options={"include_usage": True}, ) try: for chunk in stream: if chunk.usage is not None: print("\nUsage:", chunk.usage) for choice in chunk.choices: if choice.delta.content: print(choice.delta.content, end="", flush=True) finally: stream.close() ~~~ The client timeout here is an application choice, not a SimpleLLM platform timeout guarantee. Interrupting the program exits the loop and closes the stream in the cleanup block. ## Cancel the upstream request When the user presses Stop, close or abort the HTTP request; merely hiding the output in the UI does not cancel generation. If a browser talks to your backend, propagate the browser's disconnect to your backend's upstream request as well. In the JavaScript SDK, abort the stream's controller when the application cancels: ~~~javascript // Inside the stream loop or your application's Stop handler: stream.controller.abort(); ~~~ The complete JavaScript client setup is in [client examples](/docs/sdks.html). > An interrupted stream with zero recorded output tokens is not charged. Already delivered output can still incur a charge, and missing final usage can require an estimate. See [partial-usage billing](/docs/billing.html). ## Interrupted streams Treat connection closure without the expected completion signals as an incomplete result. Preserve delivered text with an incomplete/cancelled state so the user can distinguish it from a successful answer. An HTTP 200 only confirms that streaming began. After headers have been sent, a later failure cannot reliably become a new HTTP error status; inspect stream events and connection errors too. Do not silently replay a request after partial output. Retrying creates a new generation that can duplicate content, tool actions, and charges. --- # Tool calling A tool-capable model can request that your application run a function. SimpleLLM returns the model's request; your application validates it, executes the permitted action, and returns the result. > The example below requires a model that supports OpenAI-style function calling. The public model list does not expose a verified tool-capability flag; validate your chosen model before relying on this workflow. ## The request/result loop 1. Describe the available functions in `tools` and send the user's message. 2. Inspect the assistant's `tool_calls`. 3. Validate the function name and arguments against your allowlist and schema. 4. Append the assistant message and one `tool` result for each call. 5. Send the updated messages so the model can answer using the result. ## Complete Python example This example uses a read-only local demo function, with a maximum of four model rounds. Install `openai`, set `SIMPLELLM_API_KEY`, and set `SIMPLELLM_MODEL` to your validated tool-capable model. ~~~python import json import os from openai import OpenAI client = OpenAI( base_url="https://api.simplellm.eu/v1", api_key=os.environ["SIMPLELLM_API_KEY"], max_retries=0, ) tools = [{ "type": "function", "function": { "name": "lookup_stock", "description": "Read demo stock for one product SKU.", "parameters": { "type": "object", "properties": {"sku": {"type": "string"}}, "required": ["sku"], "additionalProperties": False, }, }, }] messages = [{"role": "user", "content": "How many DEMO-1 items are in stock?"}] for _ in range(4): response = client.chat.completions.create( model=os.environ["SIMPLELLM_MODEL"], messages=messages, tools=tools, tool_choice="auto", max_tokens=2048, ) answer = response.choices[0].message if not answer.tool_calls: print(answer.content or "") break messages.append(answer.model_dump(exclude_none=True)) for call in answer.tool_calls: try: if call.function.name != "lookup_stock": raise ValueError("Unknown function") args = json.loads(call.function.arguments) if not isinstance(args, dict) or set(args) != {"sku"}: raise ValueError("Expected only sku") if not isinstance(args["sku"], str) or len(args["sku"]) > 64: raise ValueError("Invalid sku") result = {"sku": args["sku"], "stock": {"DEMO-1": 12}.get(args["sku"], 0)} except (ValueError, TypeError) as error: result = {"error": str(error)} messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result), }) else: raise RuntimeError("Tool round limit reached") ~~~ ## Streamed tool calls With streaming, the tool name and JSON arguments may arrive in separate deltas. Accumulate calls by their choice and tool-call index; concatenate argument fragments and parse the JSON only once the call is complete. Do not execute an incomplete argument fragment or assume each event contains a complete function call. Multiple tool calls can occur in one assistant response. ## Safety and reliability Treat generated tool arguments as untrusted input. Never dispatch a model-supplied name directly to arbitrary code, SQL, or a shell. Require user approval for destructive or externally visible operations where appropriate. Use your own action IDs and idempotency rules so retries cannot repeat a payment, email, or deletion. Limit tool rounds, execution time, and response size. Tool results become new model input and can contribute to both context size and cost. ## Reference See [Chat completions API](/docs/reference/createChatCompletion.html) for fields and [streaming](/docs/streaming.html) for event handling. --- # Vision inputs For a vision-capable model, send a user message containing text and image content parts. The endpoint remains `POST /v1/chat/completions`. ## Check support first Model capabilities are separate from the HTTP request format. A text model does not gain vision support because an image object was included in its request. Set `SIMPLELLM_MODEL` to a model whose vision support you have verified for your application. This guide deliberately does not label every chat model as vision-capable. ## Send an image URL The image must be reachable by the model service. A URL on localhost or your private network will not generally be accessible from the service. ~~~python import os from openai import OpenAI client = OpenAI( base_url="https://api.simplellm.eu/v1", api_key=os.environ["SIMPLELLM_API_KEY"], max_retries=0, ) response = client.chat.completions.create( model=os.environ["SIMPLELLM_MODEL"], messages=[{ "role": "user", "content": [ {"type": "text", "text": "Describe the visible objects in this image."}, {"type": "image_url", "image_url": {"url": os.environ["IMAGE_URL"]}}, ], }], max_tokens=2048, ) print(response.choices[0].message.content) ~~~ Use a direct image URL, not an HTML gallery page. Avoid credentials in URLs; use inline input for private images if the model's backend supports it. ## Send a local image inline Replace the image URL with a data URL containing the matching MIME type and base64 bytes. This example assumes a JPEG file: ~~~python import base64 from pathlib import Path encoded = base64.b64encode(Path("photo.jpg").read_bytes()).decode("ascii") image_part = { "type": "image_url", "image_url": {"url": "data:image/jpeg;base64," + encoded}, } # Use image_part in the content array of the previous request. ~~~ Do not label PNG data as JPEG or pass a local filename as the remote URL. Inline data increases request size; resize unnecessarily large images before sending them. ## Multiple images and detail Where supported by the selected model, multiple `image_url` parts can accompany a question. Preserve the intended order and explicitly tell the model which image you are referring to. The optional `image_url.detail` field uses values such as `auto`, `low`, or `high`, but support and its effect are backend-dependent. There is no single documented V1 image-count or resolution guarantee across all models. ## Diagnose incorrect results If the model ignores the picture, first confirm model support and that the image is reachable and decodable. If the service rejects the request, inspect the error and request size; do not repeatedly resend the same invalid payload. Text extraction through a vision chat request is not a separate OCR API. The V1 public router documented here does not expose the direct GPU-host OCR endpoint. --- # Reasoning and structured output Reasoning models may generate internal reasoning before their visible answer. Structured-output options request a particular answer format; these are separate features and both depend on the selected model. ## Budget for reasoning `max_tokens` is an output budget, not a guarantee of that many visible words. Reasoning can consume part or all of it. If a response has little visible content, inspect the finish reason and token usage before assuming nothing was generated. A `length` finish reason suggests the output budget was reached. Start with a budget appropriate to the task and cap it deliberately. Increasing the budget can increase latency and cost; a very small budget can prevent a reasoning model from ever reaching its answer. ## Read reasoning fields defensively Providers may expose optional fields such as `reasoning_content` or `reasoning`. Do not assume one field exists on every model, or that reasoning appears in `content`. ~~~python # Inside a Python streaming loop: for choice in chunk.choices: delta = choice.delta reasoning = ( getattr(delta, "reasoning_content", None) or getattr(delta, "reasoning", None) ) if reasoning: print(reasoning, end="", flush=True) if delta.content: print(delta.content, end="", flush=True) ~~~ For a UI, keep optional reasoning and the final answer in separate buffers. Do not require visible reasoning as a condition for a successful answer. ## Request JSON Some models accept `response_format` with `json_object`. This option is forwarded; V1 does not implement a universal JSON-constraining decoder for every backend. ~~~python import json import os from openai import OpenAI client = OpenAI( base_url="https://api.simplellm.eu/v1", api_key=os.environ["SIMPLELLM_API_KEY"], max_retries=0, ) response = client.chat.completions.create( model=os.environ["SIMPLELLM_MODEL"], messages=[ {"role": "system", "content": "Return JSON with exactly title (string) and tags (array of strings)."}, {"role": "user", "content": "Summarize this topic: HTTP caching."}, ], response_format={"type": "json_object"}, max_tokens=2048, ) choice = response.choices[0] if choice.finish_reason == "length": raise RuntimeError("Output truncated; do not consume partial JSON") data = json.loads(choice.message.content or "") if not isinstance(data, dict) or set(data) != {"title", "tags"}: raise ValueError("Unexpected JSON fields") if not isinstance(data["title"], str) or not isinstance(data["tags"], list): raise ValueError("Unexpected JSON types") if not all(isinstance(tag, str) for tag in data["tags"]): raise ValueError("Tags must be strings") print(data) ~~~ Choose a model supporting this option. If it rejects the parameter, use a supported model or a prompt-based format with your own validation; do not describe a prompt alone as a strict format guarantee. ## JSON schemas and provider options `json_schema`, strict schema enforcement, reasoning-effort settings, and model-specific thinking switches are not universal V1 guarantees. Confirm support for the exact model and configuration before making your application depend on them. Regardless of the output mode, validate types, required fields, and application constraints before using generated values. JSON syntax validity is not equivalent to a correct or safe business decision. ## Related guides [Tool calling](/docs/tools.html) covers executable actions, while [streaming](/docs/streaming.html) explains incomplete responses and final usage events. --- # Speech and transcription V1 exposes separate endpoints for text-to-speech and file transcription. They share API-key authentication with chat but use different input formats and billing units. > These are the mappings and validations in the current V1 implementation. Audio service availability is separate from the chat model list; these documentation examples are not evidence of a live audio test. ## Generate speech Send JSON to `POST /v1/audio/speech` and save the returned audio bytes: ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/audio/speech \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "tts-1", "input": "Welcome to SimpleLLM.", "voice": "alloy", "response_format": "mp3", "speed": 1 }' \ --output speech.mp3 ~~~ | Field | Default | V1 behavior | | --- | --- | --- | | `input` | Required | Nonempty, at most 4096 UTF-8 bytes | | `model` | `tts-1` | Forwarded to the configured speech service | | `voice` | `alloy` | alloy, echo, fable, onyx, nova, shimmer | | `response_format` | `mp3` | mp3, opus, aac, flac, wav, pcm | | `speed` | 1 | 0.25 to 4; zero is normalized to the default | The response is binary audio, not JSON containing an audio URL. Check HTTP status before treating the saved body as a valid audio file; an error body can contain JSON instead. UTF-8 bytes are not the same as characters. Non-ASCII text can reach the input limit with fewer than 4096 characters. ## Transcribe a file Send multipart form data to `POST /v1/audio/transcriptions`: ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/audio/transcriptions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -F 'file=@recording.wav' \ -F 'model=whisper-large-v3-turbo' \ -F 'language=en' \ -F 'response_format=json' ~~~ Do not manually set a multipart Content-Type header when using cURL or a form-data library: the client must include its generated boundary. The two explicit V1 model mappings are: | Model ID | Mapping | Configured SC/min | | --- | --- | --- | | `whisper-large-v3-turbo` | Turbo transcription | 0.5 | | `whisper-large-v3` | Large-v3 transcription | 1.5 | The compatibility name `whisper-1` maps to `whisper-large-v3-turbo`; it is not another model. Omitting the model selects this alias, but explicit IDs are clearer for new integrations. ## Optional transcription fields `language`, `prompt`, `response_format`, and `temperature` are forwarded to the transcription service. For language, use a two-letter code such as `de` or `en`; omit it when automatic detection is desired instead of sending a UI label. JSON responses contain text, for example: ~~~json {"text": "Welcome to SimpleLLM."} ~~~ Other output formats depend on the upstream service. Inspect Content-Type and do not force every returned body through a JSON parser. ## Audio billing and limitations Current V1 speech billing is 3 SC per 1000 input UTF-8 bytes. Transcription uses the configured per-minute rate and an estimated duration derived from file size and extension; it is not guaranteed to be an exact measured duration. These implementation rates were checked on 2026-09-14 and are distinct from chat's per-million-token pricing. Confirm current account pricing before budgeting a larger workload. The direct GPU-host voice catalogue, voice design, and voice cloning features are not part of this public V1 interface. See the [speech reference](/docs/reference/createSpeech.html) and [transcription reference](/docs/reference/createTranscription.html). --- # Image generation Send a text prompt to `POST /v1/images/generations`. The V1 handler uses its configured image service; the request's `model` field does not select arbitrary image backends. ## Generate an image ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/images/generations \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A small lighthouse at dawn, watercolor illustration", "n": 1, "size": "1024x1024" }' ~~~ The configured model in the documented V1 handler is `stable-diffusion-xl-base-v10`. This is a configuration fact, not confirmation of current upstream availability. ## Request fields | Field | Behavior | | --- | --- | | `prompt` | Required, nonempty text | | `n` | Defaults to 1; maximum 4; V1 normalizes nonpositive values to 1 | | `size` | Accepted and defaults to 1024x1024 locally, but is not forwarded by V1; it does not control generated dimensions | | `model` | Does not change the V1 configured image backend | | `response_format` | Optional, forwarded; response support depends on the service | Multiple requested images are generated sequentially in V1. Do not assume a request for four images takes the same time as one. ## Handle the response The response has a `created` timestamp and `data` array. Each entry can contain a URL or base64 image data: ~~~json { "created": 1789380000, "data": [ {"url": "https://api.simplellm.eu/images/EXAMPLE_IMAGE_ID"} ] } ~~~ The URL above illustrates the response shape; always use the returned URL rather than constructing one. When `b64_json` is returned, decode it as base64 image bytes. The V1 image proxy stores returned URL mappings with a 24-hour expiry. Download needed results promptly and store them in your application; do not treat generated links as permanent asset hosting. ## Cost and failures The V1 implementation checked on 2026-09-14 charges 5 SC per generated image. A four-image request therefore has a configured cost of 20 SC; verify current pricing before a larger job. Handle `402` as insufficient credits and upstream errors as a failed request, not an image result. Avoid uncontrolled retries: image generation is a new operation each time. See [Image generation API](/docs/reference/createImage.html) for the schema and [vision inputs](/docs/vision.html) if your goal is to analyze an existing image instead. --- # Tokens, cache and billing Text generation is priced in SimpleCredits (SC) per million tokens, with separate rates for uncached input, cached input, and output. ## Read usage correctly In OpenAI-style usage, cached input is included in `prompt_tokens`. Never add it to the prompt total a second time. ~~~json { "prompt_tokens": 100000, "completion_tokens": 10000, "total_tokens": 110000, "prompt_tokens_details": {"cached_tokens": 70000} } ~~~ This example means 30,000 uncached input tokens, 70,000 cached input tokens, and 10,000 output tokens. A dashboard may show uncached input and cache separately; check the field semantics before comparing totals. ## Calculate text cost ~~~text uncached_input = prompt_tokens - cached_tokens cost_sc = ( uncached_input * input_rate + cached_tokens * cache_rate + completion_tokens * output_rate ) / 1_000_000 ~~~ When `price_sc_cached` is null, the regular input price applies to cached input too; the current V1 billing implementation also treats a zero cache rate as this fallback, not as free caching. A low cache rate is not a promise of a cache hit. ### Worked example Using DeepSeek-V4-Flash catalogue rates observed on 2026-09-14 (32 SC/M input, 8 SC/M cache, 48 SC/M output): | Component | Tokens | SC/M | Cost SC | | --- | --- | --- | --- | | Uncached input | 30,000 | 32 | 0.96 | | Cached input | 70,000 | 8 | 0.56 | | Output | 10,000 | 48 | 0.48 | | Total | 110,000 | — | 2.00 | This is an illustrative estimate using those rates, not an invoice or a guarantee of future pricing. Final recorded usage determines the charge. ## Encourage reusable input Keep stable system instructions, tool definitions, and shared context in a consistent order, and append changing input after stable content where your workflow permits. Caching is managed by the model service. V1 does not expose a universal cache reservation, cache lifetime, hardware affinity, or guaranteed cache-hit API. Long conversations resend history and increase input volume. Consider summarization or selective history when the task allows it; do not remove information the model still needs solely to chase a cache percentage. ## Partial usage after cancellation An interrupted streaming request with zero recorded output tokens is not charged; its credit reservation is released. Output includes reported reasoning or tool output, not just visible answer text. Non-streaming requests are streamed internally and buffered until completion. If canceled, output usage comes from provider counts or the output already received by SimpleLLM, including transmitted reasoning and tool calls. Elapsed time alone does not create output charges. Reported input usage may still be charged for a canceled non-streaming request. Once a stream has produced output, cancellation does not erase that partial usage. When final provider usage is available, the backend can use it; when the stream is interrupted before final usage, V1 can estimate usage from the request and observed output. Automatically discarded upstream attempts are not added to the customer charge. A failed non-streaming generation that produces no usable answer is not charged. The current estimated-input fallback assumes 70% cached input. That percentage is a billing fallback, not a measurement of actual cache use or a promised cache hit on the next request. A cancelled call may appear as `499` in request logs even though the client cannot receive that status after disconnecting. Keep incomplete-response handling separate from whether the request has a charge. ## Account balance versus usage window `GET /v1/usage` returns daily usage for the last 30 days alongside current `balance` and lifetime `total_spent`. Adding 30-day spending to current balance does not reconstruct lifetime purchases. Inspect [usage and key endpoints](/docs/usage.html) for integration details. Audio and images have separate units described in their guides. --- # Rate limits and retries An integration needs both a request-rate limit and a concurrency bound. Sending fewer requests per minute does not prevent long generations from filling all concurrent slots. ## Read the current status ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/rate-limit \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ The V1 status includes general requests-per-minute counters and free-model hourly/daily request and token counters. It is not a complete summary of every key, model, and upstream limit. | Fields | Meaning | | --- | --- | | `limit_rpm`, `used_rpm`, `remaining_rpm` | General minute request bucket | | `limit_rph`, `used_rph`, `remaining_rph` | Free-model hourly requests | | `limit_rpd`, `used_rpd`, `remaining_rpd` | Free-model daily requests | | `limit_tph`, `used_tph`, `remaining_tph` | Free-model hourly tokens | | `limit_tpd`, `used_tpd`, `remaining_tpd` | Free-model daily tokens | | `reset_seconds` | Seconds until the minute bucket resets, not all buckets | Read key-specific configuration with [current key metadata](/docs/reference/getCurrentKey.html). Do not copy a fixed Free/Standard/Premium rate table into your integration; active limits depend on configuration. ## Response headers V1 can return `X-RateLimit-Limit` and `X-RateLimit-Remaining` for the general request limit. Rate-limit or capacity responses can include `Retry-After`. ~~~http HTTP/1.1 429 Too Many Requests Content-Type: application/json Retry-After: 30 ~~~ Not every response or upstream failure contains every header. Parse available headers defensively. ## Decide whether to retry | Situation | Suggested behavior | | --- | --- | | 400 or 404 | Fix request parameters, model selection, or URL | | 401 or 403 | Fix credentials or permissions | | 402 | Resolve insufficient credits | | 429 before output | Respect Retry-After and reduce load | | 502 / 503 / 504 before output | Consider a bounded retry; first failure may still have incurred work | | Stream failed after output | Mark incomplete; no silent replay | | User cancelled | Stop; do not retry automatically | A missing response does not prove that no upstream work occurred. There is no documented universal idempotency guarantee for chat requests. ## Backoff with a budget For a retryable failure before output, wait at least the server's Retry-After value when present. Without it, increase the delay between attempts and add random jitter so concurrent clients do not retry together. Set a small maximum number of attempts and a total elapsed-time budget. Stop when either is reached; do not retry forever. If your SDK has built-in retries, avoid adding a second uncoordinated retry loop around it. ## Bound concurrency Use an application queue or semaphore around inference requests. Release the slot in a cleanup block after success, error, or cancellation; a streaming request occupies its slot until the stream is closed. Separate time to first output from ongoing stream progress. A reasoning model can take time before visible text appears; choose application deadlines based on the task and expose waiting/cancelled states to your users. ## Free models Free-model request and token budgets are separate from paid-model pricing. Check the current counters instead of treating a zero token price as unlimited throughput. The status route itself is an authenticated API request. Poll at a reasonable interval, not for every generated token. --- # Troubleshooting Check the HTTP status and content type before decoding the body. Chat errors generally have an `error` object, while media endpoints can also forward upstream error formats. ## Error response shape This is an illustrative error, not a promise of identical wording for every endpoint: ~~~json { "error": { "message": "Invalid API key provided.", "type": "authentication_error", "code": "invalid_api_key" } } ~~~ Treat `message`, `type`, and `code` as optional when parsing error bodies. Some capacity errors contain only a message and type; an intermediary can also return a non-JSON response. ## Status code guide | Status | Meaning | First check | | --- | --- | --- | | 400 | Invalid input or rejected request | JSON, parameters, context, content-policy message | | 401 | Missing or invalid credentials | Bearer header and key validity | | 402 | Insufficient credits | Account and key balances | | 403 | Access denied | Account/model permissions | | 404 | Unknown path, model or resource | Base URL, route, exact model ID | | 429 | Rate, concurrency or capacity limit | Retry-After and active request count | | 499 in logs | Client-side cancellation | Client deadline, disconnect, Stop action | | 500 | Internal service failure | Capture safe diagnostics; bounded retry if appropriate | | 501 | Endpoint not implemented | Use chat completions instead of legacy completions | | 502 | Routing or upstream failure | Service availability and error timing | | 503 | Temporarily unavailable | Capacity and bounded backoff | | 504 | Timeout | Application deadlines and service availability | ## A 404 with an otherwise valid key Use `https://api.simplellm.eu/v1` as the OpenAI-compatible SDK base URL. Do not append another `/v1` or point inference requests to the dashboard host. Use `chat.completions.create`, not a client's Responses API or legacy text-completion method. Check the [compatibility page](/docs/compatibility.html) before assuming another OpenAI endpoint exists. ## Empty answer or no visible output Inspect `choices`, the finish reason, optional reasoning fields, and token usage. A small output budget can be consumed by reasoning. A tool call may intentionally have no ordinary text answer until your application returns a tool result. For streaming, keep reading through role-only and usage-only events. Do not fail merely because one chunk has no text or no choices. ## Invalid JSON while reading audio Speech returns audio bytes on success. Transcription can return text formats depending on the requested output. Check Content-Type before calling a JSON parser, and use multipart form data for file uploads. ## A stream fails after HTTP 200 The status was sent before generation finished. Catch read errors, inspect any stream error events, and mark the response incomplete if it ends without a valid completion. Do not discard delivered output automatically or charge a new request to the user through an invisible retry. See [stream handling](/docs/streaming.html). ## Safe diagnostics for support Record the timestamp with timezone, endpoint, model ID, HTTP status, time to first output, total elapsed time, and whether any output arrived. Include a request identifier if the response supplies one. Remove Authorization headers, API keys, personal data, and sensitive prompts or images before sharing diagnostics. Reproduce with a minimal non-sensitive request where possible. Security findings belong at [security@stageone.solutions](mailto:security@stageone.solutions), following the [reporting policy](/docs/security.html); do not submit automated bulk reports. --- # Usage and API keys Use read-only account and key endpoints to show spending and limits in your own integration. Authentication is the same Bearer API key used for inference. ## Account usage ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/usage \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ The response contains: | Field | Scope | | --- | --- | | `usage` | Daily per-model entries for the last 30 days | | `balance` | Current account balance in SC | | `total_spent` | Lifetime recorded usage cost in SC | Daily entries contain `date`, `model`, `requests`, `tokens_in`, `tokens_out`, `tokens_cached`, and `cost_sc`, plus the account ID. Their date range differs from the lifetime total. This V1 route does not accept `from`, `to`, `days`, or `granularity` filters. Those names must not be inferred from dashboard analytics routes. ## Current key metadata ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ Metadata includes the key ID, prefix, name, nullable rate and concurrency overrides, balance settings, and creation/last-use/revocation timestamps. It does not return the key secret. Null overrides are not a promise of unlimited throughput. Account, model, and upstream limits can still apply. ## Key usage ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current/usage \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current/usage/daily \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ The aggregate returns `total_requests`, `total_tokens_in`, `total_tokens_out`, and `total_cost_sc`. The daily endpoint returns `{"usage": [...]}` for the last 30 days, with a date and those totals for each entry. These key summaries do not expose a separate cache counter. Do not manufacture one by subtracting unrelated totals; use the available account/model usage or final inference usage where appropriate. ## Other keys in the account `GET /v1/keys/` returns `{"api_keys": [...]}`. The trailing slash is part of the listed route. To read another owned key's usage, use its UUID with `/v1/keys/{keyID}/usage` or `/v1/keys/{keyID}/usage/daily`. The server checks ownership; the key ID is not the API key secret. ## Operational guidance Create, configure, rotate and revoke keys through the dashboard. The public reference covers only the existing read-only key routes, not administrator or dashboard-session mutations. Usage recording can finish after a response completes. Avoid treating a single immediate poll as proof that a request was free, and do not poll every stream chunk. For cost calculations, distinguish provider prompt totals from separately shown cache counts as explained in [Tokens, cache and billing](/docs/billing.html). --- # Python and TypeScript clients OpenAI-compatible SDKs can connect to SimpleLLM by setting the base URL and API key. The examples below use Chat Completions, not the Responses API. ## Install and configure ~~~bash # Python, preferably inside a virtual environment: python -m pip install openai # JavaScript / TypeScript, inside your application: npm install openai ~~~ ~~~bash export SIMPLELLM_API_KEY='YOUR_API_KEY' export SIMPLELLM_MODEL='qwen3.8-flash-next' ~~~ The model above was present in the public catalogue on 2026-09-14. Check [Models & pricing](/docs/models.html) before running the example; paid-model calls consume credits. Use these examples on your backend or in a terminal, not in a publicly delivered browser bundle. ## Non-streaming response Save the Python version as `chat.py` and run `python chat.py`. Save the JavaScript version as `chat.mjs` and run `node chat.mjs`; the same client calls work inside a TypeScript project. ~~~python import os from openai import OpenAI, APIStatusError client = OpenAI( base_url="https://api.simplellm.eu/v1", api_key=os.environ["SIMPLELLM_API_KEY"], max_retries=0, timeout=600.0, ) try: response = client.chat.completions.create( model=os.environ["SIMPLELLM_MODEL"], messages=[{"role": "user", "content": "Explain a hash table briefly."}], max_tokens=2048, ) print(response.choices[0].message.content or "") print("Usage:", response.usage) except APIStatusError as error: print("Request failed with HTTP", error.status_code) raise finally: client.close() ~~~ ~~~javascript import OpenAI from "openai"; if (!process.env.SIMPLELLM_API_KEY || !process.env.SIMPLELLM_MODEL) { throw new Error("Set SIMPLELLM_API_KEY and SIMPLELLM_MODEL"); } const client = new OpenAI({ baseURL: "https://api.simplellm.eu/v1", apiKey: process.env.SIMPLELLM_API_KEY, maxRetries: 0, timeout: 600_000, }); try { const response = await client.chat.completions.create({ model: process.env.SIMPLELLM_MODEL, messages: [{ role: "user", content: "Explain a hash table briefly." }], max_tokens: 2048, }); console.log(response.choices[0]?.message.content ?? ""); console.log("Usage:", response.usage); } catch (error) { if (error instanceof OpenAI.APIError) { console.error("Request failed with HTTP", error.status); } throw error; } ~~~ The example disables automatic SDK retries so one invocation does not silently become several inference attempts. Choose your application's retry policy using the [retry guide](/docs/limits.html). ## Stream with JavaScript This is a complete Node.js example. Ctrl+C aborts the stream; an application can connect its Stop button to the same abort handler. ~~~javascript import OpenAI from "openai"; if (!process.env.SIMPLELLM_API_KEY || !process.env.SIMPLELLM_MODEL) { throw new Error("Set SIMPLELLM_API_KEY and SIMPLELLM_MODEL"); } const client = new OpenAI({ baseURL: "https://api.simplellm.eu/v1", apiKey: process.env.SIMPLELLM_API_KEY, maxRetries: 0, timeout: 600_000, }); const stream = await client.chat.completions.create({ model: process.env.SIMPLELLM_MODEL, messages: [{ role: "user", content: "Explain streaming responses." }], max_tokens: 2048, stream: true, stream_options: { include_usage: true }, }); const stop = () => stream.controller.abort(); process.once("SIGINT", stop); try { for await (const chunk of stream) { for (const choice of chunk.choices) { process.stdout.write(choice.delta.content ?? ""); } if (chunk.usage) console.log("\nUsage:", chunk.usage); } } finally { process.removeListener("SIGINT", stop); stream.controller.abort(); } ~~~ For Python streaming with cleanup, see the complete example in [Streaming & cancellation](/docs/streaming.html). ## Common migration mistakes - Python uses `base_url`; JavaScript uses `baseURL`. - Include `/v1` once in the SDK base URL. - Use a SimpleLLM model ID, not an ID copied from another provider. - Do not enable browser-side secret exposure to work around an SDK warning. - SDK types may contain endpoints or options that SimpleLLM V1 does not implement. See [Compatibility](/docs/compatibility.html) for supported endpoints and [Go & Rust](/docs/go-rust.html) for direct HTTP clients. --- # Go and Rust Any HTTP client can call SimpleLLM. These complete examples avoid coupling your application to a third-party OpenAI SDK version and use the same JSON request as cURL. Set `SIMPLELLM_API_KEY` and `SIMPLELLM_MODEL` first, as shown in the [Quickstart](/docs/quickstart.html). Both examples make one non-streaming request with an application-selected ten-minute deadline. ## Go Save this as `main.go` and run `go run main.go`. It uses only the standard library, checks HTTP status, handles a missing choice, and propagates Ctrl+C through the request context. ~~~go package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "os/signal" "time" ) func run() error { key, model := os.Getenv("SIMPLELLM_API_KEY"), os.Getenv("SIMPLELLM_MODEL") if key == "" || model == "" { return fmt.Errorf("set SIMPLELLM_API_KEY and SIMPLELLM_MODEL") } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() payload, err := json.Marshal(map[string]any{ "model": model, "messages": []map[string]string{ {"role": "user", "content": "Explain a hash table briefly."}, }, "max_tokens": 2048, }) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.simplellm.eu/v1/chat/completions", bytes.NewReader(payload)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+key) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 10 * time.Minute} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("inference HTTP %d", resp.StatusCode) } var result struct { Choices []struct { Message struct { Content *string } } Usage json.RawMessage } if err := json.Unmarshal(body, &result); err != nil { return err } if len(result.Choices) == 0 { return fmt.Errorf("response has no choices") } if text := result.Choices[0].Message.Content; text != nil { fmt.Println(*text) } fmt.Println("Usage:", string(result.Usage)) return nil } func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } ~~~ ## Rust Create a binary project and add the dependencies: ~~~bash cargo new simplellm-example cd simplellm-example cargo add reqwest --features json cargo add tokio --features macros,rt-multi-thread,signal cargo add serde_json ~~~ Replace `src/main.rs` with this example, then run `cargo run`. ~~~rust use serde_json::{json, Value}; use std::{env, error::Error, time::Duration}; #[tokio::main] async fn main() -> Result<(), Box> { let key = env::var("SIMPLELLM_API_KEY")?; let model = env::var("SIMPLELLM_MODEL")?; let client = reqwest::Client::builder() .timeout(Duration::from_secs(600)) .build()?; let request = client .post("https://api.simplellm.eu/v1/chat/completions") .bearer_auth(key) .json(&json!({ "model": model, "messages": [{"role": "user", "content": "Explain a hash table briefly."}], "max_tokens": 2048 })); let work = async { let response = request.send().await?.error_for_status()?; response.json::().await }; let result = tokio::select! { result = work => result?, signal = tokio::signal::ctrl_c() => { signal?; eprintln!("Cancelled"); return Ok(()); } }; let choice = result["choices"].get(0).ok_or("response has no choices")?; println!("{}", choice["message"]["content"].as_str().unwrap_or("")); println!("Usage: {}", result["usage"]); Ok(()) } ~~~ Dropping the pending HTTP work on Ctrl+C cancels the client operation; already performed inference can still incur a charge. ## Adding streaming For Go, consume the response body incrementally; for Rust, use the HTTP client's incremental body interface. A network read is not an SSE event and not a token. If implementing your own parser, buffer incomplete lines, handle blank-line event boundaries, join data lines, and recognize `[DONE]`. Go's default Scanner token limit can be too small for large events, so choose an explicit bounded buffer or a reader-based parser. See [Streaming](/docs/streaming.html) for the wire format, usage-only events, and interruption behavior. --- # OpenCode The SimpleLLM provider setup configures OpenCode with the model catalogue and your API key. You need Node.js 20 or newer for the setup package. ## Run the setup wizard ~~~bash npx @simplellm/opencode-provider ~~~ The wizard fetches model metadata, asks for a SimpleLLM API key, validates it, and writes the provider configuration. | File | Purpose | | --- | --- | | `~/.config/opencode/opencode.json` | OpenCode provider and model configuration | | `~/.local/share/opencode/auth.json` | Stored API credential | The wizard merges its provider configuration into the existing file. Back up a hand-edited configuration before running setup, and review the result if your installation uses custom paths or settings. ## Start a session Choose an ID from the discovered model list. This example uses an ID present in the public catalogue on 2026-09-14: ~~~bash opencode --model simplellm/Qwen3-Coder-30B-A3B-Instruct ~~~ The `simplellm/` prefix is OpenCode's provider selector; it is not part of the model ID in a direct HTTP chat request. ## Refresh models Re-run the setup command after catalogue changes. The configuration is generated from the catalogue at setup time; it does not become permanently current just because the wizard was run once. The package also includes a plugin that can show the remaining SC balance when a session starts. Check the [provider README](https://github.com/SimpleLLM/opencode-provider) for plugin integration details. ## Configuration variables | Variable | Use | | --- | --- | | `SIMPLELLM_BASE_URL` | Provider setup base; defaults to https://api.simplellm.eu, without /v1 | | `SIMPLELLM_API_KEY` | Key read by the plugin when set, overriding its stored key | The setup base URL is different from the OpenAI SDK's `/v1` base URL because the provider also calls account and catalogue routes. ## Troubleshooting If a model is missing, refresh the provider configuration and compare its ID with the public catalogue. If authentication fails, confirm the stored key and any overriding environment variable. Long coding sessions resend context and can generate many tokens. Review [cache billing](/docs/billing.html) and [rate limits](/docs/limits.html), and make sure cancelling in the client actually closes its inference request. Do not include the auth file or its contents in a support report. --- # OpenClaw The SimpleLLM OpenClaw setup writes provider configuration, model metadata, and an API-key environment file. The setup package requires Node.js 20 or newer. ## Interactive setup ~~~bash npx @simplellm/openclaw-provider ~~~ The wizard fetches the model catalogue, asks for your key, and configures the provider. | Default file | Purpose | | --- | --- | | `~/.openclaw/openclaw.json` | Provider and model configuration | | `~/.openclaw/.env` | SIMPLELLM_API_KEY | The setup merges configuration rather than replacing the entire file. Back up custom configuration before changing it, and keep the environment file private. ## Start OpenClaw ~~~bash openclaw ~~~ The setup package's documented default is `simplellm/Qwen3-Coder-30B-A3B-Instruct`. Check your generated configuration and current catalogue rather than assuming the default model will always remain available. ## Environment-based setup Provide the key through an environment variable instead of placing it in a command-line argument: ~~~bash export SIMPLELLM_API_KEY='YOUR_API_KEY' npx @simplellm/openclaw-provider ~~~ Command-line secrets can appear in shell history or process listings. Use your environment or secret tooling for automated setup. ## Configuration variables | Variable | Default / meaning | | --- | --- | | `SIMPLELLM_API_KEY` | Supply the key non-interactively | | `SIMPLELLM_BASE_URL` | https://api.simplellm.eu, without /v1 | | `OPENCLAW_STATE_DIR` | ~/.openclaw; controls configuration and environment-file location | ## Refresh and diagnose Re-run the wizard to pick up model changes. If you use a custom state directory, confirm that OpenClaw and the setup command use the same one. Check key validity, selected model, and account credits when setup succeeds but inference fails. See [Troubleshooting](/docs/errors.html) for HTTP errors and the [provider README](https://github.com/SimpleLLM/openclaw-provider) for package details. Protect both configuration files and avoid sharing a diagnostic archive containing the API key. --- # Zed Connect Zed's Agent to SimpleLLM through a custom OpenAI-compatible provider. You need a SimpleLLM API key and account credits; create a key in the [dashboard](https://app.simplellm.eu/api-keys). ## Add the provider Open your user `settings.json` in Zed and merge this configuration into your existing settings. If `language_models` or `openai_compatible` already exists, add the `simplellm` entry inside it instead of creating a duplicate key. ~~~json { "language_models": { "openai_compatible": { "simplellm": { "api_url": "https://api.simplellm.eu/v1", "available_models": [ { "name": "DeepSeek-V4-Flash", "display_name": "DeepSeek V4 Flash (SimpleLLM)", "max_tokens": 1048576, "max_output_tokens": 8192, "capabilities": { "tools": true, "images": false, "chat_completions": true, "interleaved_reasoning": true, "max_tokens_parameter": true } } ] } } } } ~~~ | Setting | Meaning | | --- | --- | | `api_url` | SimpleLLM's API base, including `/v1`. Do not append `/chat/completions`. | | `name` | Exact, case-sensitive SimpleLLM model ID. Do not use an upstream provider's model name. | | `max_tokens` | Zed's model context budget, including input and output. | | `max_output_tokens` | Requested output budget; 8192 is a starting value, not the model's advertised maximum. | | `chat_completions` | Uses the supported chat-completions API; keep this enabled. | | `max_tokens_parameter` | Sends the output budget as `max_tokens`. | | `interleaved_reasoning` | Preserves streamed reasoning in the `reasoning_content` field for subsequent turns. | ## Add your API key and select the model Run `agent: open settings` from Zed's command palette. Under **LLM Providers**, find `simplellm` and enter your SimpleLLM API key. Then open the Agent panel and select **DeepSeek V4 Flash (SimpleLLM)** in the model picker. Keep the key out of `settings.json`. Zed stores keys entered through its provider settings in the system keychain. As an alternative, the provider ID `simplellm` uses the `SIMPLELLM_API_KEY` environment variable; it must be available to the Zed process when launched. ## Change models or output limits Use [Models & pricing](/docs/models.html) to check the current model ID, availability and context window. Update `name`, `display_name` and the context budget together when choosing another model. Tool, image and reasoning capabilities must match that model; these flags do not add capabilities to it. Increase the output budget if a response reaches its limit before completing the answer. Reasoning also consumes output tokens. Longer outputs may take more than 60 seconds and cost more SC. Zed's OpenAI-compatible Agent integration uses streaming; see [streaming and cancellation](/docs/streaming.html) and [billing](/docs/billing.html). ## Troubleshooting - **Provider missing:** check the settings nesting and update Zed if it does not recognize `openai_compatible`. - **401:** check the API key saved for `simplellm`. - **402:** check the account balance and API-key spending limit. - **400:** check the model ID, context size and model-specific options before retrying. See [Troubleshooting](/docs/errors.html). Configuration fields were checked against [Zed's provider documentation](https://zed.dev/docs/ai/use-api-access#openai-compatible-endpoints) and [OpenAI-compatible integration source](https://github.com/zed-industries/zed/blob/main/crates/language_models/src/provider/open_ai_compatible.rs). This sample has not been tested end to end in the Zed app. --- # Compatibility and AI-readable docs SimpleLLM V1 implements an OpenAI-compatible inference surface. Compatibility describes specific endpoints and request formats, not every feature exposed by every OpenAI client. ## Supported public routes | Operation | Endpoint | | --- | --- | | Chat, streaming, model-dependent vision and tools | POST /v1/chat/completions | | Model IDs | GET /v1/models | | Speech generation | POST /v1/audio/speech | | File transcription | POST /v1/audio/transcriptions | | Image generation | POST /v1/images/generations | | Rate-limit status | GET /v1/rate-limit | | Account usage | GET /v1/usage | | Read-only key metadata and usage | GET /v1/keys/ and documented subroutes | Audio and image availability depends on the separately configured services. Listing chat models does not establish media-endpoint availability. ## What is not included `POST /v1/completions` is a 501 stub in V1; use Chat Completions. Responses, embeddings, batch jobs, direct OCR, voice cloning, voice design, and a voice-catalogue endpoint are not part of this documented V1 router. An Anthropic-format adapter exists, but it is not an officially supported interface in this reference. Do not configure Claude/Anthropic-specific clients against the OpenAI endpoint based solely on the compatibility label. Direct GPU-host features and future V3 features are separate from the live public V1 contract. There is no documented V1 reservation or guaranteed priority-lane request option. ## Model-specific parameters V1 forwards original chat request fields while using a narrower view for validation, routing, and billing. Fields such as tools, reasoning controls, and response_format still depend on the selected model and upstream service. Use `stop` as an array of strings in V1. Unlike some other compatible APIs, a plain string is not accepted by its typed request parser. The image-generation handler accepts a size field but does not forward it to its configured backend. Do not rely on it to select output dimensions. ## Give these docs to a coding assistant Start with [llms.txt](/llms.txt), which lists the individual Markdown guides and reference pages. Load only the relevant pages for a task, or use [llms-full.txt](/llms-full.txt) when the full text is needed. [OpenAPI JSON](/openapi.json) contains the request and response schemas. Every reference page is generated from that same contract so field tables and schema downloads share a source. ## Static HTML and Markdown Every page's main content and navigation are present in the HTML response; no React application or JavaScript execution is needed to read them. Markdown versions are linked from each page and use the same content source. Optional local JavaScript adds search, copy controls, code-language tabs, and theme selection. It does not gate documentation access behind a login or fetch the main text after page load. ## Security contact Report vulnerabilities privately to [security@stageone.solutions](mailto:security@stageone.solutions). The [security policy](/docs/security.html) and [security.txt](/.well-known/security.txt) describe the reporting channel and testing boundaries. --- # List model IDs `GET /v1/models` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/models \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `object` | `"list"` | No | | | `data` | `array` | Yes | | | `data[].id` | `string` | No | | | `data[].object` | `string` | No | | | `data[].created` | `integer` | No | | | `data[].owned_by` | `string` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "object": "list", "data": [ { "id": "qwen3.8-flash-next", "object": "model", "created": 1789380000, "owned_by": "simplellm" } ] } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `listModels`. --- # Create a chat completion `POST /v1/chat/completions` Model-specific vision and tool support. stream=true returns SSE; usage-only events may have empty choices. Client cancellation closes the upstream connection. Cancelled streams with zero recorded output tokens are not charged; partial usage after output may be estimated and billed. No automatic retry after response starts. Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Request body Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `model` | `string` | Yes | Exact chat model ID from GET /v1/models. | | `messages` | `array` | Yes | Minimum items: 1. | | `messages[].role` | `string` | Yes | Values: `system`, `developer`, `user`, `assistant`, `tool`. | | `messages[].content` | `string / null / array` | No | | | `messages[].content[].type` | `string` | Yes | | | `messages[].content[].text` | `string` | No | | | `messages[].content[].image_url` | `object` | No | | | `messages[].content[].image_url.url` | `string` | Yes | | | `messages[].content[].image_url.detail` | `string` | No | Values: `auto`, `low`, `high`. | | `messages[].name` | `string` | No | | | `messages[].tool_call_id` | `string` | No | | | `messages[].tool_calls` | `array` | No | | | `messages[].tool_calls[].id` | `string` | Yes | | | `messages[].tool_calls[].type` | `"function"` | Yes | | | `messages[].tool_calls[].function` | `object` | Yes | | | `messages[].tool_calls[].function.name` | `string` | Yes | | | `messages[].tool_calls[].function.arguments` | `string` | Yes | JSON-encoded arguments; parse and validate before executing. | | `messages[].reasoning_content` | `string / null` | No | Optional provider-specific reasoning; not guaranteed. | | `messages[].reasoning` | `string / null` | No | Alternative provider-specific reasoning field; not guaranteed. | | `stream` | `boolean` | No | Default: `false`. | | `max_tokens` | `integer` | No | Output budget; reasoning can consume this budget. Minimum: 1. | | `temperature` | `number` | No | | | `top_p` | `number` | No | | | `stream_options` | `object` | No | | | `stream_options.include_usage` | `boolean` | No | | | `stop` | `array` | No | V1 accepts an array of strings, not a plain string. | | `n` | `integer` | No | Forwarded; multiple choices are model-dependent. | | `presence_penalty` | `number` | No | Model-dependent sampling parameter. | | `frequency_penalty` | `number` | No | Model-dependent sampling parameter. | | `tools` | `array` | No | | | `tools[].type` | `"function"` | Yes | | | `tools[].function` | `object` | Yes | | | `tools[].function.name` | `string` | Yes | | | `tools[].function.description` | `string` | No | | | `tools[].function.parameters` | `object` | No | | | `tools[].function.strict` | `boolean` | No | Only where the selected model/backend supports strict schemas. | | `tool_choice` | `string / object` | No | | | `response_format` | `object` | No | Forwarded, model-dependent output-format request; no universal JSON schema guarantee. | Nested required fields are required only when their containing object is supplied. Extra chat parameters may be provider-specific; acceptance does not guarantee model support. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/chat/completions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-flash-next", "messages": [ { "role": "user", "content": "Explain what an API is in one sentence." } ], "max_tokens": 1024 }' ~~~ ## Responses ### HTTP 200 JSON response or incremental SSE stream ending with data: [DONE]. Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `id` | `string` | No | | | `object` | `string` | No | | | `created` | `integer` | No | | | `model` | `string` | No | | | `choices` | `array` | No | | | `choices[].index` | `integer` | No | | | `choices[].message` | `Message` | No | | | `choices[].message.role` | `string` | Yes | Values: `system`, `developer`, `user`, `assistant`, `tool`. | | `choices[].message.content` | `string / null / array` | No | | | `choices[].message.content[].type` | `string` | Yes | | | `choices[].message.content[].text` | `string` | No | | | `choices[].message.content[].image_url` | `object` | No | | | `choices[].message.content[].image_url.url` | `string` | Yes | | | `choices[].message.content[].image_url.detail` | `string` | No | Values: `auto`, `low`, `high`. | | `choices[].message.name` | `string` | No | | | `choices[].message.tool_call_id` | `string` | No | | | `choices[].message.tool_calls` | `array` | No | | | `choices[].message.tool_calls[].id` | `string` | Yes | | | `choices[].message.tool_calls[].type` | `"function"` | Yes | | | `choices[].message.tool_calls[].function` | `object` | Yes | | | `choices[].message.tool_calls[].function.name` | `string` | Yes | | | `choices[].message.tool_calls[].function.arguments` | `string` | Yes | JSON-encoded arguments; parse and validate before executing. | | `choices[].message.reasoning_content` | `string / null` | No | Optional provider-specific reasoning; not guaranteed. | | `choices[].message.reasoning` | `string / null` | No | Alternative provider-specific reasoning field; not guaranteed. | | `choices[].finish_reason` | `string / null` | No | | | `usage` | `Usage` | No | | | `usage.prompt_tokens` | `integer` | No | | | `usage.completion_tokens` | `integer` | No | | | `usage.total_tokens` | `integer` | No | | | `usage.prompt_tokens_details` | `object` | No | | | `usage.prompt_tokens_details.cached_tokens` | `integer` | No | Subset of prompt_tokens, not additional input. | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "id": "chatcmpl-example", "object": "chat.completion", "created": 1789380000, "model": "qwen3.8-flash-next", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "An API lets software communicate." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 20, "completion_tokens": 10, "total_tokens": 30, "prompt_tokens_details": { "cached_tokens": 0 } } } ~~~ Content-Type: `text/event-stream` Response type: `string`. ~~~text data: {"choices":[{"delta":{"content":"Hello"}}]} data: [DONE] ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `createChatCompletion`. --- # Not implemented in V1 `POST /v1/completions` Use POST /v1/chat/completions instead. Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. > This endpoint is not implemented. Use [Chat completions](/docs/reference/createChatCompletion.html). ## Responses ### HTTP 501 Not implemented Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `error` | `object` | Yes | | | `error.message` | `string` | No | | | `error.type` | `string` | No | | | `error.code` | `string` | No | | ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 501 | Not implemented | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `legacyCompletions`. --- # Generate speech audio `POST /v1/audio/speech` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Request body Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `model` | `string` | No | Default: `"tts-1"`. | | `input` | `string` | Yes | At most 4096 UTF-8 bytes (not Unicode characters). | | `voice` | `string` | No | Default: `"alloy"`. Values: `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`. | | `response_format` | `string` | No | Default: `"mp3"`. Values: `mp3`, `opus`, `aac`, `flac`, `wav`, `pcm`. | | `speed` | `number` | No | Default: `1`. Minimum: 0.25. Maximum: 4. | Nested required fields are required only when their containing object is supplied. Extra chat parameters may be provider-specific; acceptance does not guarantee model support. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/audio/speech \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "tts-1", "input": "Hello from SimpleLLM.", "voice": "alloy", "response_format": "mp3" }' \ --output speech.mp3 ~~~ ## Responses ### HTTP 200 Audio bytes in the requested format. Content-Type: `audio/mpeg` Response type: `string`. Content-Type: `audio/opus` Response type: `string`. Content-Type: `audio/aac` Response type: `string`. Content-Type: `audio/flac` Response type: `string`. Content-Type: `audio/wav` Response type: `string`. Content-Type: `audio/pcm` Response type: `string`. ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `createSpeech`. --- # Transcribe an uploaded audio file `POST /v1/audio/transcriptions` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Request body Content-Type: `multipart/form-data` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `file` | `string` | Yes | | | `model` | `string` | No | Use whisper-large-v3 or whisper-large-v3-turbo. The compatibility alias whisper-1 maps to whisper-large-v3-turbo and is also the default; it is not a separate model. Default: `"whisper-1"`. Values: `whisper-1`, `whisper-large-v3`, `whisper-large-v3-turbo`. | | `language` | `string` | No | | | `prompt` | `string` | No | | | `response_format` | `string` | No | | | `temperature` | `number` | No | | Nested required fields are required only when their containing object is supplied. Extra chat parameters may be provider-specific; acceptance does not guarantee model support. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/audio/transcriptions \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -F 'file=@recording.wav' \ -F 'model=whisper-large-v3-turbo' \ -F 'language=en' ~~~ ## Responses ### HTTP 200 Transcription in the requested upstream-supported format. Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `text` | `string` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "text": "example" } ~~~ Content-Type: `text/plain` Response type: `string`. Content-Type: `text/vtt` Response type: `string`. ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `createTranscription`. --- # Generate images `POST /v1/images/generations` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Request body Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `prompt` | `string` | Yes | | | `model` | `string` | No | V1 uses the configured image backend; this field does not select a different model. | | `n` | `integer` | No | Default: `1`. Minimum: 1. Maximum: 4. | | `size` | `string` | No | Accepted locally but not forwarded by V1; does not control output dimensions. Default: `"1024x1024"`. | | `response_format` | `string` | No | | Nested required fields are required only when their containing object is supplied. Extra chat parameters may be provider-specific; acceptance does not guarantee model support. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/images/generations \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "prompt": "A small lighthouse at dawn, watercolor illustration", "n": 1, "size": "1024x1024" }' ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `created` | `integer` | No | | | `data` | `array` | No | | | `data[].url` | `string` | No | | | `data[].b64_json` | `string` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "created": 1789380000, "data": [ { "url": "https://api.simplellm.eu/images/EXAMPLE_IMAGE_ID" } ] } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `createImage`. --- # Read rate-limit status `GET /v1/rate-limit` General minute request and free-model hourly/daily counters. Does not enumerate all key, model or upstream limits. Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/rate-limit \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `limit_rpm` | `integer` | No | General minute request bucket. | | `used_rpm` | `integer` | No | General minute request bucket. | | `remaining_rpm` | `integer` | No | General minute request bucket. | | `limit_rph` | `integer` | No | Free-model rph bucket. | | `used_rph` | `integer` | No | Free-model rph bucket. | | `remaining_rph` | `integer` | No | Free-model rph bucket. | | `limit_rpd` | `integer` | No | Free-model rpd bucket. | | `used_rpd` | `integer` | No | Free-model rpd bucket. | | `remaining_rpd` | `integer` | No | Free-model rpd bucket. | | `limit_tph` | `integer` | No | Free-model tph bucket. | | `used_tph` | `integer` | No | Free-model tph bucket. | | `remaining_tph` | `integer` | No | Free-model tph bucket. | | `limit_tpd` | `integer` | No | Free-model tpd bucket. | | `used_tpd` | `integer` | No | Free-model tpd bucket. | | `remaining_tpd` | `integer` | No | Free-model tpd bucket. | | `reset_seconds` | `integer` | No | Minute-bucket reset only. | | `banned` | `boolean` | No | | | `banned_until` | `string` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "limit_rpm": 0, "used_rpm": 0, "remaining_rpm": 0, "limit_rph": 0, "used_rph": 0, "remaining_rph": 0, "limit_rpd": 0, "used_rpd": 0, "remaining_rpd": 0, "limit_tph": 0, "used_tph": 0, "remaining_tph": 0, "limit_tpd": 0, "used_tpd": 0, "remaining_tpd": 0, "reset_seconds": 0, "banned": false, "banned_until": "example" } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getRateLimit`. --- # Read account usage and balance `GET /v1/usage` Daily usage for the last 30 days, current balance and lifetime total_spent in SC. This route has no date or granularity query filters. Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/usage \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `usage` | `array` | No | | | `usage[].user_id` | `string` | No | | | `usage[].date` | `string` | No | | | `usage[].model` | `string` | No | | | `usage[].requests` | `integer` | No | | | `usage[].tokens_in` | `integer` | No | | | `usage[].tokens_out` | `integer` | No | | | `usage[].tokens_cached` | `integer` | No | | | `usage[].cost_sc` | `number` | No | | | `balance` | `number` | No | | | `total_spent` | `number` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "usage": [ { "user_id": "00000000-0000-4000-8000-000000000000", "date": "2026-09-14T00:00:00Z", "model": "qwen3.8-flash-next", "requests": 0, "tokens_in": 0, "tokens_out": 0, "tokens_cached": 0, "cost_sc": 0 } ], "balance": 0, "total_spent": 0 } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getUsage`. --- # List account API keys `GET /v1/keys/` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/ \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `api_keys` | `array` | No | | | `api_keys[].id` | `string` | No | | | `api_keys[].user_id` | `string` | No | | | `api_keys[].key_prefix` | `string` | No | Public prefix only, never the key secret. | | `api_keys[].name` | `string / null` | No | | | `api_keys[].rate_limit_rpm` | `integer / null` | No | | | `api_keys[].rate_limit_tpm` | `integer / null` | No | | | `api_keys[].max_concurrent` | `integer / null` | No | | | `api_keys[].balance_enabled` | `boolean` | No | | | `api_keys[].balance_sc` | `number` | No | | | `api_keys[].balance_limit_sc` | `number` | No | | | `api_keys[].created_at` | `string` | No | | | `api_keys[].last_used_at` | `string / null` | No | | | `api_keys[].revoked_at` | `string / null` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "api_keys": [ { "id": "00000000-0000-4000-8000-000000000000", "user_id": "00000000-0000-4000-8000-000000000000", "key_prefix": "sk-example", "name": null, "rate_limit_rpm": null, "rate_limit_tpm": null, "max_concurrent": null, "balance_enabled": false, "balance_sc": 0, "balance_limit_sc": 0, "created_at": "2026-09-14T00:00:00Z", "last_used_at": null, "revoked_at": null } ] } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `listKeys`. --- # Read current key metadata `GET /v1/keys/current` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `id` | `string` | No | | | `user_id` | `string` | No | | | `key_prefix` | `string` | No | Public prefix only, never the key secret. | | `name` | `string / null` | No | | | `rate_limit_rpm` | `integer / null` | No | | | `rate_limit_tpm` | `integer / null` | No | | | `max_concurrent` | `integer / null` | No | | | `balance_enabled` | `boolean` | No | | | `balance_sc` | `number` | No | | | `balance_limit_sc` | `number` | No | | | `created_at` | `string` | No | | | `last_used_at` | `string / null` | No | | | `revoked_at` | `string / null` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "id": "00000000-0000-4000-8000-000000000000", "user_id": "00000000-0000-4000-8000-000000000000", "key_prefix": "sk-example", "name": null, "rate_limit_rpm": null, "rate_limit_tpm": null, "max_concurrent": null, "balance_enabled": false, "balance_sc": 0, "balance_limit_sc": 0, "created_at": "2026-09-14T00:00:00Z", "last_used_at": null, "revoked_at": null } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getCurrentKey`. --- # Read current key usage `GET /v1/keys/current/usage` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current/usage \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `total_requests` | `integer` | No | | | `total_tokens_in` | `integer` | No | | | `total_tokens_out` | `integer` | No | | | `total_cost_sc` | `number` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "total_requests": 0, "total_tokens_in": 0, "total_tokens_out": 0, "total_cost_sc": 0 } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getCurrentKeyUsage`. --- # Read current key daily usage `GET /v1/keys/current/usage/daily` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/current/usage/daily \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `usage` | `array` | No | | | `usage[].date` | `string` | No | | | `usage[].total_requests` | `integer` | No | | | `usage[].total_tokens_in` | `integer` | No | | | `usage[].total_tokens_out` | `integer` | No | | | `usage[].total_cost_sc` | `number` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "usage": [ { "date": "2026-09-14", "total_requests": 0, "total_tokens_in": 0, "total_tokens_out": 0, "total_cost_sc": 0 } ] } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getCurrentKeyDailyUsage`. --- # Read usage for an owned key `GET /v1/keys/{keyID}/usage` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Path parameters | Parameter | Type | Required | Meaning | | --- | --- | --- | --- | | `keyID` | `string` | Yes | UUID of an API key belonging to your account. | ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/YOUR_KEY_ID/usage \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `total_requests` | `integer` | No | | | `total_tokens_in` | `integer` | No | | | `total_tokens_out` | `integer` | No | | | `total_cost_sc` | `number` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "total_requests": 0, "total_tokens_in": 0, "total_tokens_out": 0, "total_cost_sc": 0 } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getKeyUsage`. --- # Read daily usage for an owned key `GET /v1/keys/{keyID}/usage/daily` Base URL: `https://api.simplellm.eu`. Authenticate using `Authorization: Bearer $SIMPLELLM_API_KEY`. ## Path parameters | Parameter | Type | Required | Meaning | | --- | --- | --- | --- | | `keyID` | `string` | Yes | UUID of an API key belonging to your account. | ## Example request ~~~bash curl --fail-with-body https://api.simplellm.eu/v1/keys/YOUR_KEY_ID/usage/daily \ -H "Authorization: Bearer $SIMPLELLM_API_KEY" ~~~ ## Responses ### HTTP 200 Successful response Content-Type: `application/json` | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `usage` | `array` | No | | | `usage[].date` | `string` | No | | | `usage[].total_requests` | `integer` | No | | | `usage[].total_tokens_in` | `integer` | No | | | `usage[].total_tokens_out` | `integer` | No | | | `usage[].total_cost_sc` | `number` | No | | Illustrative response shape only; IDs, timestamps and counters below are synthetic, not live limits or customer data. ~~~json { "usage": [ { "date": "2026-09-14", "total_requests": 0, "total_tokens_in": 0, "total_tokens_out": 0, "total_cost_sc": 0 } ] } ~~~ ## Error handling Errors can occur in authentication, billing, routing or the upstream service; not every status applies to every route. | Status | Meaning | | --- | --- | | 400 | Invalid request | | 401 | Invalid credentials | | 402 | Insufficient credits | | 403 | Access denied | | 404 | Model or resource not found | | 429 | Rate or capacity limit | | 500 | Internal error | | 502 | Routing or upstream error | | 503 | Temporarily unavailable | | 504 | Deployment timeout | See [Troubleshooting](/docs/errors.html) for response examples and [Rate limits & retries](/docs/limits.html) for retry rules. ## Machine-readable schema [Download OpenAPI JSON](/openapi.json). Operation ID: `getKeyDailyUsage`.