API
reference.
Three endpoints, one model, OpenAI-shaped bodies. If you have shipped against the OpenAI chat completions API you already know this one — change the base URL and the model string.
Quickstart 01
Four steps from nothing to a token stream. A key issues at signup with an empty balance. Fifty dollars buys thirty days without a counter; five dollars buys a hundred requests that never expire.
Get a key
Sign up at signup.html. The key is shown once, starts with sk-rwi-, and is not recoverable — we store a hash, not the key. Put it in an environment variable.
Confirm it works
The model list is the cheapest health check: it authenticates and returns the single model entry.
Send a completion
Standard chat body. model is always qwen-flash-next. There is nothing else to pick.
Watch the counter
Billing is per request, not per token. GET /v1/usage reports requests consumed and remaining.
# export RAWINFER_KEY=sk-rwi-...
# 1 — auth check, returns the whole model list
curl https://api.rawinfer.com/v1/models \
-H "Authorization: Bearer $RAWINFER_KEY"
# 2 — first completion
curl https://api.rawinfer.com/v1/chat/completions \
-H "Authorization: Bearer $RAWINFER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen-flash-next",
"messages": [
{"role": "user", "content": "Explain what a TLB flush does, in three sentences."}
],
"max_tokens": 512
}'
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.rawinfer.com/v1",
api_key=os.environ["RAWINFER_KEY"],
)
# health check: one model, always
for m in client.models.list():
print(m.id, m.context_length) # qwen-flash-next 262144
r = client.chat.completions.create(
model="qwen-flash-next",
messages=[
{"role": "user", "content": "Explain what a TLB flush does, in three sentences."}
],
max_tokens=512,
temperature=0.7,
)
print(r.choices[0].message.content)
print(r.usage)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.rawinfer.com/v1",
apiKey: process.env.RAWINFER_KEY,
});
const models = await client.models.list();
console.log(models.data.map(m => m.id)); // [ 'qwen-flash-next' ]
const r = await client.chat.completions.create({
model: "qwen-flash-next",
messages: [
{ role: "user", content: "Explain what a TLB flush does, in three sentences." },
],
max_tokens: 512,
temperature: 0.7,
});
console.log(r.choices[0].message.content);
Non-streaming by default. One JSON body comes back when generation finishes. For anything longer than a paragraph, pass "stream": true — a xhigh reasoning call can spend a minute thinking before the first visible token.
Authentication 02
Every request carries a bearer token in the Authorization header. There are no cookies, no OAuth flow, no browser session, and no signed requests.
Authorization: Bearer sk-rwi-7Qx2mN-4pKd9R-bT1sVc-8yHf3Z
# bad — query parameter, not accepted
https://api.rawinfer.com/v1/models?api_key=...
# bad — wrong scheme, not accepted
Authorization: Token sk-rwi-7Qx2mN-4pKd9R-bT1sVc-8yHf3Z
- The key is displayed once at signup and never again. We store a one-way hash of it. Lose it, issue a new one from the dashboard; there is no recovery path.
- One key per account. Sharing a key is a fair-use violation on the Unlimited plan and gets the key revoked without refund.
- A missing or malformed header returns
401 invalid_api_key. A revoked or expired key returns the same code — we deliberately do not distinguish, so a leaked key gives an attacker no signal about whether it was ever valid. - Keys are not scoped by IP, referer, or origin. Treat one like a wallet seed phrase: environment variable on a machine you control, never a browser bundle, never a git repo.
The model 03
One identifier, permanently: qwen-flash-next. It resolves to the uncensored cybersecurity FP8 build of Qwen3.8-Flash-Next, served on SGLang across two machines.
https://api.rawinfer.com/v1/models{
"object": "list",
"data": [
{
"id": "qwen-flash-next",
"object": "model",
"created": 1788220800,
"owned_by": "rawinfer",
"context_length": 262144,
"max_output_tokens": 131072,
"modalities": {
"input": ["text", "image", "video"],
"output": ["text"]
}
}
]
}
| Field | Value |
|---|---|
| Context | 262,144 tokens native, extensible toward 1M |
| Max output | 131,072 tokens, trace included |
| Input | text, image, video |
| Output | text |
| Reasoning | off / low / xhigh, thinking on by default |
| Decoding | MTP speculative decoding |
Full provenance, benchmark sources, and known limitations live on the model card. That page is the authoritative one; this page is about wire format.
Chat completions 04
The only generation endpoint. OpenAI-compatible request body, one model, no variants.
https://api.rawinfer.com/v1/chat/completionsRequest parameters
| Field | Type | Default | Notes |
|---|---|---|---|
model |
string | required | Must be qwen-flash-next. Anything else → 404 model_not_found. |
messages |
array | required | Roles system, user, assistant, tool. Content is a string or an array of parts. |
max_tokens |
integer | 8192 | Caps completion tokens, reasoning trace included. Max 131,072. Exceeding the context returns 400, not silent truncation. |
temperature |
float | 0.7 | 0–2. 0 narrows sampling but does not make output reproducible on FP8. |
top_p |
float | 0.95 | Nucleus sampling. Adjust this or temperature, not both. |
stream |
boolean | false | Server-sent events. See streaming. |
stop |
string | array | null | Up to 4 sequences. The stop string is not included in the output. |
tools |
array | null | Function schemas. See tools. |
tool_choice |
string | object | auto | auto, none, or a named function object. |
response_format |
object | null | {"type":"json_object"}. No json_schema mode. |
reasoning_effort |
string | null | off · low · xhigh. Top-level extension; the server maps it onto the model's chat template. Omit for the server default. |
Minimal request
{
"model": "qwen-flash-next",
"messages": [
{"role": "system", "content": "You are assisting an authorised penetration test. Be direct."},
{"role": "user", "content": "Given this nmap output, list the three most likely initial access paths."}
],
"max_tokens": 2048,
"temperature": 0.6,
"reasoning_effort": "low"
}
Response
{
"id": "chatcmpl-9f2a71c4b0d34e51",
"object": "chat.completion",
"created": 1793834400,
"model": "qwen-flash-next",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1. The exposed 6379/TCP Redis instance...",
"reasoning_content": "Reading the scan: 22 open with OpenSSH 8.9..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 412,
"completion_tokens": 288,
"total_tokens": 700,
"completion_tokens_details": {
"reasoning_tokens": 197
}
}
}
message.contentis the answer.message.reasoning_contentis the trace, present only when thinking is on — it is a separate string and never mixed into the answer. Strip it before showing anything to a user.finish_reasonisstop,length, ortool_calls.lengthmeansmax_tokensclipped the output, trace included.usage.completion_tokensalready containsreasoning_tokens. Billing is per request, so the numbers are diagnostic, not a bill.n,logprobs,presence_penalty,frequency_penalty, andlogit_biasare accepted for SDK compatibility and ignored.
Streaming 05
"stream": true returns text/event-stream. Each event is one data: line carrying a chat completion chunk, terminated by data: [DONE].
curl -N https://api.rawinfer.com/v1/chat/completions \
-H "Authorization: Bearer $RAWINFER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen-flash-next",
"messages": [{"role": "user", "content": "Diff these two cron entries."}],
"stream": true,
"max_tokens": 1024
}'
stream = client.chat.completions.create(
model="qwen-flash-next",
messages=[{"role": "user", "content": "Diff these two cron entries."}],
stream=True,
max_tokens=1024,
reasoning_effort="low",
)
thinking = False
for chunk in stream:
d = chunk.choices[0].delta
if getattr(d, "reasoning_content", None):
if not thinking:
print("\n[thinking] ", end="")
thinking = True
print(d.reasoning_content, end="")
elif d.content:
if thinking:
print("\n[answer] ", end="")
thinking = False
print(d.content, end="", flush=True)
Wire format
data: {"id":"chatcmpl-9f2a71c4b0d34e51","object":"chat.completion.chunk","model":"qwen-flash-next","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-9f2a71c4b0d34e51","object":"chat.completion.chunk","model":"qwen-flash-next","choices":[{"index":0,"delta":{"reasoning_content":"The first entry fires at"},"finish_reason":null}]}
data: {"id":"chatcmpl-9f2a71c4b0d34e51","object":"chat.completion.chunk","model":"qwen-flash-next","choices":[{"index":0,"delta":{"content":"Both run hourly."},"finish_reason":null}]}
data: {"id":"chatcmpl-9f2a71c4b0d34e51","object":"chat.completion.chunk","model":"qwen-flash-next","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
usageis omitted from chunks. Issue a final non-streaming request, or read/v1/usage, if you need token counts.- Comment keep-alives (
: ping) are sent every 15 s while the model is thinking and no tokens exist yet. Ignore lines beginning with:. - The trace arrives as
delta.reasoning_content, then the answer asdelta.content. They are not interleaved; you can safely render the trace in a collapsed block. - A disconnect is not a refund. If you close the connection mid-stream, you have still spent one request; partial output is not resumed and there is no
chatcmplto replay.
Reasoning effort 06
Thinking is on by default. The model writes a trace before the answer, and the trace is billed as output tokens — not as time, not as a separate charge, but it does occupy max_tokens.
Effort is set per request with the top-level reasoning_effort field. It is never inferred from the prompt, and there is no system-level default that overrides what you send.
| Value | Trace | Use it when |
|---|---|---|
off |
None. reasoning_content is absent. |
Classification, extraction, rewriting, chat. Lowest latency, lowest token cost. |
low |
Short trace, typically 100–400 tokens. | The default working setting for agentic loops and most coding. |
xhigh |
Long trace. Thousands of tokens is normal. | Multi-step reasoning where a wrong answer costs more than the wait. Pair with a generous max_tokens. |
# raw body — the canonical form
{
"model": "qwen-flash-next",
"messages": [{"role": "user", "content": "..."}],
"reasoning_effort": "xhigh"
}
# python SDK — passes straight through as a keyword argument
client.chat.completions.create(..., reasoning_effort="xhigh")
# node SDK — same field, plain object property
client.chat.completions.create({ ..., reasoning_effort: "xhigh" })
Budget the trace. With xhigh, a request with "max_tokens": 256 will often return finish_reason: "length" and an empty or truncated answer, because the trace consumed the budget. Give xhigh at least 4096.
Images and video 07
content may be a string or an array of typed parts. Images and video frames are encoded into the same context window as text. Output is always text — there is no image generation endpoint.
{
"model": "qwen-flash-next",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "What is wrong with this build log?"},
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"detail": "high"
}
}
]
}
],
"max_tokens": 2048
}
{
"model": "qwen-flash-next",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Which step in this demo installs a persistence mechanism?"},
{
"type": "video_url",
"video_url": {"url": "https://example.org/clip.mp4"}
}
]
}
],
"max_tokens": 4096
}
image_url.urlacceptshttpsURLs anddata:URIs. Base64 is the reliable path — we do not proxy-fetch arbitrary URLs on the request path.detailisloworhigh.highcosts more tokens and is worth it for text-dense screenshots, stack traces, and dashboards.- Video is decoded server-side and sampled into frames, then encoded like images. Only the frames reach the model — there is no audio track in or out, no transcription, and no timeline awareness beyond frame order.
- Each image and frame consumes prompt tokens. A long video can eat a large share of a 262,144-token window; one video plus a prompt per request is the practical shape.
- Request bodies are capped at 8 MB. Larger media must be downscaled or sent as a URL the model can describe from a screenshot instead. Exceeding the cap returns
413.
Tools and JSON mode 08
Function calling follows the OpenAI shape. The model emits tool_calls; you execute them and send back a tool message. Nothing is executed on our side.
{
"model": "qwen-flash-next",
"messages": [{"role": "user", "content": "Check whether 10.0.4.19 responds on 443."}],
"tools": [
{
"type": "function",
"function": {
"name": "tcp_probe",
"description": "Open a TCP connection and report the result.",
"parameters": {
"type": "object",
"properties": {
"host": {"type": "string"},
"port": {"type": "integer"}
},
"required": ["host", "port"]
}
}
}
],
"tool_choice": "auto"
}
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_01H9x2b",
"type": "function",
"function": {
"name": "tcp_probe",
"arguments": "{\"host\":\"10.0.4.19\",\"port\":443}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
Send the result back as a tool message and keep the conversation going:
{
"model": "qwen-flash-next",
"messages": [
{"role": "user", "content": "Check whether 10.0.4.19 responds on 443."},
{"role": "assistant", "tool_calls": [{"id": "call_01H9x2b", "type": "function",
"function": {"name": "tcp_probe", "arguments": "{\"host\":\"10.0.4.19\",\"port\":443}"}}]},
{"role": "tool", "tool_call_id": "call_01H9x2b", "content": "connected in 41ms, TLS 1.2, cert CN=*.internal"}
]
}
JSON mode
{
"model": "qwen-flash-next",
"messages": [
{"role": "system", "content": "Reply with JSON only: {\"severity\": \"low|med|high\", \"reason\": string}"},
{"role": "user", "content": "Patch was applied to production without a change window."}
],
"response_format": {"type": "json_object"},
"reasoning_effort": "off"
}
json_objectguarantees syntactically valid JSON, not a schema. Describe the shape in the system message and validate client-side.json_schemawith strict mode is not implemented. Requests carrying it return400 unsupported_parameter.- Set
reasoning_efforttoofffor extraction work. A trace does not improve a well-specified schema and does cost tokens.
Errors 09
Errors return a JSON body with a stable type and code. Branch on code, not on the message text.
{
"error": {
"type": "invalid_request_error",
"code": "model_not_found",
"message": "model 'qwen3-max' does not exist. The only available model is 'qwen-flash-next'.",
"param": "model"
}
}
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request_error |
Malformed JSON, unknown role, empty messages, or a parameter outside its range. |
Fix the body. Retrying identical input cannot succeed. |
| 401 | invalid_api_key |
Missing, malformed, revoked, or expired key. Not distinguishable by design. | Check the header format and the key itself. Do not retry. |
| 402 | insufficient_credit |
The key has no credit: the pay-as-you-go balance is spent, the key was never funded, or the Unlimited period has lapsed. | Top up with crypto. No refunds and no grace requests. |
| 404 | model_not_found / not_found |
A model id other than qwen-flash-next, or an endpoint that does not exist on this API. |
Use the documented id. There is no other endpoint to discover. |
| 413 | request_too_large |
Body over 8 MB, or prompt plus max_tokens beyond 262,144 tokens. |
Trim history, downscale media, or reduce max_tokens. |
| 429 | rate_limit_exceeded |
Concurrency cap reached or the queue is full. There is no per-minute token cap — concurrency is the throttle. | Back off with jitter. Honour Retry-After when present. |
| 500 | server_error |
Unexpected failure inside the serving stack. Rare; the request may or may not have consumed credit. | Retry once after a second. If it persists, check status. |
| 503 | unavailable |
Both machines are restarting, or at capacity with no queue room. | Back off and retry. This is the code you will see during a restart window. |
A 5xx does not automatically void the charge. Request counts are consumed on admission, and there is no adjustment process — which is why the $5 pack exists: a hundred requests is enough to find that out before you buy a month.
Rate limits and concurrency 10
Billing is per request. There is no token-per-minute quota, no daily request cap on paid plans, and no burst credit to calculate. The only throttle is how many requests one key may have in flight at once.
| Plan | Concurrent | Requests | Notes |
|---|---|---|---|
| Try it | 5 | $5 / 100 | A hundred requests, prepaid, never expires. Not a plan — a look. |
| Unlimited | 5 | uncapped | $50 / month, one key. Fair use: no resale, no key sharing. |
- Requests beyond the concurrency cap wait in a short queue, then fail with
429. Retry with exponential backoff and jitter; hammering the endpoint lengthens the queue for everyone. - Only one connection per request counts, including streams. A stream held open for two minutes occupies a slot for two minutes.
- Throughput observed on this hardware is around 94 output tokens per second per stream, with p50 time-to-first-token of 1.78 s. Current numbers →
- Long-context requests are slower and occupy more KV cache. A 200K-token prompt can measurably reduce how many streams fit on the pair of machines.
Usage 11
Per-key counters only: requests, tokens, timestamps. No prompt text, no completion text, no metadata about what you sent.
https://api.rawinfer.com/v1/usage{
"object": "usage",
"plan": "payg",
"period": {
"start": "2026-11-01T00:00:00Z",
"end": "2026-11-05T14:22:09Z"
},
"requests": {
"used": 1264,
"purchased": 2000,
"remaining": 736
},
"tokens": {
"input": 1842203,
"output": 402118,
"reasoning": 271004
},
"concurrency": {
"limit": 5,
"current": 0
}
}
remainingis the number that matters. It decrements on admission, not on success.- On the Unlimited plan,
requests.purchasedisnullandremainingisnull; the field to watch isperiod.end, the paid-through date. - Counters are per key, not per account, and reset when the key is rotated. Rotating a key does not restore balance.
- There is no export, no downloadable statement, and no CSV. This JSON is the ledger.
Retention 12
Zero data retention is not a policy toggle here. There is no logging pipeline to switch off.
- Prompts and completions are held in memory for the duration of the request and are gone when it returns. Nothing is written to disk.
- No request logging, no error-report capture of payloads, no replay buffer, no training corpus. Outside the machines' RAM, your request did not exist.
- Kept: per-key counters — request count, token totals, timestamps — so usage can be metered. That is the entire record.
- Consequence: we cannot debug a bad completion for you, cannot reproduce it, and cannot produce it under subpoena. If you need an audit trail of your own requests, keep it on your side.
- Requests are processed on operator-owned hardware. No third-party inference provider, no CDN in front of the API, no APM vendor inside the process.
Details and the operator's obligations are in Privacy & ZDR.
SDK compatibility 13
Any client that speaks the OpenAI chat completions wire format works: point base_url at this host and set the key. Officially exercised in this build: openai for Python (1.40+) and Node (4+), plus curl.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.rawinfer.com/v1",
api_key=os.environ["RAWINFER_KEY"],
timeout=180.0, # xhigh traces are slow; the 60s default is too short
max_retries=2, # only 429 and 503 are retryable
)
| Surface | Status |
|---|---|
chat.completions.create | Supported, streaming and not |
models.list | Supported — one entry |
GET /v1/usage | Supported, raw HTTP only |
completions.create (legacy) | 404 |
embeddings | 404 |
images.generate | 404 |
audio.* and realtime | 404 |
files, batches, fine-tuning | 404 |
assistants, responses | 404 |
Two behavioural notes for SDK users. First, reasoning_effort is not in the OpenAI SDK schema, but both official SDKs forward unknown keyword arguments, so it passes straight through without extra_body. Second, message.reasoning_content is outside the SDK schema; in Python reach it with getattr(), in TypeScript cast the message.
One string to remember. qwen-flash-next. If a tutorial, a wrapper library, or an AI agent hands you any other model id, that call returns 404 — and that is the intended behaviour, not a bug to work around.
No part of this API generates images, audio, or embeddings. Input is text, image, and video; output is text. Cards are not accepted and nothing is refunded.
Get a key and make one call.
Fifty a month, unlimited. Try it for $5 first — a hundred requests, no expiry.