Docs

Documentation

Everything you need to build.

Silicon Network speaks the OpenAI wire format. If you already call an OpenAI-compatible API, you are a base URL away from running on a distributed network of open models.

API reference

38 routes in 10 groups, generated from the gateway's OpenAPI document (snapshot).

Quickstart

Create an API key on the API Keys page, add balance on the Billing page, and point your client at https://api.siliconnetwork.io/v1. Ask GET /v1/models which models are loaded right now; the examples use llama-3.2-3b, or the routing alias fast, which picks whichever small model is serving.

curl

silicon - zsh
export SILICON_API_KEY=sn_live_...

curl https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3.2-3b",
    "messages": [{"role": "user", "content": "Say hello in one line."}]
  }'

Python SDK

quickstart.py
# pip install siliconnetwork
from siliconnetwork import SiliconNetwork

client = SiliconNetwork()  # reads SILICON_API_KEY

out = client.chat.completions.create(
    model="llama-3.2-3b",
    messages=[{"role": "user", "content": "Say hello in one line."}],
)
print(out.choices[0].message.content)
print(out.meta.request_id, out.meta.model, out.meta.node)

TypeScript SDK

quickstart.ts
// npm install @siliconnetwork/sdk
import { SiliconNetwork } from "@siliconnetwork/sdk";

const client = new SiliconNetwork(); // reads SILICON_API_KEY

const out = await client.chat.completions.create({
  model: "llama-3.2-3b",
  messages: [{ role: "user", content: "Say hello in one line." }],
});
console.log(out.choices[0].message.content);
console.log(out.meta.requestId, out.meta.model, out.meta.node);

OpenAI SDK, unchanged

The official openai packages work with the base URL swapped. Send Silicon Network extensions (models, quality, enable_thinking, metadata.project) through extra_body in Python or as extra fields in Node.

openai_quickstart.py
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

resp = client.chat.completions.create(
    model="llama-3.2-3b",
    messages=[{"role": "user", "content": "Say hello in one line."}],
    extra_body={"models": ["fast"]},  # fall back to any fast model if this one is not loaded
)
print(resp.choices[0].message.content)
openai_quickstart.ts
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.siliconnetwork.io/v1", apiKey: process.env.SILICON_API_KEY });

const res = await client.chat.completions.create({
  model: "llama-3.2-3b",
  messages: [{ role: "user", content: "Say hello in one line." }],
});
console.log(res.choices[0].message.content);

There is no legacy /v1/completions route. A client that needs it will not work by swapping the base URL; use a chat path.

Authentication and keys

Every request that reaches a model carries an API key as a bearer token. The gateway reads Authorization: Bearer and nothing else, so a client that authenticates with x-api-key needs the header set explicitly. Keys are created on the API Keys page and shown once, at creation: only a SHA-256 hash is stored, so a lost key cannot be recovered, only replaced.

Authorization: Bearer sn_live_...

Health checks, metrics, GET /v1/models, the receipt key list and the Ollama discovery routes need no key. Everything that reaches a model does.

Key types

Credentials
PrefixWhat it is
sn_live_An API key. Belongs to one organization, is metered against its credits, and carries the policy you set on it.
sn_test_The same kind of key with a different prefix, for telling your environments apart. It is billed and served exactly like a live key: there is no sandbox tier.
snop_An operator node credential, minted for approved operators. It registers nodes; it cannot call the inference API.

The chat app and the dashboard Playground use short lived sn_live_ keys that the web server mints and holds for you. They never reach the browser and do not appear in your key list.

What you can set on a key

Everything below is optional, can be set at creation (POST /v1/keys or the dashboard form) and changed later in place (PATCH /v1/keys/{id}, where null clears a field). The secret is never regenerated by an edit. Members manage their own keys; owners and admins manage every key in the organization.

Key policy
FieldEffect
scope* (every model), or a comma separated list of model ids the key may call, matched case-insensitively. A key scoped to an alias may call every variant of it.
rate_limit_per_minRequests per minute for this key, 1 to 100,000. Default 60.
expires_in_days1 to 3,650. An expired key answers 401.
spend_cap_microsA monthly cap for this key in micro-USD (1,000,000 is $1). Past it, 402.
allowed_cidrsAn IP allowlist (see Key protection).
allowed_regions, allowed_operators, require_attestationPin the key to nodes in these regions, run by these operators, or with a current attestation. No eligible node answers 503 rather than serving elsewhere.
receiptsAttach a signed receipt to every response (see Verifiable receipts).

Revoke a key with DELETE /v1/keys/{id} or from the dashboard. The gateway caches a verified key for 15 seconds, so revocation, a new allowlist or a credit change takes effect within that window. Work that was already in flight when the key was revoked is still billed.

Key protection

Four layers stand between a leaked key and your balance: a recognisable format that scanners can find, an optional IP allowlist, anomaly alerts with optional automatic suspension, and GitHub secret scanning. The settings live in the dashboard's key protection panel on the API Keys page.

The key format

A key is sn_live_ or sn_test_, 40 random base62 characters, and a 6 character base62 checksum: 54 characters in all. The checksum is CRC32 of everything before it, written in base62 (digits, then upper case, then lower case), most significant digit first and padded to 6. A key that fails its checksum is refused without a database lookup, and anyone can match a key in text with:

key pattern
\bsn_(?:live|test)_[0-9A-Za-z]{46}\b

# keys issued before the checksum format: 48 lower case hex characters, still valid
\bsn_(?:live|test)_[0-9a-f]{48}\b

IP allowlists

Set allowed_cidrs on a key and every keyed request, including GET /v1/models with a key, must come from one of those ranges. Entries are IPv4 or IPv6 CIDRs or bare addresses (a bare address means /32 or /128), at most 64 after duplicates are merged. A range with host bits set is refused with a suggestion of the network you meant. A list that covers the whole internet (a /0, or ranges that add up to all of IPv4 or IPv6) must be confirmed with allow_any_ip: true. [] or null removes the allowlist.

PATCH /v1/keys/{id}
{
  "allowed_cidrs": ["203.0.113.0/24", "198.51.100.7", "2001:db8::/48"]
}

A request from anywhere else answers 403: client address 198.51.100.7 is not in this API key's IP allowlist. The member who created the key, or an org owner or admin, can change its allowlist (a member edits only the keys they created). The gateway reads the client address from the hop its own load balancer appended to X-Forwarded-For; entries a caller writes further left are never trusted. A batch is checked once, when it is submitted.

Anomaly alerts and suspension

Each key learns its own baseline from the last week of traffic (its first 24 hours are a learning period) and raises an alert for:

Anomalies
KindFires when
spikeThis hour's requests or tokens exceed 10 times the key's mean hour and 3 times its busiest hour (and at least 200 requests or 500,000 tokens).
distinct_ipsMore distinct client addresses in the last hour than 3 times the key's usual peak (and more than 25).
new_countryA key with settled history is used from a country it has not been used from, where the deployment has a GeoIP database.
model_mixHalf or more of at least 50 requests this hour go to models the key almost never calls.

Each kind fires at most once per key every 6 hours. Owners and admins get an email, and your webhooks get key.anomaly. Turn on automatic suspension (auto_suspend) and an anomalous key is also suspended on the spot, which sends key.suspended. Batch traffic is not judged. Client addresses are kept only as keyed hashes, for 30 days.

A suspended key answers 403 api key suspended: <reason> and is never served from the gateway's cache. Anyone who may edit a key can suspend it by hand (POST /v1/keys/{id}/suspend, with an optional reason); only an owner or admin can reinstate it (POST /v1/keys/{id}/reinstate), which sends key.reinstated.

Leaked key scanning

Silicon Network keys are registered with GitHub secret scanning. When a key turns up in a public repository, GitHub reports it to POST https://api.siliconnetwork.io/v1/keys/leaked, signed with GitHub's published key; an unsigned or forged report is refused. What happens next is your organization's leaked_action: revoke (the default) revokes the key at once, flag keeps it working and marks it leaked. Either way owners and admins get an email, your webhooks get key.leaked with the URL where it was found, and the dashboard marks the key.

key protection settings
GET  /v1/keys/security          # any member
PUT  /v1/keys/security          # owner or admin
{
  "auto_suspend": false,        # suspend a key when an anomaly fires
  "leaked_action": "revoke",    # or "flag"
  "notify_email": true          # email owners and admins
}

GET  /v1/keys/security/events?key_id=...&limit=50

Key management, including these settings, is part of the account API the dashboard calls with your signed-in session; an API key cannot manage keys. Webhook deliveries are signed (X-Silicon-Signature); set them up on the dashboard's Webhooks page.

Models, aliases and fallbacks

