API reference
The mini-lab API is compatible with OpenAI's Chat Completions API. Anything that speaks it (the official SDKs, curl, LangChain…) works by changing the base URL:
https://mini-lab.sitesolide.fr/v1
Quickstart
Create a key on the API keys page, export it, and send a message:
export MINILAB_API_KEY=sk-mini-...
curl https://mini-lab.sitesolide.fr/v1/chat/completions \
-H "Authorization: Bearer $MINILAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "mini-1", "messages": [{"role": "user", "content": "Tell me a story about a cat"}]}'
With the official Python SDK (pip install openai):
import os
from openai import OpenAI
client = OpenAI(base_url="https://mini-lab.sitesolide.fr/v1", api_key=os.environ["MINILAB_API_KEY"])
response = client.chat.completions.create(
model="mini-1",
messages=[{"role": "user", "content": "Tell me a story about a cat"}],
)
print(response.choices[0].message.content)
print(response.usage) # prompt_tokens, completion_tokens: what you are billed for
The models are tiny: they write short children's stories and do simple arithmetic (with a calculator). Don't expect more, that's the charm.
Authentication
Send your key as a bearer token: Authorization: Bearer sk-mini-.... Keys belong to a project inside an organization; every request is billed to that organization and logged with the key and project.
- We store only a hash of each key, so a key is shown once, when you create it. Lost it? Revoke it and create a new one.
- A revoked key is rejected immediately with
401 invalid_api_key. - A key can have a spend limit: once reached, it gets
429 insufficient_quotawhile other keys keep working. - Never put a key in client-side code: anyone who sees it can spend your credits.
Chat completions
POST /v1/chat/completions generates the next assistant message of a conversation.
| Parameter | Type | Description |
|---|---|---|
| model | string, required | A model id from GET /v1/models, e.g. mini-1. |
| messages | array, required | The conversation: objects with a role (system, developer, user, assistant, tool) and a content string (or text parts). Assistant messages may carry tool_calls; tool messages carry tool_call_id. |
| max_completion_tokens | integer | Maximum tokens to generate (max_tokens is accepted too). Default: until the model stops or the context window is full. |
| temperature | number, 0 to 2 | Sampling temperature. Default 1. Lower is more deterministic. |
| top_p | number, 0 to 1 | Nucleus sampling. Default 1. |
| top_k | integer | mini-lab extension: sample only among the k most likely tokens. With the Python SDK, pass extra_body={"top_k": 20}. |
| seed | integer | Makes sampling reproducible for the same request. |
| stop | string or array | Up to 4 sequences where generation stops. |
| stream | boolean | Stream the answer as server-sent events. See Streaming. |
| stream_options | object | {"include_usage": true} adds a final chunk with token usage. |
| tools | array | Function tools the model may call. The models are trained for one: calculator. |
| tool_choice | string | "auto" (default) or "none" (the model doesn't see the tools). |
| n | integer | Only 1 is supported. |
Parameters that would change the answer but aren't supported (n > 1, logprobs, JSON mode, penalties…) are rejected with a 400 rather than silently ignored. Unknown harmless fields such as user are ignored.
Response:
{
"id": "chatcmpl-8f3a...",
"object": "chat.completion",
"created": 1790000000,
"model": "mini-1",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Once upon a time, there was a cat named Tom..."},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 14, "completion_tokens": 58, "total_tokens": 72}
}
finish_reason is stop (the model finished or hit a stop sequence), length (max tokens or context window reached) or tool_calls. The request id is also returned in the x-request-id header, and appears in your logs.
Streaming
With "stream": true the response is a text/event-stream: one chat.completion.chunk per data: line, as tokens are generated, then data: [DONE].
stream = client.chat.completions.create(
model="mini-1",
messages=[{"role": "user", "content": "Tell me a story about a dog"}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage: # last chunk, with include_usage
print("\n", chunk.usage)
If generation fails after the stream has started, a chunk with an error object is sent instead of more tokens (the SDKs raise it as an exception).
Tool calling
The models were trained to use one tool, a calculator, for arithmetic. Declare it in tools; when the model wants it, the response has finish_reason: "tool_calls" and message.tool_calls. Run the calculation yourself, append the result as a tool message, and call the API again for the final answer.
tools = [{
"type": "function",
"function": {
"name": "calculator",
"description": "Evaluate an arithmetic expression.",
"parameters": {"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]},
},
}]
messages = [{"role": "user", "content": "What is 347 + 58?"}]
response = client.chat.completions.create(model="mini-1", messages=messages, tools=tools)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for call in message.tool_calls:
expression = json.loads(call.function.arguments)["expression"] # "347 + 58"
result = my_safe_calculator(expression) # "405" (never eval() it!)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
response = client.chat.completions.create(model="mini-1", messages=messages, tools=tools)
print(response.choices[0].message.content) # "The answer is 405."
When streaming, tool calls arrive at the end, one chunk per call with its complete arguments. The playground and chat app run this loop for you on the server.
Scratchpad reasoning
Some models think before answering. That scratchpad is returned separately from the answer, in the non-standard field message.reasoning_content (delta.reasoning_content when streaming), like other OpenAI-compatible servers do. It is billed as output tokens. The SDKs keep unknown fields, so message.reasoning_content works in Python.
Models
GET /v1/models lists the models currently served; GET /v1/models/{id} returns one. Besides OpenAI's fields, each model includes its description, context window and pricing. See Models & pricing.
curl https://mini-lab.sitesolide.fr/v1/models -H "Authorization: Bearer $MINILAB_API_KEY"
Errors
Errors use OpenAI's shape, so the SDKs raise the matching exception:
{"error": {"message": "You exceeded your current quota...", "type": "insufficient_quota", "param": null, "code": "insufficient_quota"}}
| Status | Code | What happened |
|---|---|---|
| 400 | invalid_request_error | The request is malformed or uses an unsupported parameter (see param). |
| 400 | context_length_exceeded | The conversation plus max_tokens doesn't fit in the model's context window. Send fewer or shorter messages, or lower max_tokens. |
| 401 | invalid_api_key | The key is missing, wrong or revoked. |
| 404 | model_not_found | No model with this id is being served. |
| 429 | insufficient_quota | Your organization has no credits left, or the key reached its spend limit. Retrying won't help: add credits. |
| 429 | rate_limit_exceeded | Too many requests or tokens per minute for this key. Retry after the reset time. |
| 502 | upstream_error | The inference server failed. Retry. |
| 503 | overloaded | The inference server is busy. Retry with backoff. |
| 503 | inference_unavailable | The inference server is down or restarting. Retry shortly. |
Failed requests are logged but not billed.
Rate limits
Each key is limited in requests per minute (RPM) and tokens per minute (TPM). The defaults are 60 RPM and 40,000 TPM. The playground and chat app share one budget per organization.
Every response carries the current state in OpenAI's headers:
x-ratelimit-limit-requests: 60
x-ratelimit-remaining-requests: 59
x-ratelimit-reset-requests: 1s
x-ratelimit-limit-tokens: 40000
x-ratelimit-remaining-tokens: 39744
x-ratelimit-reset-tokens: 384ms
Over the limit you get 429 rate_limit_exceeded. The SDKs retry these automatically with backoff.
Pricing and billing
Credits are prepaid. A request costs its prompt tokens times the model's input price plus its completion tokens times the output price (prices are per million tokens). It is deducted from your organization's balance as soon as it completes. When the balance reaches zero, requests get 429 insufficient_quota until you add credits.
| Model | Context | Input, per 1M | Output, per 1M |
|---|---|---|---|
| mini-1 | 256 | $0.50 | $1.50 |
Usage from the playground and the chat app is billed the same way, and shows up in Usage and Logs with its source.