Admin API
Every /api/admin/* endpoint needs Authorization: Bearer <key> — the key is the api_key file in the data directory — reads included. The one exception is the WebSocket upgrade /api/admin/ws, which takes a single-use ticket from POST /api/admin/ws-ticket instead. Admin endpoints are also CORS-protected.
Node Management
GET /api/admin/stats
Node statistics and hardware info.
Its network_traffic figure (also on /v1/status and the dashboard's stats) says
what this node's upload and download are made of. Since 0.3.243, direct_by_kind
lists this node's own messages by kind, and by_protocol lists every byte by the
network protocol its connection used, with opened_by: us for what this node
asked, peer for what it answered. The by_protocol rows add up to out_bytes
and in_bytes. Since 0.3.244, kad_inbound has one row for each kind of
Kademlia request other nodes send this one (find_node, get_provider,
add_provider, get_record, put_record, zeros included): requests handled,
and the peers its answers listed — closer_peers, and provider_peers for the
holders it named.
GET /api/admin/peers
Connected peers with latency, trust scores, and hosted models.
GET /api/admin/credits
The internal ledger figures (balance, lifetime earned and spent, escrow) and the tier name. Credits are dormant: the balance is self-minted, gates nothing, and the tier is the same for everyone.
GET /api/admin/credits/transactions
A bounded log of recent balance movements (delta, kind, reason, resulting balance), oldest first. lifetime_refunded is partly synthetic: unexplained gaps are attributed to refunds so the books close.
GET /api/admin/diagnostics
Plain-text health report, meant to be read in a terminal or pasted into a bug report. Covers internet reachability and NAT status, hole-punching counts, the last 50 completed requests with their routes and per-segment timings, per-peer serving performance, what this node has served for others, and recent failures including which peer served each one. See Troubleshooting for how to read it.
Network addresses are redacted unless ?full=1 is passed — replaced by a
placeholder naming their kind, with transport, port, peer id and /p2p-circuit
structure left intact. The default is the safe one because this endpoint exists
to be shared: the dashboard's Copy diagnostics button and swarmllm diagnostics both use it, and the report otherwise carries this machine's
addresses plus every remembered peer address.
GET /api/admin/performance
The JSON sibling of diagnostics, used by the dashboard's Performance view.
| Field | Contents |
|---|---|
recent | Up to 50 completed requests, newest first: route, per-phase timings, per-segment attribution (node, layer range, elapsed, transport, region), tokens, outcome |
peers | Every peer that has served part of a request: round-trip time, ms per layer, latency EWMA, sample count, region — slowest first |
served | Segments and layers this node computed for others, compute time, activation bytes returned, and ms per layer |
hourly | One bucket per hour for the past week: request and error counts, average total time, average time-to-first-token, average throughput, and counts by route. Persisted, so it survives a restart |
Fetched on demand rather than pushed over the WebSocket. The per-peer and per-request detail here is high-cardinality by nature; it is served when asked for and never retained beyond the bounded rings above.
GET /api/admin/network-map
Geographic distribution of peers and shards across regions. Each entry includes the total peer count for that region, per-model shard-holder counts, per-model request demand rates, coverage gaps (models with zero holders in the region), and per-model replication targets derived from pool size and demand. Includes the local node in its auto-detected or configured region.
Response:
{
"regions": {
"US": {
"total": 3,
"models": { "tinyllama-1.1b-q4-k-m": 2 },
"demand": { "tinyllama-1.1b-q4-k-m": 5 },
"coverage_gaps": [],
"replication_target": { "tinyllama-1.1b-q4-k-m": 2 }
}
},
"recent_routes": [
{ "model": "qwen2.5-coder-7b-instruct-q4-k-m", "regions": ["TH", "IT"], "ok": true }
]
}
recent_routes lists this node's recent requests that left its region: the
regions each one crossed, starting here, and whether it succeeded.
GET/PUT /api/admin/config
Read or update daemon configuration. Both need the API key.
PUT changes only the settings the dashboard exposes (contribution level, disk,
bandwidth, concurrency, auto-manage, update mode, relaying, LAN trust, …). A key
it does not apply is refused with a 400 naming it; for a config.toml setting
such as network.enable_upnp or network.external_addresses, the message says
which section of config.toml to put it in.
POST /api/admin/config/reload
Re-read config.toml and apply it to the running node. Bearer auth required.
The response separates what took effect from what did not:
applied— in force now. Most settings are simply re-read by whatever acts on them; a few (concurrency limit, batch size and window, auto-manage interval, session timeout) are pushed to the subsystem that has to resize or retime something.restart_required— read once at startup and unchanged by this call. Onlymax_peersis in this group: connection limits are fixed when the peer-to-peer network is built.
POST /api/admin/shutdown
Gracefully shut down the node. Localhost only, Bearer auth required.
POST /api/admin/api-key/rotate
Issue a new API key and invalidate the old one. It takes effect when the node next starts — the running server keeps the key it started with, and logs that a restart is needed.
Model Management
GET /api/admin/models
List models with shard status, VRAM estimates, and acquisition state. Each model includes:
mmprojfield withavailable(bool),local(bool), andholders(count) for VLM vision encoder statustrust_levelfield: one of"Discovered","Pinned","DemandVerified", or"NetworkPopular"indicating the model's trust status (auto-manage only downloads shards for DemandVerified+ or Pinned models, OR for any model where this node already hosts at least one shard — gap-filling exemption). R141: trusted curator publishers (meta-llama, Qwen, mistralai, bartowski, unsloth, etc.) promote at 10k HF downloads instead of 100k
POST /api/admin/models/{id}/add
Trigger model acquisition from the network.
GET /api/admin/models/{id}/status
Check model acquisition progress.
DELETE /api/admin/models/
Remove model (shards + manifest + state).
DELETE /api/admin/models/{id}/shards/
Delete a single shard.
GET/PUT /api/admin/models/{id}/auto-manage
Per-model auto-manage policy (including prune toggle).
GET/PUT/DELETE /api/admin/models/{id}/encrypted-pipeline
Per-model "Start and finish on this computer" setting (encrypted_pipeline). It is decided, first match wins, by: your choice for this model; an explicit global encrypted_pipeline = true; otherwise it is on exactly while this node holds both the first and last part of the model (encrypted_pipeline_auto, default on).
GET returns encrypted_pipeline (in force or not), decided_by (this_model, global_setting or automatic), per_model_override (your choice, or null), default_for_this_model (what it would be with no choice), global_default, automatic_when_both_ends_here, readiness (ready, has_first_shard, has_last_shard) and an overhead note.
PUT {"enabled": true} or {"enabled": false} stores a choice. Turning it on without both ends is recorded, and requests for the model are then refused until the parts are here (fails closed). A warning is logged for 2-part models (fully local, no distribution benefit). PUT {"enabled": null} or DELETE forgets the choice, so the global setting and the automatic rule decide again. A body without enabled is a 400. The choice is persisted and survives restarts.
PUT /api/admin/models/{id}/shards/{index}/lock
Lock/unlock a shard to prevent auto-pruning.
GET /api/admin/models/{id}/pipeline-plan
A preview of how a request for this model would be routed now: the ordered segments and the candidate holders of each range. "No route right now" is an answer, not a failure — 200 with routable: false and a reason carrying the same message / hint a real request would get. Only a model id this node has never heard of is a 404.
GET /api/admin/reference-models
The pinned reference models (docs/REFERENCE_MODELS.md) offered as opt-in downloads, so a speed figure from one machine means the same as one from another.
Storage & Shards
POST /api/admin/rescan-shards
Rescan local shard files on disk and update the model registry and network announcements without restarting the daemon. Useful after manually placing shard files in the data directory. Bearer auth required.
Response:
{ "status": "ok", "models_updated": ["model-id-1"], "count": 1 }
GET /api/admin/models/{id}/metadata
Read parsed GGUF metadata from a locally-stored model header (gguf_header.bin). Returns architecture parameters, tokenizer settings, quantization type, and all raw metadata key/value pairs (tokenizer vocabulary arrays are excluded). Returns 400 if no header file exists for the model.
Response shape:
{
"model_id": "...",
"general": { "name": "...", "architecture": "llama", "architecture_supported": true, "file_type": 11, "quantization": "Q4_K_M" },
"model": { "context_length": 4096, "block_count": 32, "embedding_length": 4096, "head_count": 32, "head_count_kv": 8, "rope_dimension_count": 128, "rope_freq_base": 500000.0, "layer_norm_rms_epsilon": 1e-5, "vocab_size": 32000 },
"tokenizer": { "model": "llama", "pre": "...", "eos_token_id": 2, "bos_token_id": 1, "padding_token_id": null },
"tensors": { "count": 291, "data_offset": 131072 },
"raw": [{ "key": "general.architecture", "value": "llama" }, ...]
}
POST /api/admin/models/{id}/shards/{index}/download
Trigger a P2P download of a specific shard that is not yet held locally. The daemon first checks for P2P peers that hold the shard (picking the best peer by LAN-proximity, latency, and trust), then falls back to returning HuggingFace source info if no peers are available. Bearer auth required.
Responses:
{ "status": "already_local", ... }— shard is already on disk{ "status": "downloading", "source": "p2p", "peer": "...", ... }— P2P download started{ "status": "use_hf", "source": "huggingface", "repo_id": "...", "filename": "...", ... }— no P2P peers, usehf/download-shardsinstead- 400 if no peers and no HuggingFace source known
POST /api/admin/models/{id}/shards/{index}/unload
Unload a single shard from memory (VRAM/RAM) without deleting the file from disk. Narrows the model's shard window to exclude this shard and restarts the worker subprocess. If this is the last loaded shard, the model is fully unloaded. Bearer auth required.
Response:
{ "status": "unloaded", "model_id": "...", "shard_index": 0, "remaining_loaded": [1, 2] }
POST /api/admin/models/{id}/shards/{index}/load
Load a shard that is on disk into memory. The shard must already be present locally (use /download first if not). Expands the model's shard window to include the shard and restarts the worker subprocess. Bearer auth required.
Response:
{ "status": "loaded", "model_id": "...", "shard_index": 0, "loaded_shards": [0, 1, 2] }
POST /api/admin/models/{id}/unload
Unload an entire model from memory (VRAM/RAM) without deleting any files from disk. Evicts all split-model entries, kills the worker subprocess, clears GGUF metadata cache, and clears the loaded-model record. Bearer auth required.
Response:
{ "status": "unloaded", "model_id": "...", "model_name": "...", "segments_removed": 2, "estimated_freed_mb": 4096 }
GET /api/admin/shard-storage
Per-model storage breakdown, disk and VRAM usage.
GET /api/admin/prune-history
Recent auto-prune events.
GET /api/admin/storage/breakdown
The figures behind the storage bar, in MB: total_mb, used_mb, free_mb, auto_target_mb (the cap on what auto-manage may hold in total, not head-room on top of used_mb), auto_remaining_mb, auto_limited_by, plus held_shards, auto_manage_enabled and the contribution level.
GET/PUT /api/admin/schedule
Resource schedule management.
HuggingFace Integration
GET /api/admin/hf/search?q=...
Search HuggingFace for GGUF models. The parameter is q — query is ignored, and a missing or empty q returns []. Optional tasks=chat,code,vision,multilingual,reasoning narrows the results. Returns results grouped by repository with quantization variants, recommended variant, and VRAM fitness indicator.
Response format:
[{
"repo_id": "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF",
"downloads": 50000,
"likes": 120,
"variants": [
{ "filename": "...Q4_K_M.gguf", "size_bytes": 668000000, "quant": "Q4_K_M" },
{ "filename": "...Q8_0.gguf", "size_bytes": 1100000000, "quant": "Q8_0" }
],
"recommended_variant": "Q4_K_M",
"fits_vram": true
}]
GET /api/admin/hf/probe?repo_id=...&filename=...
Probe a remote GGUF file (size, shard layout).
POST /api/admin/hf/download-shards
Download specific shard indices from HuggingFace. Bearer auth required.
Name exactly one of these, or the request is refused with a 400:
"shards": [0, 1, 2]— these parts."all_shards": true— every part of the file, worked out from the probe the endpoint runs anyway. What the Models tab's Download sends for a model that fits this computer."peer_fair_share": true— one seed part, chosen by BLAKE3(node_id || model_id) so different computers seed different parts. Other computers with auto-manage on pick up the rest only once the model clears the auto-manage trust gate, so on its own this does not make a model usable.
curl -X POST http://localhost:8800/api/admin/hf/download-shards \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"repo_id": "Qwen/Qwen2.5-Coder-7B-Instruct-GGUF", "filename": "qwen2.5-coder-7b-instruct.Q4_K_M.gguf", "all_shards": true}'
GET /api/admin/hf/source/
Look up the HuggingFace source (repo + filename) for a locally-known model. First checks the in-memory source cache and the probe cache, then auto-discovers by searching HuggingFace if neither has an entry. If found via auto-discovery the result is cached to the database and hf_source.json in the model directory.
Response:
{ "model_id": "...", "repo_id": "TheBloke/TinyLlama-...-GGUF", "filename": "tinyllama-...Q4_K_M.gguf" }
GET /api/admin/downloads
List the download queue with per-shard progress, speed, and source.
POST /api/admin/downloads/{model_id}/cancel
Cancel an in-progress download.
LoRA Adapters
A registered adapter is applied to any chat request that names it in
lora_adapter. It runs only on the computer it was registered on, and only for
a model that computer holds whole — a request that cannot use it is refused
with a 400 saying why, never answered by the base model.
Registration is recorded in <data_dir>/adapters/<id>.adapter.json and
survives a restart. If you replace the adapter file, unregister and register it
again: a file whose contents changed since registration is refused.
GET /api/admin/adapters
List all registered LoRA adapters with their metadata (id, name, base model, rank, alpha, path).
Response: { "adapters": [ { "id": "...", "name": "...", "base_model": "...", "rank": 16, "alpha": 32.0, "path": "..." } ] }
POST /api/admin/adapters
Register a LoRA adapter from a safetensors file (PEFT naming, …layers.N.self_attn.q_proj.lora_A.weight; F32, F16 or BF16). Bearer auth required. The file must be inside <data_dir>/adapters/. id must be a plain name (no /, \ or leading .); if omitted, a UUID is generated. rank must match the file's matrices.
Request body:
{ "id": "my-adapter", "name": "My Adapter", "base_model": "tinyllama-...", "rank": 16, "alpha": 32.0, "path": "my-adapter.safetensors" }
path may be absolute or relative to <data_dir>/adapters/.
Response: { "status": "ok", "adapter": { ... } }
DELETE /api/admin/adapters/
Unregister a LoRA adapter. Does not delete the file from disk. Bearer auth required. Returns 400 if the id is not found.
Response: { "status": "ok", "message": "Adapter 'my-adapter' removed" }
Cloud Providers
GET /api/admin/providers
List configured cloud providers (name + configured flag, no keys exposed).
PUT /api/admin/providers
Update cloud provider API keys. Bearer auth required. Keys are encrypted at rest.
GET /api/admin/provider-models
List available models from all configured cloud providers. Results are cached for 30 seconds; stale results are returned immediately and refreshed in the background. Includes models from OpenAI, Anthropic (static list), DeepSeek, Mistral, Groq, NVIDIA NIM, Cerebras, SambaNova, Fireworks, Together AI, DeepInfra, and Moonshot/Kimi.
Calling this endpoint also refreshes the routing table that decides whether a model whose id carries no recognisable prefix is proxied to a provider or refused locally. Since v0.3.174 that table is also refreshed on its own every 15 minutes, so a node whose dashboard is never opened still routes its cloud models; before that it was filled only here, and a node that had not been visited refused them outright. A provider that fails to answer a refresh keeps the models it had, rather than having them dropped — a failed fetch used to be indistinguishable from a provider with no models.
Response: { "models": [ { "id": "gpt-4o", "name": "GPT-4o", "provider": "openai" } ] }
GET /api/admin/provider-health
Probe each configured provider by sending a tiny max_tokens=1 inference request (using a suitable test model per provider). All probes run in parallel with a connect timeout.
Response:
{ "providers": [ { "provider": "openai", "status": "up", "latency_ms": 320, "detail": "" } ] }
Status values: up, rate_limited, overloaded, timeout, unreachable, error_<code>.
POST /api/admin/provider-model-status
Probe availability and latency for a list of specific cloud model IDs (up to 20 per request). Sends a max_tokens=1 request to each model's provider endpoint. Anthropic models are skipped (no cloud proxy probing). Needs the API key, like every admin endpoint.
Request body: { "models": ["gpt-5.4", "claude-sonnet-5", "deepseek-v4-flash"] }
Response:
{ "models": [ { "model": "gpt-4o", "status": "up", "latency_ms": 210 } ] }
Status values: up, rate_limited, not_found, unavailable, timeout, error.
Claude Subscription (feature-gated)
Requires building with
--features claude-subscription. When the feature is not enabled, these endpoints return{"error": "claude-subscription feature not enabled"}.
GET /api/admin/claude-subscription/status
Detect whether the claude CLI is installed and authenticated on this machine. Reads version from claude --version and subscription info from ~/.claude/.credentials.json (read-only).
Response:
{
"cli_installed": true,
"cli_version": "2.1.92 (Claude Code)",
"authenticated": true,
"subscription_type": "max",
"rate_limit_tier": "default_claude_max_5x"
}
PUT /api/admin/providers (claude_subscription_enabled field)
Enable or disable the Claude subscription provider. Pass claude_subscription_enabled alongside other provider key updates.
{ "claude_subscription_enabled": true }
When enabled, claude-* model requests are routed through the local CLI subprocess instead of the Anthropic API key. The Anthropic API key (if configured) is used as fallback when disabled.
Updates
GET /api/admin/version
Current binary version info.
POST /api/admin/update/check
Check for available updates. Returns version info and changelog if update available.
POST /api/admin/update/apply
Download and apply an update. Bearer auth required.
Discovery
GET /api/admin/network-code
Get an encrypted shareable invite code and network phase. The code embeds the node's TCP listening address encrypted with ChaCha20Poly1305 — the IP is not visible in the code.
POST /api/admin/join-network
Join the network via encrypted invite code (swarm://...) or raw multiaddr. Immediately dials the peer and saves the address to the peer cache.
GET /api/admin/foreign-pool-catalog
Models other pools advertise — discovery only, and only when pool.share_model_catalog is on. Using them is a separate opt-in, pool.allow_cross_pool_inference (settable live through PUT /api/admin/config).
Responses API listing
GET /api/admin/responses
List stored Responses-API records (backs the dashboard's Responses tab).
Optional query params: ?limit=N (cap on returned records, default 100,
max 500) and ?status=... (filter by completed / in_progress /
cancelled / failed / queued). See Responses API
for the user-facing surface.
Auto-Manage Surfaces
GET /api/admin/swarm/capacity
A snapshot of the swarm's collective capacity: online nodes, total graphics memory and disk, GPU nodes, regions represented, which models it can serve, which it could with more holders, which this node hosts, and their redundancy.
GET /api/admin/swarm/capacity-plan
What-if scenarios over that snapshot, with a headline target naming how many more contributors a model needs.
GET /api/admin/wishlist
R111 — ranked list of models the swarm wants. Triggers a wishlist refresh on every call so the response always reflects current state. Response shape:
{
"computed_at": 1727712345,
"entries": [
{
"model_id": "mistral-7b-instruct-v0.3-Q4_K_M",
"display_name": "Mistral 7B Instruct v0.3",
"status": "serveable",
"score": 78,
"swarm_replicas": 4,
"target_replicas": 3,
"size_mb": 4368,
"vram_required_mb": 5023,
"shards_covered": 8,
"total_shards": 8,
"hosted_by_us": false,
"why_tags": ["wishlist.why.popular_on_swarm", "wishlist.why.fits_your_memory"]
},
{
"model_id": "hf-candidate:bartowski/Llama-3.2-3B-Instruct-GGUF",
"display_name": "Llama-3.2-3B-Instruct-GGUF",
"status": "candidate",
"score": 65,
"hf_repo_id": "bartowski/Llama-3.2-3B-Instruct-GGUF",
"task_tags": ["chat"],
"why_tags": ["wishlist.why.popular_on_hf", "wishlist.why.trusted_publisher", "wishlist.why.candidate_one_click"]
}
]
}
status is one of hosting / serveable / aspirational / candidate
(R141) / unreachable / blocked. Candidate entries carry an
hf_repo_id field and a synthetic model_id (prefix hf-candidate:)
so the frontend can route the click to the HF browse without colliding
with real model_ids. See Wishlist architecture
for the score formula and trust-promotion path.
GET /api/admin/hf/trending
R112 — most recent HF trending GGUF snapshot from the background
HfWatcher (refreshed hourly). Response shape mirrors HfTrendingSnapshot
with entries: [{ repo_id, downloads, likes, task_tags, created_at_secs }]
and fetched_at: <unix-secs>. Empty entries means the watcher hasn't
completed its first poll yet (30s startup delay) OR the daemon is
configured with auto_manage.hf_watcher_enabled = false.
GET /api/admin/quant-recommendations
R133 — per-family quant-choice recommendations. Returns the
highest-quality variant that fits the swarm's aggregate VRAM with
reasonable replication for each model family. R141 flipped
auto_manage.auto_switch_quants to default true, so this surface
now drives automatic trust promotion of the recommended variant — the
UI uses it as a transparency view rather than a manual action surface.
Authentication
GET /api/admin/api-key
Retrieve the API key. Bearer auth required.
The dashboard is the exception: it has no key yet on page load, so it may fetch one here without a Bearer token, provided both of the following hold.
- The request arrives over a network this node trusts — loopback always, a
Tailscale-style overlay when the node is on one too, and a private/LAN
address only with
api.dashboard_trust_lan. See the[api]section of the configuration reference. - It carries the single-use
X-Dashboard-Noncethat the daemon substituted into the served HTML for that page load (60s TTL).
A bare curl against this endpoint therefore returns 401 — it has neither. To
read the key from a script, use the api_key file in the data directory.
WebSocket
GET /api/admin/ws
WebSocket for live updates. Pushes the following event types:
| Event | Trigger | Data |
|---|---|---|
activity_event | Any subsystem event | kind, model_id, message, timestamp, toast_level |
stats_update | Every 2s | Peer count, credits, acquisitions, shard registry, swarm_capacity (R110), wishlist (R111), hardware (RAM/GPU/disk, from a cache refreshed at most every 6 s), network_traffic (bytes in and out plus the current rate — null, never zero, when nothing is counting) |
peer_list | Peer connect/disconnect | Full peer snapshot |
models_changed | Shard download/load/prune | (none — signals dashboard to refresh) |
update_available | New version detected | Version info, changelog |