Pass a model in the model field: a catalog alias from the table below, or a full engine id (anything with a slash, passed through untouched, including a model an operator published on the marketplace). Aliases are case-insensitive. Every alias resolves, but only the models an operator has loaded serve: the network is machines, not a warm datacenter. Ask for one that is not loaded and you get a 404 with code: "model_not_available" and an available_models list of what is serving.

GET /v1/models needs no key and is the only live answer to "what can I call right now": an id it returns serves. It lists each loaded model with its aliases, the variants and routing aliases that can serve, approved marketplace models, and the pricing policy in pricing_policy. Add ?all=true (with a key) for the full catalog a node could load, where each card carries its kind (chat, embeddings, rerank, audio_transcription, audio_speech, image). The table holds the chat models first, then the embedding, rerank, audio and image models, each callable only on its own route.

Aliases
llama-3.2-1bLlamaTextmlx-community/Llama-3.2-1B-Instruct-4bit
llama-3.2-3bLlamaTextmlx-community/Llama-3.2-3B-Instruct-4bit
llama-3.1-8bLlamaTextmlx-community/Meta-Llama-3.1-8B-Instruct-4bit
llama-3.1-70bLlamaTextmlx-community/Meta-Llama-3.1-70B-Instruct-4bit
llama-3.3-70bLlamaTextmlx-community/Llama-3.3-70B-Instruct-4bit
deepseek-v3.1DeepSeekTextmlx-community/DeepSeek-V3.1-4bit
deepseek-v3.2DeepSeekTextmlx-community/DeepSeek-V3.2-4bit
qwen3-235bQwenTextmlx-community/Qwen3-235B-A22B-Instruct-2507-4bit
qwen3-vl-4bQwenVisionmlx-community/Qwen3-VL-4B-Instruct-4bit
kimi-k2-thinkingKimiReasoningmlx-community/Kimi-K2-Thinking
glm-4.7GLMTextmlx-community/GLM-4.7-4bit
gpt-ossOpenAI OSSTextmlx-community/gpt-oss-20b-MXFP4-Q8
gpt-oss-20bOpenAI OSSTextmlx-community/gpt-oss-20b-MXFP4-Q8
gpt-oss-120bOpenAI OSSTextmlx-community/gpt-oss-120b-MXFP4-Q8
bge-m3BGEEmbeddingsmlx-community/bge-m3-mlx-fp16
qwen3-embedding-0.6bQwenEmbeddingsmlx-community/Qwen3-Embedding-0.6B-8bit
all-minilm-l6-v2MiniLMEmbeddingsmlx-community/all-MiniLM-L6-v2-bf16
qwen3-reranker-0.6bQwenRerankmlx-community/Qwen3-Reranker-0.6B-mxfp8
qwen3-reranker-4bQwenRerankmlx-community/Qwen3-Reranker-4B-mxfp8
whisper-large-v3-turboWhisperTranscriptionmlx-community/whisper-large-v3-turbo
whisper-smallWhisperTranscriptionmlx-community/whisper-small-mlx
kokoro-82mKokoroSpeechmlx-community/Kokoro-82M-bf16
flux-schnellFLUXImageexolabs/FLUX.1-schnell-4bit
flux-devFLUXImageexolabs/FLUX.1-dev-4bit
flux-krea-devFLUXImageexolabs/FLUX.1-Krea-dev-4bit
flux-kontext-devFLUXImage editexolabs/FLUX.1-Kontext-dev-4bit
qwen-imageQwenImageexolabs/Qwen-Image-4bit
qwen-image-editQwenImage editexolabs/Qwen-Image-Edit-2509-4bit

Routing aliases: fast and best

Two names stand for an ordered list of models, and the first one that is serving answers. GET /v1/models lists a routing alias only while one of its members is available, with the chain in resolves_to and the member it would use now in serving.

Routing aliases
AliasTries, in order
fastllama-3.2-3b, llama-3.2-1b, gpt-oss-20b
bestdeepseek-v3.2, qwen3-235b, gpt-oss-120b, llama-3.3-70b
silicon - zsh
curl https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "best", "messages": [{"role": "user", "content": "Prove there are infinitely many primes."}]}'

A key scoped to fast may call its whole chain; otherwise each member must be in the key's scope.

Fallback chains

Name several models and the gateway tries them in order: model may be a list, and an OpenRouter-style models array is tried after it. Routing aliases expand in place, duplicates are dropped, and a request may name at most 5 models after that. Chains work on every JSON surface (chat, Responses, Messages, Ollama, embeddings, rerank, speech, image generation), streaming included, and never on multipart uploads.

fallback.py
from siliconnetwork import SiliconNetwork

client = SiliconNetwork()
out = client.chat.completions.create(
    model="llama-3.3-70b",
    models=["qwen3-235b", "fast"],  # tried in order when the first cannot serve
    messages=[{"role": "user", "content": "Summarise RFC 9110 in three lines."}],
)
print(out.meta.model)  # the engine id that actually answered

The next model is tried when the current one cannot serve: it is not loaded (404), no node can take it (503, including a pinned key with no eligible node, an adapter not deployed, or no node with the context), the prompt is too long for its context, the engine is unreachable or times out, or it answers 408, 429 or any 5xx. Any other 4xx is your request's fault and is answered at once, as is anything after the first byte has reached you. Scope and credit are checked for every candidate before the first attempt.

Only the attempt that answers is billed, at the served model's price. Silicon-Model names the engine id that served, and Silicon-Fallback-From (the first candidate's engine id) appears whenever a later candidate answered.

Quantization variants and quality

Some models are offered in several quantizations. The alias alone is the default, listed first below (4-bit for the chat and image models; the small embedding and audio models default to their wider weights); <alias>@<label> asks for one exactly, and only nodes serving that variant take it. Or send quality: economy (the narrowest), standard (the default) or max (the widest). quality applies to chat surfaces and to a model named by alias; an exact @label or full id is served as named. It never reaches the engine, and the image surface keeps its own OpenAI quality.

Variants
AliasLabels
llama-3.2-3b4bit, 8bit
llama-3.1-8b4bit, 8bit, bf16
llama-3.3-70b4bit, 8bit, fp16
deepseek-v3.1, deepseek-v3.24bit, 8bit
qwen3-235b4bit, 8bit
glm-4.74bit, 6bit
qwen3-embedding-0.6b8bit, 4bit
all-minilm-l6-v2bf16, 4bit
whisper-large-v3-turbofp16, 4bit
kokoro-82mbf16, 8bit
flux-schnell, flux-dev, flux-krea-dev, flux-kontext-dev4bit, 8bit, bf16
qwen-image, qwen-image-edit4bit, 8bit, bf16
variants.ts
import { SiliconNetwork } from "@siliconnetwork/sdk";

const client = new SiliconNetwork();

const exact = await client.chat.completions.create({
  model: "llama-3.1-8b@8bit",
  messages: [{ role: "user", content: "Hello" }],
});

const widest = await client.chat.completions.create({
  model: "llama-3.3-70b",
  quality: "max", // fp16 for this model
  messages: [{ role: "user", content: "Hello" }],
});

An unknown label is a 400 that lists the variants that exist, and an unknown quality is a 400 too. A variant without its own price costs its default's price scaled by bit width (8-bit 1.5 times, bf16 and fp16 2 times a 4-bit default; the table is in pricing_policy). A key scoped to the alias may call every variant and the full id the alias resolves to; a key scoped to alias@label only that one. The variant served is named in Silicon-Model, the usage record and the receipt.

Long-context tier and pricing

A text request whose prompt plus output limit is above 32,768 tokens is routed only to nodes that report enough context for that model, and is billed at 1.5 times its whole price. Billing uses the engine's actual prompt count plus the output limit forwarded. The gateway always forwards a limit on text surfaces (8,192 when you set none), so without max_tokens a request crosses the threshold once its prompt is above about 24,576 tokens; set max_tokens to what you need to keep a long prompt below it.

The gateway estimates the prompt before routing from its text (UTF-8 bytes divided by 3, plus allowances for turns, tools and images), which over-counts English and code; billing never uses the estimate. A prompt that cannot fit the model at all is a 400 that names the context and suggests a longer one. If no node can hold the request right now, the answer is a 503; with a fallback chain both move on to the next model. The threshold and multiplier are published in GET /v1/models (pricing_policy.long_context) and on the pricing page.

Context windows and parameter counts come from each model card at load time, so they are not hardcoded here.

Chat completions

POST /v1/chat/completions takes the OpenAI shape. The generated reference lists every route; this section is about what the gateway does with the body.

Parameters that work

