Reference · v1

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.

https://api.rawinfer.com/v1Base URL
qwen-flash-nextModel id
BearerAuth scheme
2026-11Document revision

Base URL https://api.rawinfer.com/v1 · all examples below are live-shaped, not pseudocode

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.

01

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.

02

Confirm it works

The model list is the cheapest health check: it authenticates and returns the single model entry.

03

Send a completion

Standard chat body. model is always qwen-flash-next. There is nothing else to pick.

04

Watch the counter

Billing is per request, not per token. GET /v1/usage reports requests consumed and remaining.

shell
# 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
  }'
python · openai>=1.40
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)
node · openai>=4
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.

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

GEThttps://api.rawinfer.com/v1/models
200 application/json
{
  "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"]
      }
    }
  ]
}
FieldValue
Context262,144 tokens native, extensible toward 1M
Max output131,072 tokens, trace included
Inputtext, image, video
Outputtext
Reasoningoff / low / xhigh, thinking on by default
DecodingMTP 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.

POSThttps://api.rawinfer.com/v1/chat/completions

Request 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

POST /v1/chat/completions
{
  "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

200 application/json
{
  "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.content is the answer. message.reasoning_content is 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_reason is stop, length, or tool_calls. length means max_tokens clipped the output, trace included.
  • usage.completion_tokens already contains reasoning_tokens. Billing is per request, so the numbers are diagnostic, not a bill.
  • n, logprobs, presence_penalty, frequency_penalty, and logit_bias are 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].

shell · -N disables buffering
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
  }'
python
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

text/event-stream
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]
  • usage is 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 as delta.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 chatcmpl to 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.
setting effort · three languages
# 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.

image_url · https or data URI
{
  "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
}
video_url · sampled to frames
{
  "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.url accepts https URLs and data: URIs. Base64 is the reliable path — we do not proxy-fetch arbitrary URLs on the request path.
  • detail is low or high. high costs 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.

request with tools
{
  "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"
}
response with a tool call
{
  "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:

tool result turn
{
  "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

response_format
{
  "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_object guarantees syntactically valid JSON, not a schema. Describe the shape in the system message and validate client-side.
  • json_schema with strict mode is not implemented. Requests carrying it return 400 unsupported_parameter.
  • Set reasoning_effort to off for 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 envelope
{
  "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.

GEThttps://api.rawinfer.com/v1/usage
200 application/json
{
  "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
  }
}
  • remaining is the number that matters. It decrements on admission, not on success.
  • On the Unlimited plan, requests.purchased is null and remaining is null; the field to watch is period.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.

python · env-var pattern
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
)
SurfaceStatus
chat.completions.createSupported, streaming and not
models.listSupported — one entry
GET /v1/usageSupported, raw HTTP only
completions.create (legacy)404
embeddings404
images.generate404
audio.* and realtime404
files, batches, fine-tuning404
assistants, responses404

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.

Done reading

Get a key and make one call.

Fifty a month, unlimited. Try it for $5 first — a hundred requests, no expiry.