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
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.

python
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)
javascript
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-…
A key belongs on a server, in an environment variable. Anything shipped to a browser or committed to a repository is public, whatever the repository’s settings say. The API deliberately does not support browser-side use with a session cookie, because there is no safe way to put a key in a page.

Models

ModelidSizeBest for
SoreQen S1 Minisoreqen-s1-mini0.8BFastest. Good for quick questions and everyday chat.
SoreQen S1soreqen-s12BThe default. Balanced speed and depth.
SoreQen S1 Megasoreqen-s1-mega4BDeepest 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/completions

The only endpoint that generates. Everything below is a field on its body.

model
string · required
One of the ids above.
messages
array · 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.
stream
boolean
Server-sent events instead of one JSON body. Defaults to false.
effort
string
How hard to think: low, medium, high, extra, max. See Reasoning effort.
thinking
boolean
Turn reasoning off entirely for the fastest, cheapest answer. Defaults to whatever the chosen effort implies.
max_tokens
integer
Longest reply, up to 32,768. max_completion_tokens is accepted as a synonym. Counts thinking as well as prose.
context_tokens
integer
Ask for a smaller context window than the tier allows. Useful for cost control.
temperature
number
0 to 2. Overrides the sampling the effort preset would have chosen.
top_p
number
0 to 1.
presence_penalty
number
-2 to 2.
frequency_penalty
number
-2 to 2.
stop
string | string[]
Up to four sequences that end the generation.
seed
integer
Best-effort reproducibility. Identical inputs and seed usually give identical output; batching on the server means usually is not always.
tools
array
Up to 128 functions the model may call. See Tool calling.
tool_choice
string | object
auto, none, required, or a named function.
response_format
object
Constrain the reply to JSON or to a schema. See Structured output.
user
string
An opaque id of your own end user. Forwarded, never stored.

The response is the standard OpenAI shape.

json
{
  "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.

json
{
  "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.

If you want cheap and fast, send "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.

json
{
  "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:

json
"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:

json
"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.

json
{
  "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.

json
{
  "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,…"}}
    ]
  }]
}
Whether a given model is serving vision right now depends on how its workers were launched. A request the deployment cannot serve is refused with 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/models

Unauthenticated — 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.

json
{
  "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.

StatuscodeWhat it means
400invalid_request_errorThe body is malformed, or a field is out of range.
400model_not_foundThat model id does not exist.
400unsupported_parameterA chat-site field such as search or documents. Named, never silently dropped.
400vision_unavailableAn image was sent to a model not serving vision right now.
401invalid_api_keyMissing, wrong, or revoked. A revoked key is deliberately indistinguishable from a wrong one.
402insufficient_creditOut of credit, or this request costs more than the balance.
402spend_cap_reachedThe key hit its own monthly cap. Raise it in the console.
413context_length_exceededThe conversation is longer than the model is serving.
429Too many requests in flight. Back off and retry.
502The model returned an error or was unreachable.
503worker_cold_startA GPU is starting. Retry in a minute — see Limits.
503api_unavailableThe 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 window
per request
262,144 tokens entitled. What is served today is reported by GET /v1/models.
Output
per request
32,768 tokens, thinking included.
Tools
per request
128 function definitions.
Images
per request
4.
Request timeout
per request
15 minutes, which is well beyond any normal generation.
Keys
per 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.

Cold starts are real and you should handle them. The fleet scales to zero when nobody is calling, so the first request after a quiet period starts a GPU and can take a minute or more. It answers 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.

ModelInput / 1MOutput / 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.

  • effort and thinking are additions. Nothing else is.
  • stream defaults to false, as it does at OpenAI.
  • Reasoning comes back in reasoning_content, and reasoning tokens are counted separately in usage.
  • n, logprobs and logit_bias are 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 search is 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.
Something unclear, or missing? Get in touch — the documentation is part of the product, and a question you had to ask is a bug in this page.
API Documentation · SoreQen Platform