messages, stream, stream_options, max_tokens and max_completion_tokens, temperature, top_p, top_k, min_p, stop, seed, frequency_penalty, presence_penalty, repetition_penalty, logprobs, top_logprobs, tools, tool_choice, response_format, reasoning_effort, and enable_thinking (chain of thought on thinking models, returned separately as reasoning_content). Silicon Network adds models and quality (see Models) and metadata.project (see Project tags). Fields with no value are omitted rather than null: no tool_calls key unless the model called a tool, no reasoning_content unless it thought.

Sampling parameter bounds

Sampler settings must be finite numbers in range, or the request is refused with 400 before any engine sees it. The same bounds apply on the Responses, Claude and Ollama surfaces (inside Ollama's options too) and to batch requests on those endpoints. A null is ignored.

Bounds (inclusive)
ParameterRange
temperature0 to 2
top_p, min_p0 to 1
top_kan integer from -1 to 1000 (0 and -1 mean off)
repetition_penalty, repeat_penalty0.01 to 10
presence_penalty, frequency_penalty-2 to 2
400
{"detail": "invalid sampling parameter: 'temperature' must be between 0 and 2"}

The other reasons read 'top_k' must be an integer, 'top_p' must be a number and 'min_p' must be a finite number.

Parameters that are refused

logit_bias (no per-token biasing), n above 1 (one completion per request), and parallel_tool_calls: false (calls cannot be forced to run one at a time). Nothing below the gateway reads them, so a request that depends on one is refused rather than silently doing something else:

400
{"detail": "unsupported parameter: 'n': only one completion is generated per request. Remove and retry."}

Sending the value that matches what already happens is fine: n: 1, an empty logit_bias, parallel_tool_calls: true.

Output length

A requested output length above 8,192 tokens is clamped down to 8,192, without an error, so one call cannot run up an unbounded charge. When you set no limit the gateway sends 8,192 for you. The same applies to max_output_tokens on Responses and options.num_predict on Ollama.

Streaming

Set "stream": true and the answer arrives as server-sent events: chat.completion.chunk objects with an incremental delta, then data: [DONE]. Reasoning streams as delta.reasoning_content and tool calls as delta.tool_calls[].function.arguments fragments. Responses streams its own events (response.output_text.delta, with usage on response.completed), Claude Messages streams Anthropic events (message_start, content_block_delta, message_stop), and the Ollama routes stream newline-delimited JSON, by default, as Ollama does.

silicon - zsh
curl -N https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-3b", "stream": true,
       "messages": [{"role": "user", "content": "Count to five."}]}'

: keep-alive

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"1"}}]}
...
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],
       "usage":{"prompt_tokens":14,"completion_tokens":9,"total_tokens":23,"prompt_tokens_details":{"cached_tokens":0}}}

: generation_stats {...}

data: [DONE]
stream.py
from siliconnetwork import SiliconNetwork

client = SiliconNetwork()
stream = client.chat.completions.stream(
    model="llama-3.2-3b",
    messages=[{"role": "user", "content": "Count to five."}],
)
for chunk in stream:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="", flush=True)
print()
print(stream.meta.ttft_ms, "ms to first token")
stream.ts
const stream = await client.chat.completions.create({
  model: "llama-3.2-3b",
  messages: [{ role: "user", content: "Count to five." }],
  stream: true,
});
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");

What to expect

Lines that start with a colon are SSE comments: the engine opens every stream with : keep-alive and may send : prefill_progress while it reads a long prompt, and : generation_stats at the end. Every client ignores them. The last chunk of a managed stream, the one with the finish_reason, always carries usage (the gateway turns stream_options.include_usage on, because it bills from it), so read cached tokens from the body there; Silicon-Cached-Tokens is a non-streaming header. Silicon-TTFT-Ms is set on streams. With a receipt requested, one more event follows [DONE] (see Verifiable receipts).

Closing the connection cancels generation on the node; an abandoned stream is billed a conservative floor estimated from what was sent. A fallback chain can only move to the next model before the first byte reaches you. If the serving node fails mid-answer the stream ends without [DONE] or a usage chunk, and that request is not billed: treat a stream without [DONE] as failed and retry it.

Structured outputs and tools

response_format is enforced with grammar-constrained decoding on the serving node, so the output is valid JSON (json_object) or matches your schema (json_schema with strict: true) at any temperature, not just when the model feels like it. Supported schema keywords: object, array, string, number, integer, boolean, null, enum, const, required, additionalProperties: false, nested objects and arrays, and anyOf of scalars. An unsupported keyword is refused with a 400 that names it, before any tokens are generated. The same machinery serves the Responses API (text.format), Claude Messages (tool_choice of type tool or any), and Ollama's format ("json" or a schema).

structured.py
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

resp = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Extract: Ada Lovelace, born 1815 in London."}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "born": {"type": "integer"},
                    "city": {"type": "string"},
                },
                "required": ["name", "born", "city"],
                "additionalProperties": False,
            },
        },
    },
)
print(resp.choices[0].message.content)  # {"name": "Ada Lovelace", "born": 1815, "city": "London"}

Tool calls

Pass tools and the model decides whether to call one; calls come back in tool_calls with finish_reason: "tool_calls". tool_choice: "required" makes it call some tool, and {"type": "function", "function": {"name": ...}} forces that one, with the arguments constrained to the tool's parameter schema. Forced calls work on the Llama, Qwen, GLM, Kimi, MiniMax, Gemma, gpt-oss and DeepSeek families; a model whose tool format cannot be constrained answers 400 instead of pretending.

tools.ts
import { SiliconNetwork } from "@siliconnetwork/sdk";

const client = new SiliconNetwork();
const out = await client.chat.completions.create({
  model: "llama-3.3-70b",
  messages: [{ role: "user", content: "What is the weather in Oslo?" }],
  tools: [
    {
      type: "function",
      function: {
        name: "get_weather",
        description: "Current weather for a city",
        parameters: {
          type: "object",
          properties: { city: { type: "string" } },
          required: ["city"],
          additionalProperties: false,
        },
      },
    },
  ],
  tool_choice: { type: "function", function: { name: "get_weather" } },
});
const call = out.choices[0].message.tool_calls?.[0];
console.log(call?.function.name, JSON.parse(call?.function.arguments ?? "{}"));

Send the result back as a {"role": "tool", "tool_call_id": ..., "content": ...} message and call again, exactly as with OpenAI.

Logprobs

Set logprobs: true and every generated token comes back with its log-probability and the most likely alternatives at that position, streaming and non-streaming, on every text model. Useful for classification, confidence thresholds, and eval harnesses that score tokens rather than parse text.

logprobs.py
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

resp = client.chat.completions.create(
    model="llama-3.2-3b",
    messages=[{"role": "user", "content": "Answer with one word: is 97 prime?"}],
    max_tokens=1,
    logprobs=True,
    top_logprobs=5,
)

first = resp.choices[0].logprobs.content[0]
print(first.token, first.logprob)
for alt in first.top_logprobs:
    print(alt.token, alt.logprob)

top_logprobs is passed through as you send it; the only bound is the model vocabulary, and the OpenAI limit of 20 is not enforced here. Omit it while logprobs is true and you get five alternatives per token. Each entry carries token and logprob, and every alternative also carries its UTF-8 bytes. When you stream, each chunk carries the logprobs for the token in that chunk.

Vision

qwen3-vl-4b accepts images in the standard OpenAI content-parts shape. Pass a data URL, or an https:// URL that the serving node can reach; the node fetches the URL itself, so a link only your machine can see will fail. Several images in one message are fine.

vision.py
import base64
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

with open("chart.png", "rb") as fh:
    data_url = "data:image/png;base64," + base64.b64encode(fh.read()).decode()

resp = client.chat.completions.create(
    model="qwen3-vl-4b",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What does this chart show?"},
                {"type": "image_url", "image_url": {"url": data_url}},
            ],
        }
    ],
)
print(resp.choices[0].message.content)

Responses, Claude Messages and Ollama

Same models, same key, four request shapes: OpenAI Chat Completions at /v1/chat/completions, OpenAI Responses at /v1/responses, Anthropic Messages at /v1/messages, and Ollama at /ollama/api/chat and /ollama/api/generate. The bodies are the ones those APIs define, and every surface gets the same aliases, fallbacks, bounds, refusals and output clamp.

OpenAI Responses

input, instructions, tools, stream, reasoning, max_output_tokens, and text.format for structured output. Cached tokens are in usage.input_tokens_details.cached_tokens.

responses.py
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

resp = client.responses.create(
    model="gpt-oss-120b",
    instructions="Answer in one sentence.",
    input="Why is the sky blue?",
)
print(resp.output_text)

Claude Messages

model, messages, system, max_tokens, stream, tools, tool_choice and thinking. The body is drop-in for the Anthropic SDK; the credential is not. Those clients send x-api-key, which the gateway does not read, so set the bearer header yourself.

