Kynetra ConfluxLaunching soon

Kynetra Conflux · Phase 0 · Launching soon

One key. Every model.

Conflux is an OpenAI-compatible gateway to a growing catalog of language models, with a single API key, unified credit-based billing, and per-key usage in one dashboard — so you stop juggling a different key, bill, and rate limit for every provider.

Kynetra.dev See the quickstart

Quickstart

Drop-in for the OpenAI SDK

Conflux speaks the OpenAI chat completions format. Point your existing OpenAI client at Conflux's base URL and swap in a Conflux key — nothing else in your code has to change.

Conflux isn't publicly available yet — the snippet below is a preview of the interface it will expose at launch, not a live endpoint you can call today.

from openai import OpenAI

client = OpenAI(
    base_url="https://conflux.kynetra.dev/v1",
    api_key="qai_...",
)

response = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.5",
    messages=[
        {"role": "user", "content": "Say hello from Conflux."}
    ],
    stream=True,
)

for chunk in response:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
curl https://conflux.kynetra.dev/v1/chat/completions \
  -H "Authorization: Bearer qai_..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.5",
    "messages": [
      {"role": "user", "content": "Say hello from Conflux."}
    ]
  }'

Keys will be issued from the QuantumOS admin console (prefixed qai_) once Conflux launches. At that point, conflux.kynetra.dev will be served by the QuantumOS admin host — the same /v1/chat/completions and /v1/models endpoints shown above. Set stream: true for server-sent-event streaming, as shown above; omit it for a single non-streaming JSON response.

Capabilities

What's actually in Phase 0

No roadmap items below — this is what the API is built to do, ahead of launch.

OpenAI-compatible

Standard /v1/chat/completions request/response shape, so existing OpenAI SDKs and tooling work with a base-URL and key swap.

Streaming and non-streaming

Set stream: true for server-sent-event token deltas, or omit it for a single JSON completion.

Unified credit billing

One credit balance across every model you call — no separate accounts or invoices per provider.

Per-key rate limits

Every key gets its own token-bucket limit, reported via standard RateLimit-* response headers.

Multi-provider routing

Requests route through OpenRouter to a wide model catalog, with bring-your-own-key (BYOK) support for your own provider accounts, plus support for local/self-hosted models via Ollama.

Usage dashboard

Per-key call counts, token usage, and cost show up in the QuantumOS admin console as they happen.

Architecture

How it works

Conflux is a thin, OpenAI-compatible layer in front of QuantumOS's existing multi-provider AI gateway — not a separate infrastructure stack. It's Phase 0: one Next.js route handler per endpoint, backed by Postgres, not a distributed global edge network.

Request path

