# flm API guide (for AI agents) flm is a free chat completions API that speaks the OpenAI format. Point any OpenAI SDK or HTTP client at the base URL below. No account and no API key are needed. Base URL: https://flm.stonech.at/v1 ## Authentication None. Either omit the Authorization header, or (for SDKs that require a key) send: Authorization: Bearer flm_anonymous Any other key is rejected with 401. ## Endpoints - POST https://flm.stonech.at/v1/chat/completions: chat completions (streaming, tools, JSON mode) - GET https://flm.stonech.at/v1/usage: your remaining allowance (counts as a request) - GET https://flm.stonech.at/v1/models: lists a single model, "auto" - GET https://flm.stonech.at/limits: current limits as JSON (not counted) ## Model The model is chosen automatically for every request. Any "model" you send is ignored; use "auto". ## Limits (per IP address, live values) - Requests per minute: 60 (rolling 60-second window) - Requests per day: 20,000 (resets 00:00 UTC) - Tokens per minute: unlimited - Tokens per day: 10,000,000 (input + output; resets 00:00 UTC) - Max input tokens per request: 65,536 - Max output tokens per request: 8,192 Every request to /v1 counts toward the request limits, including errors caused by the request (400s). If max_tokens (or max_completion_tokens) is omitted, output is capped at 2,048 tokens and the response carries a "notice" field saying so. Set max_tokens (up to 8,192) for longer output. ## Request: POST https://flm.stonech.at/v1/chat/completions JSON body (OpenAI format): - messages (required): array of { role, content } - role "system" | "user": content is a string or an array of { "type": "text", "text": "..." } - role "assistant": content (string or null) and optional tool_calls - role "tool": tool_call_id and content (the result of a tool call) - Text only: image, audio and file content parts are not supported. - stream: true for server-sent events - max_tokens / max_completion_tokens: output cap (see Limits) - temperature (0-2), top_p (0-1), stop (string or up to 4 strings), presence_penalty (-2..2), frequency_penalty (-2..2), seed - tools, tool_choice ("none" | "auto" | "required" | { "type": "function", "function": { "name": "..." } }), parallel_tool_calls - response_format: { "type": "json_object" } or { "type": "json_schema", "json_schema": { "name": "...", "schema": { ... } } } Requests with temperature 0 or no temperature may be answered from a short-lived cache of identical requests. With a temperature above 0, a request repeated verbatim within a minute may get one of the last few answers to it; change anything in the request (e.g. the seed) for a fresh sample. ## Response A standard chat.completion object (object, created, choices, usage) plus: - rate_limit: your remaining allowance after this request - notice: present only when a default was applied (see Limits) Example: { "object": "chat.completion", "created": 1760054400, "choices": [{ "index": 0, "finish_reason": "stop", "message": { "role": "assistant", "content": "Hi there!" } }], "usage": { "prompt_tokens": 9, "completion_tokens": 4, "total_tokens": 13 }, "rate_limit": { "scope": "ip", "requests": { "minute": { "limit": 60, "remaining": 59, "reset_seconds": 54 }, "day": { "limit": 20000, "remaining": 19999, "reset_seconds": 40210 } }, "tokens": { "day": { "limit": 10000000, "remaining": 9999987, "reset_seconds": 40210 } }, "max_input_tokens": 65536, "max_output_tokens": 8192 } } Windows without a limit are omitted from rate_limit. A message may also carry a "reasoning" field with the model's visible reasoning; use "content" for the answer. Headers (tightest window of each kind): x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests, x-ratelimit-limit-tokens, x-ratelimit-remaining-tokens, x-ratelimit-reset-tokens (resets look like "54s" or "11h10m10s"). ## Streaming With "stream": true the response is text/event-stream: "data: {chat.completion.chunk}" lines, then "data: [DONE]". Read choices[0].delta.content (and delta.tool_calls). The last chunk before [DONE] carries usage, rate_limit and, when applicable, notice. If something fails mid-stream, a chunk with an "error" object is sent before [DONE]. ## Long requests Long answers don't time out. If a response isn't ready after about 15 seconds, it starts anyway and is kept alive until the answer is done: - JSON responses begin with spaces (one every 15s), then the JSON body. Leading whitespace is valid JSON; parse the whole body as usual. - Streams send SSE comment lines (": keepalive") until the first chunk. SSE clients ignore them. From that point the HTTP status is already 200, so a failure arrives in the body: a JSON body with an "error" object (and rate_limit) instead of choices, or a "data: {"error": …}" event followed by the limits chunk and [DONE] in a stream. Always check for "error" before reading choices. Generation limits: the first token within 30s plus about 1s per 1,000 input tokens, no gap longer than 60s, and 5 minutes for the whole request, retries included. Non-streaming requests that fail mid-generation are retried on other capacity automatically; a stream that already sent text ends with an error event instead. Prefer "stream": true for long outputs (you see progress and can stop early). On a committed response the x-ratelimit-* headers are a snapshot from when it started; the rate_limit object in the body is current. ## Tool calling Send tools in the OpenAI format. When the model wants a tool, finish_reason is "tool_calls" and message.tool_calls holds { id, type: "function", function: { name, arguments } } (arguments is a JSON string). Run the tool yourself, append the assistant message and a { "role": "tool", "tool_call_id": id, "content": result } message, and call again. ## JSON mode Use response_format { "type": "json_object" } (also say "JSON" in your prompt) or { "type": "json_schema", ... } for schema-shaped output. Parse choices[0].message.content as JSON. ## Errors Error bodies are { "error": { "message", "type", "code" } } and also carry rate_limit. After a long wait (see Long requests) they come with status 200; the codes below still apply. - 400: invalid request; code context_length_exceeded when input or max_tokens is over the limits; unsupported_request when no available model supports the request (e.g. a tools or JSON option) - 401 invalid_api_key: a key other than flm_anonymous was sent; drop the header - 429 rate_limit_exceeded: you hit one of your limits; wait retry-after seconds (see rate_limit for which window) - 429 rate_limited: the service is busy right now (requests wait up to 8s for capacity first); retry shortly with backoff - 502 upstream_error: the answer failed mid-way; retry - 503 service_unavailable: temporarily unavailable; retry later Retry guidance: on 429, honor the retry-after header; on 429 rate_limited, 502 or 503, retry with exponential backoff (e.g. 2s, 4s, 8s). Do not retry 400 or 401 unchanged. ## Examples curl: curl https://flm.stonech.at/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 512}' Python (pip install openai): from openai import OpenAI client = OpenAI(base_url="https://flm.stonech.at/v1", api_key="flm_anonymous") r = client.chat.completions.create(model="auto", messages=[{"role": "user", "content": "Hello!"}]) print(r.choices[0].message.content) JavaScript (npm i openai): import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://flm.stonech.at/v1", apiKey: "flm_anonymous" }); const r = await client.chat.completions.create({ model: "auto", messages: [{ role: "user", content: "Hello!" }] }); console.log(r.choices[0].message.content); Check your allowance: curl https://flm.stonech.at/v1/usage