messages.py
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.siliconnetwork.io",
    api_key="unused",  # required by the client; Silicon Network reads the header below
    default_headers={"Authorization": "Bearer sn_live_..."},
)
msg = client.messages.create(
    model="glm-4.7",
    max_tokens=512,
    messages=[{"role": "user", "content": "One sentence on trustless inference."}],
)
print(msg.content[0].text)
silicon - zsh
curl https://api.siliconnetwork.io/v1/messages \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-3b", "max_tokens": 256,
       "messages": [{"role": "user", "content": "Hello"}]}'

Ollama

For Ollama-native clients the base URL is https://api.siliconnetwork.io/ollama. Discovery (the root probe, /api/version, /api/tags, /api/ps) needs no key and lists only the models loaded right now, so a client's model picker never offers one that will not serve. Chat and generate need your key, so use a client that lets you set an Authorization header. They stream newline-delimited JSON unless you send "stream": false.

silicon - zsh
curl https://api.siliconnetwork.io/ollama/api/chat \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -d '{"model": "llama-3.2-3b", "stream": false,
       "messages": [{"role": "user", "content": "Hello"}],
       "options": {"temperature": 0.2}}'

POST /ollama/api/show and the other Ollama management routes are not served by the managed API.

Embeddings, rerank, audio and images

These models have short aliases like the chat models, resolved the same way: case-insensitive, any full id passes through, and a key scoped to an alias may call it by its full id too. Each serves only on its own route. GET /v1/models shows which are loaded now, and GET /v1/models?all=true gives each catalog card's kind. Where a model comes in several quantizations, <alias>@<label> picks one (the variants table under Models, aliases and fallbacks).

Aliases by surface
AliasKindRouteEngine id
bge-m3Embeddings/v1/embeddingsmlx-community/bge-m3-mlx-fp16
qwen3-embedding-0.6bEmbeddings/v1/embeddingsmlx-community/Qwen3-Embedding-0.6B-8bit
all-minilm-l6-v2Embeddings/v1/embeddingsmlx-community/all-MiniLM-L6-v2-bf16
qwen3-reranker-0.6bRerank/v1/rerankmlx-community/Qwen3-Reranker-0.6B-mxfp8
qwen3-reranker-4bRerank/v1/rerankmlx-community/Qwen3-Reranker-4B-mxfp8
whisper-large-v3-turboTranscription/v1/audio/transcriptionsmlx-community/whisper-large-v3-turbo
whisper-smallTranscription/v1/audio/transcriptionsmlx-community/whisper-small-mlx
kokoro-82mSpeech/v1/audio/speechmlx-community/Kokoro-82M-bf16
flux-schnellImage/v1/images/generationsexolabs/FLUX.1-schnell-4bit
flux-devImage/v1/images/generationsexolabs/FLUX.1-dev-4bit
flux-krea-devImage/v1/images/generationsexolabs/FLUX.1-Krea-dev-4bit
flux-kontext-devImage edit/v1/images/editsexolabs/FLUX.1-Kontext-dev-4bit
qwen-imageImage/v1/images/generationsexolabs/Qwen-Image-4bit
qwen-image-editImage edit/v1/images/editsexolabs/Qwen-Image-Edit-2509-4bit

Embeddings

POST /v1/embeddings, OpenAI shape: input as a string, a list of strings, or token ids; encoding_format float or base64; dimensions to truncate Matryoshka models. Billed per prompt token. Aliases: bge-m3, qwen3-embedding-0.6b, all-minilm-l6-v2.

embeddings.py
from siliconnetwork import SiliconNetwork

client = SiliconNetwork()
out = client.embeddings.create(
    model="bge-m3",
    input=["The quick brown fox", "jumps over the lazy dog"],
)
print(len(out.data), len(out.data[0].embedding))

Rerank

POST /v1/rerank in the Cohere and Jina shape: query, documents (strings or {"text": ...}), optional top_n and return_documents. Answers {model, results: [{index, relevance_score, document?}], usage}. Billed per token.

rerank.ts
const ranked = await client.rerank.create({
  model: "qwen3-reranker-0.6b",
  query: "How do I rotate an API key?",
  documents: ["Keys are shown once.", "Revoke a key and create a new one.", "Pricing is per token."],
  top_n: 2,
});
for (const r of ranked.results) console.log(r.index, r.relevance_score);

Speech to text

POST /v1/audio/transcriptions, multipart: file, model, optional language, and response_format json, text, verbose_json, srt or vtt. Whisper on MLX, billed per second of audio. Uploads are capped at 25 MB.

transcribe.py
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")
with open("meeting.m4a", "rb") as fh:
    text = client.audio.transcriptions.create(model="whisper-large-v3-turbo", file=fh)
print(text.text)

Text to speech

POST /v1/audio/speech: model, input (up to 4,096 characters), optional voice and speed (0.25 to 4), and response_format wav (the default) or pcm; mp3 only where the node has an encoder, else a 400 naming the formats it has. Kokoro on MLX, billed per input character.

silicon - zsh
curl https://api.siliconnetwork.io/v1/audio/speech \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "kokoro-82m", "input": "Hello from Silicon Network.", "response_format": "wav"}' \
  -o hello.wav

Images

POST /v1/images/generations (JSON) and POST /v1/images/edits (multipart), FLUX and Qwen image models, billed per image. Image models serve only where an operator has enabled and loaded one, so check GET /v1/models for one first. Results come back as b64_json by default.

image.py
import base64
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")
img = client.images.generate(
    model="flux-schnell",
    prompt="a lighthouse on a dusk coastline, flat illustration",
    size="1024x1024",
)
open("lighthouse.png", "wb").write(base64.b64decode(img.data[0].b64_json))

Files and batches

Work that can wait runs as a batch at half price. The OpenAI flow works unchanged: upload a JSONL file with POST /v1/files, create the batch, poll it, and download the output file.