POST /v1/chat/completions and GET /v1/models run inside the same admin-web app as the rest of QuantumOS. The global admin-web auth middleware explicitly lets any /v1/* path through unauthenticated, so each Conflux route owns its own bearer-key check rather than relying on session auth. Once a request is authenticated, rate-limited, and priced, the actual model call is delegated to the gateway's aiCall() / aiCallStream() functions — the same call path QuantumOS's own internal AI features use.

Provider resolution

For a given model, the gateway resolves a provider key in a fixed order: first a workspace's own bring-your-own-key (BYOK) credential for that provider, then a native platform API key from environment config if one exists for that provider, and finally OpenRouter as the universal fallback that can route almost any model without a dedicated key, via OpenRouter's own chat completions API.

What Conflux adds on top

The underlying gateway doesn't know about customer-facing qai_ keys, per-key rate limits, or catalog-priced credit billing — those are entirely Conflux's own layer, added by three small modules that sit in front of the shared gateway call:

Auth

conflux-auth.ts — resolves a qai_ bearer key to a tenant by hashing it and looking it up in qos_ai_keys.

Rate limiting

conflux-ratelimit.ts — a Postgres-backed token bucket per key, stored in qos_conflux_rate_buckets.

Catalog & billing

ai-model-catalog.ts — the shared, cached qos_ai_models catalog both /v1/models and the credits debit read from.

This is intentionally a stopgap: the plan for Conflux beyond Phase 0 is a dedicated Rust gateway that replaces the Postgres-backed rate limiter with an in-process one — but that hasn't shipped yet, and nothing on this page describes it as though it has.

Authentication

Bearer keys, qai_-prefixed

Every /v1/chat/completions request is authenticated with a bearer key issued from the QuantumOS admin console. GET /v1/models requires no authentication at all — it's a public catalog listing.

Header format

Authorization: Bearer qai_<the rest of your key>

The route matches the header against /^Bearer\s+(qai_\S+)$/ — anything that isn't exactly Bearer, one space, then a token starting with qai_ fails to even extract a candidate key, and is treated as unauthenticated.

How a key resolves to a tenant

  1. The plaintext key is SHA-256 hashed (the same hashKey() the internal gateway uses, so one key row means the same thing everywhere it's checked).
  2. The hash is looked up against qos_ai_keys.key_hash, fetching id, tenant_id, status, and expires_at.
  3. No matching row → unauthenticated.
  4. status isn't exactly 'active' (e.g. revoked) → unauthenticated.
  5. expires_at is set and already in the past → unauthenticated.
  6. Otherwise the key resolves to { tenantId: row.tenant_id ?? row.id, keyId: row.id } — every later step (rate limiting, credits, usage rows) is scoped to that tenant and key.

qos_ai_keys doesn't yet have a distinct workspace_id column, so the workspaceId a request resolves to is currently the same value as tenantId.

On failure

Any of the cases above — missing header, malformed header, unknown key, revoked key, or expired key — produces the same response: 401 with detail Invalid or missing API key and a WWW-Authenticate: Bearer header. Conflux doesn't distinguish "wrong key" from "expired key" from "revoked key" in the response body, so a caller can't fingerprint valid-but-inactive keys by probing.

API Reference

Endpoints

Two endpoints today. Both are OpenAI-compatible in shape; every error response across both uses the RFC 7807 application/problem+json body — { type, title, status, detail?, instance? }.

GET /v1/models

No authentication required. Returns the active rows from the qos_ai_models catalog (a shared, 5-minute in-process cache — the same cache the billing path reads from, so a model priced here is priced identically when it's actually called). Responses are sent with Cache-Control: public, max-age=300.

{
  "object": "list",
  "data": [
    {
      "id": "anthropic/claude-sonnet-4.5",
      "object": "model",
      "owned_by": "anthropic",
      "context_length": 200000,
      "pricing": {
        "prompt": "0.000003",
        "completion": "0.000015"
      }
    }
  ]
}

id is <provider>/<model_id> — except OpenRouter-catalogued rows, whose model_id is already the real upstream id (e.g. anthropic/claude-sonnet-4.5), so no extra openrouter/ prefix is added. pricing.prompt / pricing.completion are per-single-token USD decimal strings (the catalog stores per-1M-token prices; this endpoint divides them down), computed with exact string/BigInt math so a stored 3.0000 always renders 0.000003, never float-corrupted. A catalog read failure returns 500 with a generic {"type":"about:blank","title":"Internal Server Error","status":500} body — no detail field, to avoid leaking query internals.

POST /v1/chat/completions

Requires the Authorization: Bearer qai_... header described above. Request order of operations, each of which can short-circuit the response: auth → rate limit → body/JSON validation → model lookup against the catalog → credits balance check → the actual model call.

FieldTypeRequiredNotes
modelstringyesMust resolve against the active qos_ai_models catalog, else 400. A stray leading openrouter/ (from a pre-fix cached id) is stripped automatically before lookup.
messagesarrayyesStandard OpenAI-shaped message objects, non-empty, passed through unmodified.
toolsarraynoOpenAI-style tool/function defs. Rejected (400) together with stream: true.
temperaturenumbernoPassed through only if it's a JS number; otherwise omitted entirely.
max_tokensnumbernoSame passthrough rule as temperature.
streambooleannotrue switches to the SSE response path described below.

Non-streaming (stream omitted or false) response, 200:

{
  "id": "chatcmpl-<uuid>",
  "object": "chat.completion",
  "created": 1735689600,
  "model": "anthropic/claude-sonnet-4.5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello from Conflux."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 5,
    "total_tokens": 17
  }
}

If the model returned tool calls, message.tool_calls is present alongside content. Response headers on a successful non-streaming call include x-conflux-cost-nanousd plus the RateLimit-* headers (see below).

Header reference

HeaderWhereMeaning
AuthorizationrequestBearer qai_... key.
x-conflux-cost-nanousd200 responseExact cost of this one call, in nano-USD (10⁻⁹ USD), derived from the catalog's per-model price × the real token counts the provider reported — never an estimate.
RateLimit-Limitevery responseBucket capacity, in whole tokens (requests).
RateLimit-Remainingevery responseWhole tokens left in the bucket after this call.
RateLimit-Resetevery responseRelative delta in seconds until the bucket refills to full capacity — not a Unix epoch timestamp.
Retry-After429 responseSeconds to wait before the bucket has at least one whole token again.
WWW-Authenticate401 responseAlways Bearer.

RateLimit-Reset here is deliberately a delta, not an absolute timestamp — a token bucket has no natural "window boundary" instant to report the way a fixed-window limiter does, so "seconds until full" is the more useful number for a client deciding how long to back off.

Streaming

Server-sent events, OpenAI framing

Set stream: true and Conflux responds with Content-Type: text/event-stream instead of a single JSON body.

Frame format

Each frame is a data: -prefixed line followed by a blank line, carrying a chat.completion.chunk object:

data: {"id":"chatcmpl-<uuid>","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.5","choices":[{"index":0,"delta":{"role":"assistant","content":"Hel"},"finish_reason":null}]}

data: {"id":"chatcmpl-<uuid>","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.5","choices":[{"index":0,"delta":{"content":"lo"},"finish_reason":null}]}

data: {"id":"chatcmpl-<uuid>","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

The first chunk carries delta.role: "assistant" alongside the first bit of content; every following content chunk carries only delta.content; the final chunk carries an empty delta and the real finish_reason. Every stream — success, upstream failure, or client abort — always ends with a terminal data: [DONE] frame.

Genuinely incremental, not buffered

The gateway reads the upstream provider's own SSE response body line-by-line as bytes arrive and forwards each parsed content delta to the response stream immediately — it isn't collected and replayed. The response also sets X-Accel-Buffering: no to discourage intermediary proxies from buffering it themselves.

Streaming + tool calls

Requesting stream: true together with a non-empty tools array is rejected up front with 400 and detail tools are not yet supported with stream: true. The SSE frame builder doesn't carry tool_calls deltas today — rather than silently streaming text and quietly dropping any tool call the model made, that combination is refused before the stream ever opens. Non-streaming tool calls work today (see the API reference above).

Client aborts

If the client disconnects mid-stream, that cancellation is bridged into an AbortController that's threaded all the way down into the upstream provider fetch() — the upstream read is actually cut short, not left running to completion against the provider for a response nobody is reading. An aborted call meters zero usage — no credits are debited and no partial cost is recorded, since the abort path never learns a token count to bill from.

Rate limiting

Per-key token bucket, backed by Postgres

Every qai_ key gets its own token bucket, currently the same defaults for every key: 60 request capacity, refilling at 60 per minute (roughly one request/second, averaged).

Why a database, not memory

/v1/* traffic can land on any serverless instance. An in-memory limiter would let each instance track its own count, so the effective limit would balloon to roughly limit × instance count. Instead the bucket state lives in one table, qos_conflux_rate_buckets (key_id, tokens_milli, capacity, refill_per_min, updated_at), keyed by the same keyId auth resolves. Token counts are stored ×1000 (tokens_milli) so refill math and the 1-token-per-request decrement stay integer arithmetic — no float rounding drift under concurrent requests.

When it runs

Rate limiting is checked after authentication (it needs the resolved key id) and before the credits balance check — a caller that's already being rate-limited shouldn't also cost a credits-balance database round trip.

On a denied request

A request with less than one whole token available gets 429 with detail Rate limit exceeded, plus the full RateLimit-* header set and a Retry-After header — the number of seconds until the bucket has refilled to at least one whole token again (not until it's merely non-negative).

Models

Served live from /v1/models

The catalog isn't a static page — call GET /v1/models and you get the current list of active models with per-token pricing, in the OpenAI list format. The rows below are illustrative examples of the shape, not a live quote — check the endpoint for current prices.

Model idOwned byContextPricing (illustrative)
anthropic/claude-sonnet-4.5anthropic200kexample only
openai/gpt-4o-miniopenai128kexample only
meta-llama/llama-3.1-70bmeta-llama128kexample only

Illustrative rows only — exact model ids, context lengths, and prices are set by the live catalog at /v1/models, not by this page.

Billing & credits

Credit-based, priced from the live catalog

Cost is computed per call, not per request — actual input/output tokens × that model's catalog price, the same price /v1/models publishes for that model.

How a call is priced

The catalog stores each model's input_cost_per_1m_usd and output_cost_per_1m_usd in qos_ai_models. After a call completes, Conflux multiplies those catalog prices by the real prompt/completion token counts the provider reported — using exact integer/BigInt math throughout, never a floating-point conversion of the catalog's decimal price strings — to get a nano-USD figure. That exact figure is what x-conflux-cost-nanousd reports on the response. It is explicitly not derived from any internal cost-estimate heuristic — billing off an unrelated estimate table was found to overcharge some models by roughly 100x their advertised catalog price during Conflux's build-out, which is why billing was rewired to read the same numbers /v1/models shows you.

Credits

Balance is a flat integer counter, qos_ai_credits.balance, defaulting to 50 for a tenant that has never been billed before (the column's own default, mirrored in code so a brand-new key's very first call is priced correctly instead of erroring). Each call's nano-USD cost is rounded up to whole US cents, then converted to credits at roughly 1 credit ≈ 1 US cent — a call that rounds to 0 cents (a genuinely free, zero-priced catalog model, or a call cheap enough to round down to nothing) debits 0 credits; anything above that debits at least 1. Every metered call also writes a row into qos_ai_usage (feature: 'conflux') so Conflux traffic shows up in the same usage dashboards as QuantumOS's internal AI features.

At zero credits

Once qos_ai_credits.balance is at or below zero, calls return 402 Payment Required with detail Insufficient credits — top up at /settings/ai/credits. That check runs after auth, rate limiting, and model validation, but strictly before any model call goes out or any SSE stream opens — you're never billed past your balance, and a 402 never has partial usage attached to it.

Topping up

The QuantumOS admin console sells credit packs for topping up a balance. Pack pricing is set in the platform's billing code today and is subject to change before Conflux's own general availability — treat these as illustrative of the mechanism, not a locked-in Conflux price list:

PackCreditsPrice (USD)
starter500$5
growth2,000$15
pro6,000$40
scale20,000$120

We're pre-launch — pricing mechanics (credits, catalog pricing, packs) are what's described here, but exact numbers are still subject to change ahead of general availability.

Errors

Error reference

Every error is application/problem+json: { type, title, status, detail?, instance? }. instance, when present, is a request id for support correlation — the underlying error (which can carry raw upstream/provider detail) is logged server-side against that id and never echoed back in the response body itself.

StatusConditionExample detailNotes
400Body isn't valid JSONRequest body must be valid JSON
400Body parses but isn't a JSON objectRequest body must be a JSON objectArray/primitive top-level bodies rejected.
400Missing/empty modelmodel is required
400Missing/empty messagesmessages is required
400stream: true with a non-empty toolstools are not yet supported with stream: trueChecked before the SSE stream opens.
400model not in the active catalogUnknown modelValidated against the same catalog /v1/models serves.
401Missing / malformed / unknown / revoked / expired keyInvalid or missing API keyWWW-Authenticate: Bearer header set.
402Credits balance ≤ 0Insufficient credits — top up at /settings/ai/creditsChecked before the model call goes out.
429Token bucket has < 1 tokenRate limit exceededRetry-After + RateLimit-* headers included.
500/v1/models catalog query throws— (no detail field)Generic about:blank/Internal Server Error body only.
502Upstream model call failed (non-streaming)Upstream model call failedReal error logged server-side; instance carries a correlatable request id.

Streaming failures are a special case: once the SSE response's 200 status line is sent it can't be downgraded, so an upstream failure mid-stream is surfaced as a final chunk with finish_reason: "error" instead of an HTTP error status — followed by the terminal data: [DONE] frame. Usage is metered at zero for that call, and the real error is still logged server-side with a request id.

FAQ

Common questions

Does it really work with the OpenAI SDK?

Yes — point the OpenAI Python (or any OpenAI-compatible) SDK at https://conflux.kynetra.dev/v1 with a Conflux qai_ key as the API key. The request and response shapes for /v1/chat/completions follow the OpenAI format, including streaming via stream: true.

What happens when my credits hit zero?

Calls return an HTTP 402 Payment Required response (RFC 7807 application/problem+json body) until you top up. You won't be billed past your balance — the credit check runs before the model call goes out.

Is streaming supported?

Yes. Set stream: true in your request and Conflux returns server-sent-event chunks in the OpenAI chat.completion.chunk format, ending with a standard [DONE] frame.

Can I use my own provider API keys (BYOK)?

Yes — bring-your-own-key lets requests route through your own provider account instead of the shared OpenRouter pool for a given model, configured from the admin console.

How is my data handled?

Requests are logged for billing and usage purposes (token counts, cost, model, timestamps) and are visible in your own workspace's usage dashboard. We're not making broader privacy or compliance claims at this stage — ask if you need specifics for your use case.

What model id do I pass?

<provider>/<model_id>, e.g. anthropic/claude-sonnet-4.5 — exactly the id field GET /v1/models returns for that model. A stray leading openrouter/ from a previously cached id (an id shape an earlier catalog version briefly advertised) is tolerated and stripped automatically before the model is resolved.

Do you support function/tool calling?

Yes, in non-streaming mode — pass an OpenAI-shaped tools array and any tool calls the model makes come back on choices[0].message.tool_calls. Combining tools with stream: true is currently rejected with a 400 (see Streaming above) rather than silently dropped mid-stream.

Can I send multiple messages or a system prompt?

Yes — messages is a standard OpenAI-shaped array of any length and any mix of roles (including system), passed through to the model unmodified.

What happens if I send a field Conflux doesn't recognize?

It's ignored. The route only reads model, messages, tools, temperature, max_tokens, and stream from the request body — there's no strict schema rejection of unrecognized extra fields today.

Is there a Conflux-specific SDK?

No, and that's by design — the whole point of the OpenAI-compatible shape is that the standard OpenAI SDKs (Python, Node, or any OpenAI-compatible client) work against Conflux unmodified once you swap the base URL and key, as shown in the quickstart.

Is there an idempotency key for safely retrying a request?

Not currently. /v1/chat/completions doesn't read an idempotency-key-style header, so retrying an identical request is billed and rate-limited as an independent call. Not yet documented as a feature because it isn't implemented.

What's the request timeout?

Not yet documented. Nothing in the chat-completions route or the underlying gateway call sets an explicit request timeout beyond whatever the runtime's own fetch defaults to — we'd rather say "not documented" than publish a number we can't point to in code.

If I abort a streaming request, am I billed for the partial output?

No. A client disconnect is bridged into an AbortController that cuts the upstream provider call short, and the aborted call is metered at zero — no credits are debited for it.