Developers
Quickstart
Point an OpenAI-compatible client at the API host, use your project key, send a chat completion. The format is compatible; the service is Media Holding's. No OpenAI services are involved.
Five steps
Create a project key in the cabinet
Keys belong to a project of your organisation. The secret is shown once; store it in an environment variable (
SOVEREIGN_API_KEYin the examples), never in code.Send a chat completion with curl
One request, one answer, the usage in the response body.
Use the Python client
Install the
openaipackage and setbase_urlto the API host. Nothing else in your code changes; the package only speaks the format — it calls our host, not OpenAI's.Stream
Set
stream=Trueandstream_options={"include_usage": True}; print the deltas as they arrive and read the usage from the final chunk.Read your usage
GET /v1/usagereturns requests and token counts of your project for a period — the same ledger your invoice is built from.
OpenAI-compatible API format. No OpenAI services are involved. The API host in the examples is the platform host; the cabinet shows the host of your dedicated instance.
curl
curl https://sovereignai-api.mediaholding.tech/v1/chat/completions \
-H "Authorization: Bearer $SOVEREIGN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mh-sovereign",
"messages": [
{"role": "system", "content": "You answer briefly and cite the clause you rely on."},
{"role": "user", "content": "Summarise the termination clause of the attached contract in two sentences."}
]
}'
Python — the openai client with base_url
The openai package is used only as a client for the OpenAI-compatible request format. With base_url set to our host no request reaches OpenAI.
import os
from openai import OpenAI # the OpenAI-compatible client format; no OpenAI service is called
client = OpenAI(
base_url="https://sovereignai-api.mediaholding.tech/v1",
api_key=os.environ["SOVEREIGN_API_KEY"],
)
response = client.chat.completions.create(
model="mh-sovereign",
messages=[
{"role": "system", "content": "You answer briefly and cite the clause you rely on."},
{"role": "user", "content": "Summarise the termination clause of the attached contract in two sentences."},
],
)
print(response.choices[0].message.content)
print(response.usage) # prompt_tokens, completion_tokens, total_tokens
Streaming
stream = client.chat.completions.create(
model="mh-sovereign",
messages=[{"role": "user", "content": "List three risks of the clause."}],
stream=True,
stream_options={"include_usage": True}, # the final chunk carries usage
)
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:
print("\n", chunk.usage)
Thinking for numeric tasks
Thinking on request
MeasuredThe 9B model reasons before answering when thinking is enabled for the request (budget 1,024 tokens). Measured on 2026-10-12: tasks with money, prices or quantities need it.
# Money, prices and quantities: switch the model's thinking on for the request.
response = client.chat.completions.create(
model="mh-sovereign-reason",
messages=[{"role": "user", "content": "Invoice total 1,240.50 EUR net; VAT 24 %. Gross amount?"}],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
max_tokens=1024,
)
`chat_template_kwargs` is the llama.cpp runtime's switch for the model's thinking mode; the final request field of the public API is fixed in the generated specification.
Choosing a model
mh-sovereign is the default alias and resolves to the 9B model (mh-sovereign-reason). Use mh-sovereign-small for classification and extraction at speed, mh-sovereign-lingua for multilingual drafts. The catalogue carries the measured numbers and the known limits of each.
What the API guarantees
Keys
BuiltPrefix visible in the cabinet, secret shown once, stored as a keyed one-way hash (HMAC-SHA-256); rotation and revocation without downtime for other keys of the project.
Client libraries
BuiltAny client that speaks the OpenAI-compatible chat-completions format works by changing base_url and the key — Python, JavaScript, Go, curl. No OpenAI services are involved.
Request identifiers
BuiltX-Request-Id on every response and error.request_id in every error body; the same id in our audit line, so support can find the request — its metadata, never its content.
Rate limits
Built429 with Retry-After on a rate limit or a project budget; back off for the seconds given. Rate-limit headers are planned; names are final in the generated specification.
Error contract
BuiltOne JSON shape, eight error types, the HTTP status naming the class; messages free of URLs, tokens, paths and upstream text.
Status labels — MEASURED — exists and was measured on the stated date by the stated method. BUILT — exists and works, not yet measured under load. PLANNED — designed, not yet built. A line without a label is not a claim of this platform.
Errors
Every error is one JSON shape with the HTTP status that names its class. The message never contains URLs, tokens, paths or upstream exception text.
| HTTP | error.type | When | Retry-After |
|---|---|---|---|
400 | invalid_request_error | The request body or parameters are invalid. | no |
401 | authentication_error | Missing, malformed, revoked or unknown API key. | no |
403 | permission_error | The key is valid but may not use this model, project or route. | no |
404 | invalid_request_error | Unknown model or route; code names it. | no |
429 | rate_limit_error · budget_exceeded | A rate limit or a project budget is reached; wait the seconds given. | yes |
500 | server_error | An unexpected failure on our side; quote the request id. | no |
503 | backend_busy · upstream_unavailable | The GPU is held by another job, or the inference provider is down. No fallback to an external supplier exists. | yes |
An error response with its headers
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-Request-Id: 5b8e2b2e-4a2f-4b7c-9d2e-1f3a4b5c6d7e
Retry-After: 12
{
"error": {
"type": "rate_limit_error",
"code": "requests_per_minute",
"message": "Too many requests for this project. Retry after the number of seconds in Retry-After.",
"request_id": "5b8e2b2e-4a2f-4b7c-9d2e-1f3a4b5c6d7e"
}
}