requests.jsonl
{"custom_id": "q1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "llama-3.2-3b", "messages": [{"role": "user", "content": "Capital of France?"}]}}
{"custom_id": "q2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "llama-3.2-3b", "messages": [{"role": "user", "content": "Capital of Japan?"}]}}
batch.py
import time
from openai import OpenAI

client = OpenAI(base_url="https://api.siliconnetwork.io/v1", api_key="sn_live_...")

f = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")
batch = client.batches.create(
    input_file_id=f.id,
    endpoint="/v1/chat/completions",
    completion_window="24h",
    metadata={"job": "capitals"},
)
while batch.status not in ("completed", "failed", "cancelled", "expired"):
    time.sleep(30)
    batch = client.batches.retrieve(batch.id)

if batch.output_file_id:
    for line in client.files.content(batch.output_file_id).text.splitlines():
        print(line)  # {"id", "custom_id", "response": {"status_code", "body"}, "error"}
silicon - zsh
curl https://api.siliconnetwork.io/v1/files -H "Authorization: Bearer $SILICON_API_KEY" \
  -F purpose=batch -F file=@requests.jsonl

curl https://api.siliconnetwork.io/v1/batches -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input_file_id": "file-...", "endpoint": "/v1/chat/completions", "completion_window": "24h"}'

curl https://api.siliconnetwork.io/v1/batches/batch_... -H "Authorization: Bearer $SILICON_API_KEY"

Rules

Batches and files
WhatRule
endpoint/v1/chat/completions or /v1/embeddings, the same url on every line.
completion_window24h.
SizeUp to 10,000 requests and 32 MB per batch or file; an organization's live uploads at most 256 MB together.
Each lineValid JSON, a unique custom_id (at most 200 characters), method POST, and an object body whose model your key may use. A bad line is a 400 naming its line number (403 for a model outside the key's scope).
Inline formInstead of input_file_id, send requests: [{custom_id, method, url, body}] in the create body (never both).
Statusesvalidating, in_progress, completed, failed, cancelling, cancelled, expired.
ResultsGET /v1/files/{output_file_id}/content, or GET /v1/batches/{id}/output and /errors. The error file holds only the failed requests.
PriceHalf the interactive price. Batch requests run when interactive traffic allows and use the key's rate limit with a reserve kept for interactive calls.
OwnershipFiles belong to the organization: any of its keys can use them, and no other organization can see that they exist (404).

An upload may set expires_after[anchor]=created_at and expires_after[seconds] (3,600 to 2,592,000); otherwise it is kept 30 days. Send X-Silicon-No-Retention: true with the upload and it is deleted as soon as a batch is created from it, or after an hour if none is. Batch requests never carry receipts. What is stored, and for how long, is under Privacy.

LoRA adapters

Serve your own fine-tune on top of a base model without hosting it: register a LoRA adapter, deploy it to your nodes, and call it by appending its id to the base model.

Register

Adapters are managed on the account API with your signed-in session (owners and admins write, every member reads); there is no dashboard screen for them yet. Register from the Hugging Face Hub, or upload the files:

POST /v1/adapters
{
  "name": "support-tone",
  "base_model": "mlx-community/Llama-3.2-3B-Instruct-4bit",
  "hf_repo": "your-org/support-tone-lora",
  "revision": "main"
}

# or POST /v1/adapters/upload, multipart:
#   name, base_model, config=@adapter_config.json, weights=@adapters.safetensors

The answer carries the id (adp_ and 32 hex characters), the callable model string, and the rank, scale and target modules read from the files. Register with the base model's full engine id.

What is accepted
RuleLimit
Formatmlx_lm LoRA: adapter_config.json plus adapters.safetensors. PEFT, DoRA and pickle files (.bin, .pt, .npz) are refused.
Rank and scaleRank 1 to 64; scale (or alpha over rank) above 0 and at most 64.
SizeConfig at most 64 KB; weights at most 256 MB from the Hub or 64 MB uploaded.
Per organization50 live adapters and 256 MB of uploads.
Base modelIf the config names the model it was trained on, it must match base_model.
Not yetSharded (multi-node) base models, mixture-of-experts layers, embedding targets.

Deploy

POST /v1/adapters/{id}/deploy installs it on your organization's ready single-node deployments of the base model, in the background (each install goes pending to ready or failed; GET /v1/adapters/{id} shows them). With no such deployment it answers 409, unless you send {"place_base": true} to place the base model first. PATCH renames it or sets visibility to public so other organizations can deploy and call it (never change it); DELETE uninstalls it and deletes the uploaded files.

Call

Put <base>:<adapter id> in model. The base may be its alias; the gateway resolves it and keeps the suffix.

silicon - zsh
curl https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-3b:adp_0123456789abcdef0123456789abcdef",
       "messages": [{"role": "user", "content": "My order is late."}]}'

A key scoped to the base model may call its adapters. Adapter requests serve only on nodes with the adapter installed, never on the platform's own engine, and are text generation only. They cost the base model's price plus the adapter's surcharge (zero unless the platform sets one), and the usage export names the adapter_id.

Errors
StatusMeaning
404adapter 'adp_...' not found: unknown, or another organization's private adapter.
400The adapter is for a different base model; the message says how to call it.
503Not deployed on any online node (a fallback chain moves on), or the adapter registry is unreachable (retry).

The uploaded config and weights are stored until you delete the adapter and are copied to each node it is deployed on, where that node's operator can read them.

Project tags and the cost explorer

Label a request with a project and the dashboard's cost explorer breaks spend down by it. Send "metadata": {"project": "search-api"} in the body of any JSON surface, or a Silicon-Project: search-api header on any surface (the only way on multipart audio and image uploads; X-Silicon-Project works too).

silicon - zsh
curl https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Silicon-Project: search-api" \
  -d '{"model": "fast", "messages": [{"role": "user", "content": "Hello"}]}'
project.py
from siliconnetwork import SiliconNetwork

client = SiliconNetwork()
client.chat.completions.create(
    model="fast",
    messages=[{"role": "user", "content": "Hello"}],
    metadata={"project": "search-api"},
)

The tag is lowercased and must be 1 to 64 characters of letters, digits, ., _ and -, starting with a letter or digit. Anything else, or a body and header that disagree, is a 400 before the request is admitted. The gateway removes the tag before forwarding (other metadata keys pass through) and records it on the usage event. It is a grouping label: do not put names, emails or user ids in it.

The cost explorer

Owners, admins and billing members see spend, tokens and requests grouped by one or two of day, key, model and project, over 7, 30 or 90 days or a custom range (up to 366 days, UTC days), filtered by key, model or project (- selects untagged traffic), and can export the same aggregate as CSV. The CSV has the dimension columns (a key expands to its id, name and prefix) and then requests, errors, prompt_tokens, completion_tokens, cached_tokens, tokens, units and cost_micros (micro-USD).

Prefix caching

The engine keeps the attention state of recent prompts. When a new request starts with a prefix it has already processed (a long system prompt, a document, the earlier turns of a conversation), those prompt tokens are served from cache: the answer starts sooner and those tokens bill at a quarter of the prompt rate unless a model sets its own cached price.

You do not have to do anything to get it, but you can help it: put the parts that repeat first (system prompt, tools, documents) and the parts that change last. The gateway also steers requests from your organization that share a long prefix (about 1 KB or more) to the node that served it within the last ten minutes, as a preference, when that node is eligible and not busy. The cache is partitioned per organization, so a hit only ever comes from your own earlier requests, and a request sent with X-Silicon-No-Retention: true is never cached.

Where cached tokens are reported
SurfaceField
Chat completionsusage.prompt_tokens_details.cached_tokens
Responsesusage.input_tokens_details.cached_tokens
Non-streaming responsesthe Silicon-Cached-Tokens header, when above zero
SDKsmeta.cached_tokens (Python), meta.cachedTokens (TypeScript)

Claude Messages and Ollama responses have no cached-token field, so they report none, though the cache still works for them.

Headers, rate limits and spend caps

Response headers
HeaderMeaning
Silicon-Request-Id, x-request-idThe same id under both names, minted by the gateway and forwarded to the serving node. Quote it in support requests and disputes. Not set on a 401, which is answered before an id exists.
Silicon-ModelThe full engine id that served the request (aliases, variants and casing resolved). Successes only.
Silicon-Fallback-FromThe first candidate's engine id, when a later model in a fallback chain answered.
Silicon-NodeThe registered node that served. Absent when the platform's own engine served.
Silicon-TTFT-MsTime to first token, on streams.
Silicon-Cached-TokensPrompt tokens served from the prefix cache, on non-streaming responses.
Silicon-Attestationsha256=<model hash>;operator=<org>;egress=locked|open, when the serving node has a current attestation.
Silicon-ReceiptThe signed receipt (base64url JSON), when you asked for one. On a stream it is a final event instead.
x-ratelimit-limit, -remaining, -resetThe key's requests per minute, what is left, and seconds until the budget is whole. Omitted when no limit was enforced.
Retry-AfterSeconds to wait, on 429 and on a draining 503.

Request headers you can send

Request headers
HeaderEffect
Authorization: BearerYour API key. The only credential the gateway reads.
Silicon-Receipt1 (or true, yes, on) asks for a signed receipt; 0 (or false, no, off) skips one a key would otherwise get.
X-Silicon-No-Retentiontrue: no verification sampling, and the engine keeps nothing of the request (see Privacy).
Silicon-ProjectA project tag for the cost explorer (X-Silicon-Project also works).

The gateway mints its own request id; it does not read one from the caller. Keep your own correlation id on your side, next to the Silicon-Request-Id you get back.

Rate limits

Each key has a token bucket of 60 requests per minute by default, adjustable per key (rate_limit_per_min). The bucket starts full, so a whole minute's budget can be spent in a burst, and refills continuously. Over the budget, the answer is 429 rate limit exceeded with Retry-After. Inference routes, files, batches and a keyed GET /v1/models all draw from it; batch work leaves a quarter of it for interactive calls.

Spend caps

Set a monthly cap for the organization on the Billing page, and optionally a cap per key (spend_cap_micros, over a rolling 30 days). Once reached, requests answer 402 with monthly spend cap reached or API key spend cap reached; an empty balance answers insufficient credits. Webhooks warn you first: spend.threshold at 50, 80 and 100 percent of the organization cap, and credits.low when the balance drops below your threshold ($5 by default). Caps and balance changes reach the gateway within 15 seconds.

Errors

An error the gateway raises is JSON with a detail string. Two have an OpenAI-style error object as well: a model that is not loaded, and a gateway that is draining for a deploy. An error from the engine is passed through as the engine wrote it. Every error carries the request id header except a 401.

404 model_not_available
{"error": {"message": "No engine is currently serving model 'llama-3.3-70b' (full id 'mlx-community/Llama-3.3-70B-Instruct-4bit'). ...",
           "type": "not_found", "code": "model_not_available", "param": "model",
           "available_models": ["mlx-community/Llama-3.2-3B-Instruct-4bit"]}}
Status codes
StatusMeaning
400A parameter the gateway cannot honor, a sampler setting out of range, a bad model list, variant, quality or project tag, a prompt too long for the model, an unsupported schema keyword, or a malformed body. The detail names it.
401missing bearer API key, or invalid or revoked API key (expired keys included).
402insufficient credits, monthly spend cap reached, or API key spend cap reached.
403The key's scope does not include the model; the client address is outside the key's IP allowlist; or the key is suspended.
404model_not_available (with available_models), an unknown adapter, or an unknown file or batch.
413A body over the limit: 64 MB for JSON, 25 MB for uploads, 32 MB for a batch file.
429rate limit exceeded. Honor Retry-After.
502The engine was unreachable, timed out, dropped the connection mid-response, or answered too large. Not billed; safe to retry.
503No eligible node for a pinned key, no node with the context the request needs, an adapter not deployed, the control plane unreachable for a key it has not seen recently, or a draining instance (code draining, Retry-After: 1).

With a fallback chain, 404, 408, 429, every 5xx and the too-long-prompt 400 move on to the next model instead of being returned (see Models). Retry 429, 502 and 503 with backoff; the SDKs do it for you on non-streaming calls.

Privacy and retention

Requests run on an operator's machine, so that node necessarily processes your prompt, and unless you say otherwise it also retains it. Here is exactly who stores what. The privacy policy says the same in plain terms.

Who stores what
WhereWhat is kept
Serving nodePrompts and completions, in the engine's event log on the operator's disk, until the log rotates away; the engine also serves that log on its own API (open unless the operator sets a node key). Not with X-Silicon-No-Retention: true.
GatewayNothing persisted. The body is held in memory to check your key, resolve the model, forward the request and meter the answer.
Usage recordOrganization, key, model id, token counts, latency, status, region, cost, project tag and timestamp. No field for prompt or completion text.
Batches and filesUploaded batch files and a batch's requests and responses, for a bounded time (below).
Chat appConversations you save, encrypted, for the period you choose (below). Temporary chats: nothing.
LoRA adaptersThe config and weights you upload, until you delete the adapter, and a copy on each node it is deployed on.
Key protectionClient addresses seen per key, only as keyed hashes, for 30 days, to detect anomalies.
ReceiptsNot stored. You get the only copy.

Batches and files

A batch input file is deleted at its expiry (30 days by default, or the expires_after you set) or when you delete it. A batch's requests and responses are deleted 30 days after it finishes, or as soon as you delete its output and error files. An upload sent with X-Silicon-No-Retention: true is deleted as soon as a batch is created from it, or an hour after upload if none is. After that, a batch keeps its status, counts, timestamps, metadata and each request's custom_id, and no bodies.

The chat app

A conversation saved in /chat (title, system prompt, and each message with its attachments) is encrypted at rest and readable only by you, in the organization you started it in. It is deleted 1, 7, 30, 90 or 365 days after its last message, as you choose (30 by default), or when you delete it, delete all your chats, or delete your account. Model ids, token counts, cost and timestamps are stored in the clear next to it. A temporary chat stores nothing on our side and sends X-Silicon-No-Retention: true with every request. Chat requests are ordinary metered requests on a short lived key the web server holds for you; each person's chat spend is capped at $5 per rolling 24 hours by default.

What can widen the gateway's view

Two features are off unless the platform turns them on: content moderation, which sends prompt text to a moderation service before serving, and verification sampling, which re-runs a sampled request on the platform's own engine to check the node that served it.

X-Silicon-No-Retention

Send X-Silicon-No-Retention: true and the gateway skips verification sampling for that request, so your prompt is never re-sent to a second engine, and forwards the header to the serving node. The engine honors it on every surface (chat, completions, responses, embeddings, rerank, speech, transcription and images): it redacts your prompt and the output from its event log, its API logs and its event feed, and never writes the request to its prefix cache. A generated image is held in memory only, for up to an hour.

silicon - zsh
curl https://api.siliconnetwork.io/v1/chat/completions \
  -H "Authorization: Bearer $SILICON_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Silicon-No-Retention: true" \
  -d '{"model": "fast", "messages": [{"role": "user", "content": "Draft a reply to this customer email: ..."}]}'

This is enforced by the engine software on the node, so it binds a node that runs that software unmodified. Only operators the platform has approved can register a node into the routable pool. For confidentiality that does not depend on someone else's machine, pin your key to approved operators and attested nodes (the attestation comes back in Silicon-Attestation), or run your own node, where your machine is the whole data path.

Verifiable receipts

Send Silicon-Receipt: 1 (or turn receipts on for a key) and a successful response carries a signed receipt: the serving node signs the hashes of the request it received and the answer it produced, with the key it attests its model with, and the gateway countersigns which node served it, its operator, region and attestation, and the hashes of what you sent and got. Hashes and counts only, never content. It arrives as the Silicon-Receipt header, or on a stream as a final silicon.receipt event after [DONE].

receipt.py
import httpx
from siliconnetwork import fetch_receipt_keys, verify_receipt

body = b'{"model": "fast", "messages": [{"role": "user", "content": "Hi"}]}'
resp = httpx.post(
    "https://api.siliconnetwork.io/v1/chat/completions",
    content=body,
    headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json",
             "Silicon-Receipt": "1"},
)
result = verify_receipt(
    resp.headers["silicon-receipt"], fetch_receipt_keys(),
    request_body=body, response_body=resp.content,
)
print(result.valid, result.bound)  # bound: the receipt is about these exact bytes

valid means every signature and cross-check passed; bound means, in addition, that the hashes match the bytes you passed in, which is what you want when a receipt is your proof of an exchange. Platform keys are published at GET /v1/receipts/keys; a retired key stays listed with the time it stopped signing (not_after), so old receipts still verify and a receipt it signed later does not. Read the receipts guide for the format, streams, the command line, and exactly what a receipt does and does not prove.

Pricing

You pay per token for the model you actually call, metered from real requests. Prompt tokens served from the prefix cache bill at a quarter of the prompt rate, batch requests at half price, requests in the long-context tier at 1.5 times, and higher-precision variants scale with bit width. Audio bills per second, speech per character, images per image. A model with no published row bills at $0.50 per million prompt tokens and $1.50 per million completion tokens. There are no seats and no idle clusters to rent, so the meter only runs when you do.

Per-model rates are on the pricing page and the public GET /v1/billing/pricing and /v1/billing/pricing/policy routes of the account API.

Billing

Billing is prepaid credit in US dollars, held in micro-USD. Top up with stablecoins or a card on the Billing page (owners, admins and billing members) under Add balance. One USDT or USDC buys one dollar of credit. A top-up fee, when one applies, is shown before you pay; you send the gross amount and are credited the net.

Money flowCallers top up with stablecoins, USDT or USDC on Arbitrum One, USDC on Base, or USDT on Tron, or with a card through Privy, which buys USDC on Base and pays the same way. A watcher credits each transfer once it is final, after a second provider re-checks it on chain, less the top-up fee. The balance is prepaid credit in US dollars, with organization and per-key spend caps. Credit is spent per token and per model, metered by the gateway. The operator side is built and switched off: an operator's share would be escrowed, then released or slashed, and released earnings become operator credits spendable on inference, with no cash-out. No token is part of this flow.StablecoinsUSDT, USDC on Arbitrum OneUSDC on BaseUSDT on TronCard, through Privybuys USDC on Base,then pays like a walletWatchercredits at finality,re-checked on chain,less the top-up feeCreditsprepaid, in USD;org and keyspend capsSpendper token, per model,metered bythe gatewayOperator earningsbuilt, switched off; the go/no-go reads no-goOFFOperator shareescrowed, thenreleased or slashedOperator creditsspent on inference,no cash-outNo token is part of this flow.Callers are billed on what the gatewaycounted, never on a node's own claim.

Stablecoins

Accepted
NetworkTokenCredited after
Arbitrum OneUSDT, USDCthe block is finalized, usually 15 to 20 minutes
BaseUSDCthe block is finalized, usually 15 to 20 minutes
TronUSDT (TRC-20)the block is solidified, about a minute

First bind the wallet you will pay from, then send the token to the receive address the dashboard shows for that network, from that wallet. Send only the listed token on the listed network: anything else cannot be credited. A transfer is matched to your organization by the sending address, so it must come from the bound wallet; a withdrawal straight from an exchange cannot be matched, so withdraw to your own wallet first. The dashboard updates by itself when the transfer is final. Each transfer is credited once, and the control plane checks it on chain before crediting. A single top-up may be up to $100,000.

Binding a wallet

Binding proves the wallet is yours with a signature; it authorizes no transaction and moves no funds. One EVM wallet covers Arbitrum and Base (signed with personal_sign), and a Tron wallet is bound separately (signed with TronLink's signMessageV2). An address can pay for only one organization; binding again replaces the old wallet. The message you sign names the site, your organization and the wallet family, and expires after 15 minutes:

bind message
www.siliconnetwork.io wants you to link your wallet to a Silicon Network org.

Only sign this on www.siliconnetwork.io. Payments from this wallet will credit the org named below. Signing authorizes no transaction and moves no funds.

URI: https://www.siliconnetwork.io
Org: <your org name> (<org id>)
Wallet: EVM (Ethereum compatible chains)
Nonce: <32 hex characters>
Issued At: 2026-10-03T12:00:00Z
Expiration Time: 2026-10-03T12:15:00Z

The site in the message is the one you are signing on. Do not sign a binding message that names another site or an organization you do not recognize. Renaming the organization invalidates an unsigned challenge.

Card

Pay with card signs you in to Privy with your email, which creates an embedded wallet linked to your organization. The card provider buys USDC on Base into that wallet, and the dashboard sends it to the receive address with the network fee sponsored, so it is credited like any other USDC top-up, once final. The provider applies its own limits and may add its own fee at checkout; USDC left in the embedded wallet can be sent to your balance later.

Webhooks and the API

credits.topup fires with the gross, fee and credited amounts and the transaction hash. The account API answers your balance, monthly spend, cap and recent transactions at GET /v1/billing/credits, and the accepted networks, tokens, receive addresses and bound wallets at GET /v1/billing/status.

Status

The status page probes the inference API, the control plane and the relay on every visit and refreshes every 30 seconds. Below the live checks it shows 90 days of uptime per component (from checks every minute; stablecoin top-ups are a component too) and the incident history. An incident opens automatically after five minutes of a component down or degraded, and resolves after five minutes of recovery; updates are posted as it progresses.

Feeds
URLFormat
/status/rss.xmlRSS 2.0, the last 50 incidents.
/status/feed.jsonJSON Feed 1.1, the same items, tagged with status, impact and components.

An item's id changes with every update, so a feed reader shows each update as new. The control plane serves the same data as JSON at GET /v1/status and GET /v1/status/incidents (public, cached for 30 seconds). For your own probes, GET https://api.siliconnetwork.io/health answers whether inference can be served right now (200 with the number of warm models, or 503 degraded or unhealthy) and /livez whether the process is up.

SDKs

Two first-party clients, thin and typed, with no runtime dependencies beyond httpx and pydantic (Python) or fetch (TypeScript). The official openai packages also work with the base URL set.

silicon - zsh
pip install siliconnetwork            # Python 3.10+
pip install "siliconnetwork[receipts]"  # adds signature checks and silicon-verify-receipt
npm install @siliconnetwork/sdk       # Node 20+ (receipt checks use WebCrypto Ed25519), Bun, Deno, browsers

If a package is not on its registry yet, install it from the repository: pip install "git+https://github.com/ekempinski/siliconnetwork#subdirectory=sdks/python", or clone it and npm install ./sdks/typescript.

What the clients cover
CallPython / TypeScript
Chatchat.completions.create(...), chat.completions.stream(...)
Modelsmodels.list()
Embeddingsembeddings.create(model, input, ...)
Rerankrerank.create(model, query, documents, ...)
Any JSON routerequest(method, path, body): Responses, Messages, Ollama, speech, image generation, batches. Multipart uploads (files, transcription, image edits) need the openai package or a plain HTTP client.
Receiptsfetch_receipt_keys / fetchReceiptKeys, verify_receipt / verifyReceipt, split_stream_receipt / splitStreamReceipt

Every result carries the gateway headers as meta: request id, serving node, resolved model, cached tokens, time to first token, the rate-limit budget, and the receipt when there is one. Failures raise SiliconError with status, code, the request id and retry_after, plus is_rate_limited and is_out_of_credits. Non-streaming calls retry 429 and 5xx twice by default, honoring Retry-After. Both read SILICON_API_KEY, and SILICON_BASE_URL as an origin without /v1 (default https://api.siliconnetwork.io).

Python

sdk.py
from siliconnetwork import SiliconNetwork, SiliconError

client = SiliconNetwork(
    default_headers={"Silicon-Project": "checkout", "X-Silicon-No-Retention": "true"},
)
try:
    out = client.chat.completions.create(
        model="fast",
        messages=[{"role": "user", "content": "Hello"}],
    )
    print(out.choices[0].message.content, out.meta.request_id, out.meta.cached_tokens)
except SiliconError as err:
    print(err.status, err.code, err.request_id)

# routes without a typed method
data, meta = client.request("POST", "/v1/responses", {"model": "fast", "input": "Hello"})

AsyncSiliconNetwork has the same surface with await. Constructor options: api_key, base_url, timeout (seconds, 120), max_retries (2), default_headers.

TypeScript

sdk.ts
import { SiliconNetwork, SiliconError } from "@siliconnetwork/sdk";

const client = new SiliconNetwork({ defaultHeaders: { "Silicon-Project": "checkout" } });

try {
  const out = await client.chat.completions.create(
    { model: "fast", messages: [{ role: "user", content: "Hello" }] },
    { headers: { "X-Silicon-No-Retention": "true" }, timeout: 30_000 },
  );
  console.log(out.choices[0].message.content, out.meta.requestId, out.meta.cachedTokens);
} catch (err) {
  if (err instanceof SiliconError) console.log(err.status, err.code, err.requestId);
}

Options: apiKey, baseURL, timeout (milliseconds, 120,000), maxRetries (2), defaultHeaders, and your own fetch. Every call takes { signal, headers, timeout } as its second argument.

Receipts in the SDKs

The TypeScript client puts the raw Silicon-Receipt header on meta.receipt; the Python client puts the decoded receipt there, and on a non-streaming call with a receipt adds meta.request_sha256 and meta.response_sha256 of the exact bytes, which verify_receipt takes in place of the bodies. The stream iterators skip the receipt event, so to verify a streamed answer read the raw bytes and split it off with split_stream_receipt. Both verifiers return valid, bound, the list of checks and the errors, honor retired key windows, and require the gateway signature unless you pass require_gateway=False (requireGateway: false). See Verifiable receipts.

receipt.py
from siliconnetwork import SiliconNetwork, fetch_receipt_keys, verify_receipt

client = SiliconNetwork(default_headers={"Silicon-Receipt": "1"})
out = client.chat.completions.create(model="fast", messages=[{"role": "user", "content": "Hi"}])
result = verify_receipt(
    out.meta.receipt, fetch_receipt_keys(),
    request_sha256=out.meta.request_sha256, response_sha256=out.meta.response_sha256,
)
print(result.valid, result.bound, result.errors)

Integrations

Every framework that speaks /v1/chat/completions works with a base URL swap. These packages and configs add the catalog, key lookup and Silicon Network extras on top. Note that the framework packages read SILICON_BASE_URL with /v1, unlike the SDKs.

Packages
FrameworkInstall
LangChainpip install langchain-siliconnetwork
LlamaIndexpip install llama-index-llms-siliconnetwork
Vercel AI SDKnpm install @siliconnetwork/ai-provider ai
LiteLLMpip install litellm-siliconnetwork
n8nn8n-nodes-siliconnetwork (community node)
Continue, Open WebUI, Zapierconfig and app files in the repository

LangChain

langchain.py
from langchain_siliconnetwork import ChatSiliconNetwork, SiliconNetworkEmbeddings

llm = ChatSiliconNetwork(model="llama-3.2-3b")  # reads SILICON_API_KEY
print(llm.invoke("Hello").content)

emb = SiliconNetworkEmbeddings(model="bge-m3")
print(len(emb.embed_query("Hello")))

ChatSiliconNetwork is a ChatOpenAI that checks the model against the catalog; pass validate_model_name=False for a routing alias, a variant or an adapter. Plain langchain_openai.ChatOpenAI with base_url="https://api.siliconnetwork.io/v1" works too.

LlamaIndex

llamaindex.py
from llama_index.core import Settings
from llama_index_siliconnetwork import SiliconNetwork, SiliconNetworkEmbedding

Settings.llm = SiliconNetwork(model="llama-3.2-3b")
Settings.embed_model = SiliconNetworkEmbedding(model="bge-m3")
print(Settings.llm.complete("Hello"))

# or the generic OpenAI-like class; is_chat_model=True is required, or the
# client calls the legacy /v1/completions route, which is not served.
from llama_index.llms.openai_like import OpenAILike
llm = OpenAILike(model="llama-3.2-3b", api_base="https://api.siliconnetwork.io/v1", api_key="sn_live_...", is_chat_model=True)

Vercel AI SDK

ai-sdk.ts
import { createSiliconNetwork } from "@siliconnetwork/ai-provider";
import { generateText } from "ai";

const silicon = createSiliconNetwork({
  apiKey: process.env.SILICON_API_KEY,
  headers: { "Silicon-Project": "web" },
});
const { text } = await generateText({ model: silicon("llama-3.2-3b"), prompt: "Hello" });
console.log(text);

silicon.textEmbeddingModel(id) gives embeddings. With @ai-sdk/openai-compatible directly, use createOpenAICompatible with the base URL. With @ai-sdk/openai, take the chat model explicitly (openai.chat("llama-3.2-3b")), because that provider's default targets OpenAI's Responses format.

Continue

~/.continue/config.yaml
models:
  - name: Silicon Network
    provider: openai
    model: llama-3.3-70b
    apiBase: https://api.siliconnetwork.io/v1
    apiKey: ${{ secrets.SILICON_API_KEY }}
    roles: [chat, edit, apply]

The full config in integrations/continue adds an autocomplete model and embeddings. Cursor: Settings, Models, add your key, override the OpenAI base URL with https://api.siliconnetwork.io/v1, and add a model id by hand. Aider: set OPENAI_API_BASE=https://api.siliconnetwork.io/v1 and OPENAI_API_KEY to your key, then run aider --model openai/llama-3.2-3b.

Open WebUI

Admin Panel, Settings, Connections, add an OpenAI API connection with the URL https://api.siliconnetwork.io/v1, your key, and the prefix ID silicon. With Model IDs left empty, Open WebUI fills its picker from GET /v1/models, which lists every loaded model, embedding and rerank models included; list the chat models under Model IDs to keep the picker to chat. The connection's Headers field can carry a project tag. Or import the whole setup (connection, filtered model list, a fast task model for titles, embeddings for documents) from integrations/openwebui under Admin Panel, Settings, Database, Import Config.

connection.json
{
  "openai.api_base_urls": ["https://api.siliconnetwork.io/v1"],
  "openai.api_keys": ["sn_live_..."],
  "openai.api_configs": {
    "0": {
      "enable": true,
      "prefix_id": "silicon",
      "model_ids": ["llama-3.2-3b", "llama-3.3-70b", "gpt-oss-120b"],
      "headers": { "Silicon-Project": "open-webui" }
    }
  },
  "task.model.external": "silicon.llama-3.2-3b",
  "rag.embedding_engine": "openai",
  "rag.embedding_model": "bge-m3",
  "rag.openai.api_base_url": "https://api.siliconnetwork.io/v1",
  "rag.openai.api_key": "sn_live_..."
}

Importing replaces Open WebUI's whole list of OpenAI connections; merge with an export first if you have others. The optional Silicon Network function (silicon_network_pipe.py, pasted under Admin Panel, Functions) lists only the chat models loaded right now, shows the serving node and a receipt check under each answer, and adds per-user keys, project tags and a no-retention switch. An Ollama connection works too: point it at https://api.siliconnetwork.io/ollama.

LiteLLM

The proxy reaches Silicon Network through its OpenAI-compatible route; each model is exposed as silicon/<alias>.

config.yaml
model_list:
  - model_name: silicon/llama-3.2-3b
    litellm_params:
      model: openai/llama-3.2-3b
      api_base: https://api.siliconnetwork.io/v1
      api_key: os.environ/SILICON_API_KEY
      extra_headers: { Silicon-Project: litellm }
  - model_name: silicon/*
    litellm_params:
      model: openai/*
      api_base: https://api.siliconnetwork.io/v1
      api_key: os.environ/SILICON_API_KEY

# SILICON_API_KEY=sn_live_... litellm --config config.yaml

In Python, litellm-siliconnetwork (tested with LiteLLM 1.90 to 1.103) registers a silicon/ provider for chat, streaming, async and embeddings, and maps the public price list onto every alias, so litellm.completion_cost and the proxy's spend logs use Silicon Network prices. Batch requests bill at half price, which LiteLLM does not know about. It reads SILICON_API_KEY and SILICON_API_BASE.

litellm_silicon.py
import litellm
import litellm_siliconnetwork

litellm_siliconnetwork.register(project="checkout")  # key from SILICON_API_KEY
litellm_siliconnetwork.register_costs()              # prices from the pricing endpoint

resp = litellm.completion(
    model="silicon/llama-3.2-3b",
    messages=[{"role": "user", "content": "Hello"}],
)
print(litellm.completion_cost(resp))

# model_info blocks with current prices, for the proxy config:
# python -m litellm_siliconnetwork.pricing --format yaml

n8n

Install the community node n8n-nodes-siliconnetwork (Settings, Community Nodes, Install), then create a Silicon Network API credential with your key; the base URL defaults to https://api.siliconnetwork.io/v1. The Silicon Network node does chat completions (prompt or message list, JSON output, a receipt, no-retention, a project tag), embeddings, the live model list filtered by kind, and batches: create one from the incoming items or from JSON, and fetch its status and parsed output. A requested receipt is decoded into the output, not verified.

self-hosted n8n - zsh
mkdir -p ~/.n8n/custom && cd ~/.n8n/custom
npm install n8n-nodes-siliconnetwork
# restart n8n; the node appears as "Silicon Network"

Zapier

The Silicon Network Zapier app has two actions, Generate Text and Create Embedding, each with an optional project tag, and a polling trigger, Batch Completed, that fires once per finished batch with its counts and output link. It authenticates with your API key (and an optional base URL). Until it is listed in Zapier's app directory, deploy it to your own Zapier account from integrations/zapier and invite your team.

silicon - zsh
cd integrations/zapier && npm install && npm test
npx zapier-platform login
npx zapier-platform register "Silicon Network"   # once
npx zapier-platform push

The Zapier embedding action defaults to the bge-m3 alias; any other embedding alias (qwen3-embedding-0.6b, all-minilm-l6-v2) or full id works too.

Run a node

A node runs the Silicon Network engine on Apple Silicon, loads models, and serves what the gateway routes to it. Approved operators join from the menu bar app or the engine dashboard with a device sign-in, serve from behind NAT through the relay, choose the models they offer, and accrue credit for verified work (payouts are off today).

silicon - zsh
SILICON_API_KEY=choose-a-long-secret uv run --extra mlx silicon
# then open http://localhost:52415/network and choose Sign in and join

Read the operator guide for installation, device sign-in, the relay, offered models, attestation, verification and escrow, and updates.

Architecture

Four parts: the engine on each node, the gateway that authenticates, routes and meters requests, the control plane that holds accounts, keys, usage, billing and node state, and the relay that lets nodes anywhere on the internet serve without opening a port. The full design is in the litepaper.

ArchitectureA caller sends an HTTPS request to the gateway at api.siliconnetwork.io, the only public entry point. The gateway asks the control plane to authenticate the key, schedule a node and record usage. It forwards the request to an operator Mac over a private network, or through the relay to a Mac behind NAT, which dialed out to the relay over port 443. Macs on one local network form a cluster that can shard one model. Operator Macs register, heartbeat and attest to the control plane. The gateway also uses a platform-run trusted engine to score sampled responses and, when enabled, to screen prompts.Your appOpenAI, Claude orOllama clientHTTPSControl planeaccounts, keys, billing,scheduler, ledgerGatewayapi.siliconnetwork.io,the only public doorTrusted enginea platform-run Mac:verification, moderationauth, schedule,usagescore samples,screen promptsRelayMacs dial outover port 443Operator MacsApple SiliconMac StudioMac miniLAN cluster: onemodel, shardedbehind NATMacBook ProMac miniprivate networkregister, heartbeat, attestrequest pathcontrol trafficMac serving a model

One request, end to end: admission (key, allowlist, rate limit), optional moderation, routing to a node, serving, metering, and a receipt when you ask for one.

Request lifecycleA sequence across five participants: caller, gateway, control plane, operator Mac and trusted engine. 1, admission: the caller sends a request with an API key; the gateway verifies the key with the control plane, cached, and gets back its scope, pins and credit, then checks the IP allowlist and rate limit. 2, moderation: when enabled, the gateway screens the prompt with Llama Guard on the trusted engine. 3, routing: the gateway asks the control plane to schedule a node by model, trust, the key's pins, prefix cache and context length. 4, serving: the gateway forwards the request to the operator Mac, which streams tokens back through the gateway to the caller. 5, metering: the gateway counts the tokens and sends a usage event, which the control plane prices. 6, receipt: on request, the Mac signs a receipt and the gateway countersigns it for the caller.CallerGatewayControl planeOperator MacTrusted engine1 Admissionkey, scope, IP list,rate limit, creditrequest, API keyverify key (cached)scope, pins, credit2 ModerationLlama Guard,off by defaultscreen the prompt3 Routingmodel, trust, pins,prefix cache, contextschedulea node4 Servingbatching, adapters,structured outputthe requesttokens, streamedstream5 Meteringgateway counts,control plane pricesusage event6 Receipted25519, opt-insigned receiptcountersigned
Silicon Network›Docs37 items