API Documentation
One endpoint, in the OpenAI chat-completions format. If your code already calls an OpenAI-compatible API, two lines change.
Quickstart
Create a key in the console. Every account starts with $5.00 of credit, which is several hundred thousand tokens on SoreQen S1. No card, no monthly minimum.
curl https://soreqen.com/v1/chat/completions \
-H "Authorization: Bearer $SOREQEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "soreqen-s1",
"messages": [{"role": "user", "content": "Explain a B-tree in three sentences."}]
}'The OpenAI SDKs work unchanged. Point the base URL here and use a SoreQen model id.
from openai import OpenAI
client = OpenAI(
api_key=os.environ["SOREQEN_API_KEY"],
base_url="https://soreqen.com/v1",
)
resp = client.chat.completions.create(
model="soreqen-s1",
messages=[{"role": "user", "content": "yaar ek regex samjha do"}],
)
print(resp.choices[0].message.content)import OpenAI from 'openai'
const client = new OpenAI({
apiKey: process.env.SOREQEN_API_KEY,
baseURL: 'https://soreqen.com/v1',
})
const stream = await client.chat.completions.create({
model: 'soreqen-s1',
messages: [{ role: 'user', content: 'Summarise this contract.' }],
stream: true,
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}Authentication
A bearer token on every request. Keys begin soreqen-live- and carry 32 bytes of randomness — guessing one is not a threat model. The prefix is spelled out rather than initialled so a key found in a log, a screenshot or a pasted stack trace announces whose it is, which is also what lets secret scanners recognise one.
Authorization: Bearer soreqen-live-…Models
| Model | id | Size | Best for |
|---|---|---|---|
| SoreQen S1 Mini | soreqen-s1-mini | 0.8B | Fastest. Good for quick questions and everyday chat. |
| SoreQen S1 | soreqen-s1 | 2B | The default. Balanced speed and depth. |
| SoreQen S1 Mega | soreqen-s1-mega | 4B | Deepest reasoning. Best for hard problems and long documents. |
Every key reaches all three. The API is pay-as-you-go and separate from the chat plans — a subscription buys chat, credit buys calls, and neither gates the other.
Chat completions
POST https://soreqen.com/v1/chat/completionsThe only endpoint that generates. Everything below is a field on its body.
modelstring · required | One of the ids above. |
messagesarray · required | Turns, in order. Roles are system, user, assistant and tool. Content is a string, or an array of parts when it carries images. An assistant turn may carry tool_calls and no content. |
streamboolean | Server-sent events instead of one JSON body. Defaults to false. |
effortstring | How hard to think: low, medium, high, extra, max. See Reasoning effort. |
thinkingboolean | Turn reasoning off entirely for the fastest, cheapest answer. Defaults to whatever the chosen effort implies. |
max_tokensinteger | Longest reply, up to 32,768. max_completion_tokens is accepted as a synonym. Counts thinking as well as prose. |
context_tokensinteger | Ask for a smaller context window than the tier allows. Useful for cost control. |
temperaturenumber | 0 to 2. Overrides the sampling the effort preset would have chosen. |
top_pnumber | 0 to 1. |
presence_penaltynumber | -2 to 2. |
frequency_penaltynumber | -2 to 2. |
stopstring | string[] | Up to four sequences that end the generation. |
seedinteger | Best-effort reproducibility. Identical inputs and seed usually give identical output; batching on the server means usually is not always. |
toolsarray | Up to 128 functions the model may call. See Tool calling. |
tool_choicestring | object | auto, none, required, or a named function. |
response_formatobject | Constrain the reply to JSON or to a schema. See Structured output. |
userstring | An opaque id of your own end user. Forwarded, never stored. |
The response is the standard OpenAI shape.
{
"id": "cmpl-…",
"object": "chat.completion",
"model": "soreqen-s1",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "A B-tree keeps its keys sorted …",
"reasoning_content": "…"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 24,
"completion_tokens": 118,
"reasoning_tokens": 340,
"total_tokens": 482
}
}finish_reason is stop when the model finished, length when it hit max_tokens mid-sentence, or tool_calls when it wants a function run. Treat length as a truncated answer, not a complete one.
Reasoning effort
SoreQen’s one real addition to the request body, and the main lever on both quality and cost. Higher settings spend more output tokens thinking before answering, and thinking is billed as output.
{
"model": "soreqen-s1",
"messages": [{"role": "user", "content": "Why is this query slow?"}],
"effort": "high",
"thinking": true
}The reasoning appears in reasoning_content, separately from the answer, so you can log it, show it, or ignore it without parsing it out of the prose.
SoreQen S1 Mini accepts thinking but ignores the upper effort settings. On a model that size they produce loops rather than better answers, so the request is clamped rather than refused.
"thinking": false. It is the single biggest cost lever in the API — far larger than switching to a smaller model.Streaming
Set "stream": true for server-sent events. Content arrives in delta frames; the final frame carries usage and an empty choices array, and then data: [DONE].
data: {"choices":[{"index":0,"delta":{"content":"A B-tree"}}]}
data: {"choices":[{"index":0,"delta":{"content":" keeps"}}]}
data: {"choices":[],"usage":{"prompt_tokens":24,"completion_tokens":118}}
data: [DONE]Read usage from that penultimate frame rather than counting tokens yourself. A stream you abandon is still billed for what was generated before you disconnected — the GPU did the work either way.
Tool calling
Standard OpenAI shape, in four steps: send tools, receive tool_calls, run the function yourself, send the result back as a tool turn carrying the same id.
{
"model": "soreqen-s1",
"messages": [{"role": "user", "content": "weather in Jaipur?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}],
"tool_choice": "auto"
}The model answers with a call rather than prose:
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\":\"Jaipur\"}"}
}]
},
"finish_reason": "tool_calls"Run it, then send the whole exchange back with the result appended:
"messages": [
{"role": "user", "content": "weather in Jaipur?"},
{"role": "assistant", "content": null, "tool_calls": [ … ]},
{"role": "tool", "tool_call_id": "call_1", "content": "34C, clear"}
]arguments is a JSON string, not an object — that is OpenAI’s shape and it is kept for compatibility. Parse it before use, and guard the parse: a model can produce malformed JSON, and strict: true on the function makes that far less likely without making it impossible.
Tool names must be unique within a request. The model answers with a name, so a duplicate makes the answer ambiguous, and the request is refused rather than resolved arbitrarily.
Structured output
Constrain the reply to a JSON Schema and the decoder cannot emit anything else. No fenced block to strip out of prose, no retry loop when the model adds a preamble.
{
"model": "soreqen-s1",
"messages": [{"role": "user", "content": "Extract the invoice total."}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": true,
"schema": {
"type": "object",
"properties": {
"total": {"type": "number"},
"currency": {"type": "string"}
},
"required": ["total", "currency"]
}
}
},
"temperature": 0
}{"type": "json_object"} also works when any valid JSON will do. Pair either with temperature: 0 for repeatable extraction — the API honours your sampling settings rather than overriding them with a preset.
Vision
Images travel as OpenAI content parts, as a URL or inline as a data URI. Up to four images per request.
{
"model": "soreqen-s1",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is written on this sign?"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,…"}}
]
}]
}vision_unavailable, naming the models that can — never forwarded to fail obscurely. GET /v1/models reports which models accept images today.Listing models
curl https://soreqen.com/v1/modelsUnauthenticated — everything it returns is published here anyway, and requiring a key would break the first thing many SDKs do on connect.
Two context numbers, deliberately. entitled_context_window is what your key grants; context_window is what the workers can actually serve at this moment. They match today. They would not if the fleet were ever running a shorter build, and the smaller of the two is what applies — so this endpoint, not this page, is the number to trust in code.
{
"id": "soreqen-s1",
"object": "model",
"owned_by": "zorqelis-ai",
"soreqen": {
"context_window": 262144,
"entitled_context_window": 262144,
"max_output_tokens": 32768,
"capabilities": {
"vision": true,
"toolCalling": true,
"structuredOutput": true,
"streaming": true,
"reasoning": true
},
"pricing_usd_per_million": {"input": 0.10, "output": 0.30}
}
}Errors
Every error is a JSON body with an error object carrying a message, a type, and usually a machine-readable code. Switch on the code, show the message.
| Status | code | What it means |
|---|---|---|
| 400 | invalid_request_error | The body is malformed, or a field is out of range. |
| 400 | model_not_found | That model id does not exist. |
| 400 | unsupported_parameter | A chat-site field such as search or documents. Named, never silently dropped. |
| 400 | vision_unavailable | An image was sent to a model not serving vision right now. |
| 401 | invalid_api_key | Missing, wrong, or revoked. A revoked key is deliberately indistinguishable from a wrong one. |
| 402 | insufficient_credit | Out of credit, or this request costs more than the balance. |
| 402 | spend_cap_reached | The key hit its own monthly cap. Raise it in the console. |
| 413 | context_length_exceeded | The conversation is longer than the model is serving. |
| 429 | — | Too many requests in flight. Back off and retry. |
| 502 | — | The model returned an error or was unreachable. |
| 503 | worker_cold_start | A GPU is starting. Retry in a minute — see Limits. |
| 503 | api_unavailable | The API is not enabled on this deployment. |
Retry 429, 502 and 503 with exponential backoff. Do not retry 400, 401 or 402 — the same request will fail the same way, and a retry loop on a 402 is a loop that never ends.
Limits and concurrency
Context windowper request | 262,144 tokens entitled. What is served today is reported by GET /v1/models. |
Outputper request | 32,768 tokens, thinking included. |
Toolsper request | 128 function definitions. |
Imagesper request | 4. |
Request timeoutper request | 15 minutes, which is well beyond any normal generation. |
Keysper account | 25 active. Revoked keys do not count. |
Concurrency. Requests are queued per model and served by the GPU fleet behind it. There is no published per-key concurrency limit — what you actually get is the fleet’s capacity at that moment, shared fairly. Under load a request waits in the queue rather than being rejected, which is why the timeout is generous.
503 with worker_cold_start rather than hanging. Retry it; the second request is fast. If your workload cannot tolerate that, say so — always-warm capacity is arrangeable.Credit and billing
Pay-as-you-go from a prepaid balance. Every account starts with $5.00, and calls are charged as each one finishes.
| Model | Input / 1M | Output / 1M |
|---|---|---|
| SoreQen S1 Mini | $0.05 | $0.15 |
| SoreQen S1 | $0.10 | $0.30 |
| SoreQen S1 Mega | $0.25 | $0.75 |
Reasoning tokens are billed as output tokens. They are generated, and asking for more thinking is asking the GPU for more work. Full pricing →
When the balance runs out, calls answer 402 rather than degrading. A request whose prompt alone would cost more than the balance is refused before it runs, and a reply is capped at what the remaining balance can pay for — so a call cannot leave an account overdrawn by more than a rounding error.
Keys and security
A key is shown once, at creation, and stored only as a SHA-256 hash. There is no reveal-key button and there cannot be one: the plaintext does not exist anywhere after the response that created it. A lost key is rotated, not recovered.
Rotate rather than revoke when a key is merely old. Rotation issues the replacement first and revokes the original after, so nothing stops working in between — which matters, because rotation is usually something you do in a hurry.
Set a monthly cap on every key. It is the difference between a leaked key costing a fixed amount and costing whatever the month allows.
Revocation takes effect within about ten seconds. The gateway caches a resolved key for that long, which is what keeps a burst of requests from becoming a burst of database reads.
Differences from OpenAI
Worth knowing before you port something across.
effortandthinkingare additions. Nothing else is.streamdefaults tofalse, as it does at OpenAI.- Reasoning comes back in
reasoning_content, and reasoning tokens are counted separately inusage. n,logprobsandlogit_biasare accepted and ignored rather than rejected, so an existing client does not break on them. One completion is returned.- Web search is not part of the API. It costs money at a provider and has no line in the per-token pricing, so sending
searchis refused by name rather than silently dropped. - There are no organisation or project headers. A key belongs to an account, and that is the whole hierarchy.