Skip to content
Small team, full backlog, zero orders dropped. Support replies are slower than we’d like. Read our status update → Zero orders dropped. Status → 📬 Check your spam folder — most of our replies land there. We do answer. Status update → 📬 Check your spam folder. Status →

AxeOS API Reference: Every ESP-Miner v2.14.2 Endpoint, Verified From Source

Every Bitaxe serves a complete, unauthenticated HTTP API on port 80 — the same API the AxeOS dashboard itself uses. This page is the endpoint-by-endpoint reference for that API, pinned to ESP-Miner v2.14.2 (the current release, published 2026-07-08). Every path, method, request field and response shape below was re-derived from the source at that tag — main/http_server/http_server.c and main/http_server/openapi.yaml — not from another article. The master branch moves; this page names its tag so you can hold it to account.

Scope: this is a reference, not a tutorial. For workflows — overclocking methodology, multi-pool strategy, network hardening, OTA planning — use the AxeOS advanced configuration & API guide; this page will not re-teach them. If you are looking for the DCENT_OS API, that is a different product with its own surface (REST, WebSocket, cgminer 4028, MCP) — see the DCENT_OS API reference. New to the device itself? Start at the Bitaxe hub.

Jump to: conventions · endpoint index · read endpoints · actions · PATCH settings · OTA · WebSockets · monitoring · the LAN rule

Conventions: base URL, authentication, errors

  • Base URL: http://<device-ip> — plain HTTP on port 80. Examples below use 192.168.1.45; if mDNS works on your network, the device’s hostname (default shown in AxeOS settings) resolves as http://<hostname>.local.
  • Authentication: none. No password, no token, no API key. The only gate is a network-range check (next section).
  • Content types: JSON in and out for the /api/system family, except /api/system/logs (plain text) and the two OTA upload endpoints (application/octet-stream).
  • CORS: the server answers OPTIONS /api/* preflights and sets Access-Control-Allow-Origin: *, so LAN dashboards served from another host can call the API from the browser.
  • Error responses: 401 — request failed the network-range check (see below); 400 — invalid settings body or invalid firmware file; 500 — internal error. Documented per-endpoint in the repo’s openapi.yaml.

The LAN rule (Verified in source): the /api/system endpoints answer only when both the requesting IP and the browser Origin header (when present) fall inside the RFC-1918 private ranges — 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (is_network_allowed and ip_in_private_range in http_server.c). Anything else gets 401 Unauthorized — including well-intentioned calls that arrive with a non-private source address, such as some VPN or overlay-network setups. When the device is in AP mode (initial setup), the check is waived. This origin-checking behavior is the descendant of the CSRF fix ESP-Miner shipped in v2.5.0 (January 2025, patch by @benjamin-wilson, release notes) — a changelog fact worth knowing when you read older API writeups.

Endpoint index — everything registered at v2.14.2

Method Path What it does
GET /api/system/info Full system status and telemetry — the object every dashboard polls
GET /api/system/asic ASIC model, chip count, and the valid frequency/voltage option lists
GET /api/system/statistics On-device history series; ?columns= selects which series
GET /api/system/scoreboard Top 20 best-difficulty shares
GET /api/system/wifi/scan Scan for nearby Wi-Fi networks
GET /api/system/logs Download current device logs as a text file
POST /api/system/identify Make the physical unit identify itself
POST /api/system/restart Reboot the device
POST /api/system/pause Pause mining
POST /api/system/resume Resume mining
POST /api/system/blockFound/dismiss Dismiss the block-found banner (keeps the count)
PATCH /api/system Write settings: pools, SV2 fields, tuning targets, fan, network, display
POST /api/system/OTA Upload a new esp-miner.bin (firmware OTA)
POST /api/system/OTAWWW Upload a new www.bin (AxeOS web UI)
GET /api/theme Read the UI color scheme and accent colors
POST /api/theme Set the UI color scheme and accent colors
GET /api/ws WebSocket: live device log stream (text frames)
GET /api/ws/live WebSocket: live telemetry stream (JSON diffs, ≤1 update per 500 ms)
GET /recovery Recovery page (also served for every route when the web-UI filesystem is missing)
OPTIONS /api/* CORS preflight

Unmatched /api/* routes hit a catch-all handler, and every other GET serves the AxeOS single-page app.

Added after v2.14.2 (master branch, not at the tag): PUT /api/system/pools/* and DELETE /api/system/pools/* (a pool-list CRUD pair) and POST /api/system/boot exist in current master but are not registered at v2.14.2 — on this release, pool changes go through PATCH /api/system. Conversely, endpoints that appear in older documentation — POST /api/system/factoryreset, GET /api/system/statistics/dashboard — are not registered at v2.14.2 either. If a script depends on any of these, test against your installed version before trusting a writeup, this one included.

Read endpoints

GET /api/system/info — the workhorse. Returns one large JSON object; the schema in openapi.yaml lists 80+ fields. The ones most scripts care about:

Field Meaning
hashRate, hashRate_1m, hashRate_10m, hashRate_1h Hashrate in GH/s: instantaneous plus 1 m / 10 m / 1 h averages
expectedHashrate What the current voltage/frequency should produce, in GH/s — compare against hashRate to spot underperformance
temp, temp2, vrTemp Average chip temperature (two sensors) and voltage-regulator temperature
power, voltage, current Power draw (W), input voltage, current (mA)
frequency, actualFrequency, coreVoltage, coreVoltageActual Configured vs measured ASIC frequency (MHz) and core voltage (mV)
sharesAccepted, sharesRejected, sharesRejectedReasons Session share counters; rejections broken down by pool-reported reason
bestDiff, bestSessionDiff, poolDifficulty Best share ever / this session, and current pool difficulty
stratumURL, stratumPort, stratumUser, stratumProtocol, isUsingFallbackStratum Active pool configuration; stratumProtocol is SV1 or SV2
responseTime Pool response time in ms (v2.14.0+ measures this per share on SV2 too)
uptimeSeconds, resetReason, runningPartition Uptime, why the last reset happened, which OTA partition is active
version, axeOSVersion, ASICModel, boardVersion, macAddr, hostname Identity: firmware and UI versions, chip, board, network identity
freeHeap, cpuUsage, wifiRSSI, wifiStatus Device health: memory, CPU, Wi-Fi signal
fanspeed, fanrpm, fan2rpm, autofanspeed, temptarget Fan state and the PID temperature target
miningPaused, overheat_mode, power_fault, hardware_fault Pause state, overheat protection, and fault strings when something tripped
blockFound, blockHeight, networkDifficulty, coinbaseOutputs Solo-mining context: blocks found, chain height, decoded coinbase outputs (when coinbase decoding is enabled)
curl -s http://192.168.1.45/api/system/info | jq .
# or just the fields you chart:
curl -s http://192.168.1.45/api/system/info | jq '{hashRate, temp, vrTemp, power, sharesAccepted}'

GET /api/system/asic — the tuning envelope. Returns the detected chip and the exact option lists AxeOS itself offers, so scripts never have to hard-code valid values:

curl -s http://192.168.1.45/api/system/asic | jq .

Response shape (field examples from the spec): ASICModel (one of BM1366, BM1368, BM1370, BM1397), deviceModel (e.g. "Ultra"), asicCount, defaultFrequency / frequencyOptions (MHz), defaultVoltage / voltageOptions (mV), and swarmColor. Anything you plan to PATCH into frequency or coreVoltage should come from these lists.

GET /api/system/statistics — on-device history. Returns currentTimestamp, a labels array, and a statistics array of data-point rows matching those labels. The columns query parameter selects series (comma-separated); the spec’s own example list: hashrate, hashrate_1m, hashrate_10m, hashrate_1h, asicTemp, vrTemp, asicVoltage, voltage, power, current, fanSpeed, fanRpm, fan2Rpm, wifiRssi, freeHeap, responseTime. Sampling cadence is the statsFrequency setting (seconds; 0 disables), and statsLimit in the info object reports the buffer depth.

curl -s 'http://192.168.1.45/api/system/statistics?columns=hashrate,asicTemp,power' | jq .

GET /api/system/scoreboard — the top 20 best-difficulty shares, each with rank, since (seconds ago), difficulty, and the raw share components (job_id, extranonce2, ntime, nonce, version_bits). The badge-of-honor endpoint.

GET /api/system/wifi/scan — nearby networks as {ssid, rssi, authmode} objects, authmode being the ESP-IDF auth-mode code (0 = open through WPA3 variants). GET /api/system/logs — the current log buffer as a plain-text download:

curl -s http://192.168.1.45/api/system/logs -o bitaxe-logs.txt

Action endpoints

All four are parameterless POSTs answering {"message": "..."}:

curl -s -X POST http://192.168.1.45/api/system/restart   # reboot; replies "System will restart shortly."
curl -s -X POST http://192.168.1.45/api/system/identify  # the unit says hi (find it on a crowded shelf)
curl -s -X POST http://192.168.1.45/api/system/pause     # stop hashing without powering off
curl -s -X POST http://192.168.1.45/api/system/resume    # start hashing again

The restart endpoint is the one that earns its keep: it is the trigger behind the Bitaxe auto-restart watchdog that recovers flatlined units automatically. POST /api/system/blockFound/dismiss clears the block-found banner while preserving the blockFound count — the one endpoint you hope to need.

Writing settings: PATCH /api/system

One endpoint writes every persistent setting. Send a JSON object containing only the keys you want to change; they are validated against the Settings schema (invalid values return 400) and persisted to NVS. Two things to know before scripting it: the schema sets additionalProperties: true, so an unknown (misspelled) key is not rejected — spell keys exactly; and password-class fields (stratumPassword, fallbackStratumPassword, wifiPass) are write-only — they never appear in GET /api/system/info.

Group Keys Constraints (from the schema)
Primary pool stratumURL, stratumPort, stratumUser, stratumPassword port 1–65535
Primary protocol stratumProtocol, stratumV2ChannelType, stratumV2AuthorityPubkey protocol SV1 | SV2; channel type standard | extended; pubkey optional, base58, ≤52 chars
Fallback pool fallbackStratumURL, fallbackStratumPort, fallbackStratumUser, fallbackStratumPassword, fallbackStratumProtocol, fallbackStratumV2ChannelType, fallbackStratumV2AuthorityPubkey, useFallbackStratum same shapes as primary; useFallbackStratum forces the fallback
Tuning frequency, coreVoltage, overclockEnabled MHz / mV, ≥1; overclockEnabled 0|1 unlocks custom values in AxeOS
Cooling autofanspeed, fanspeed, temptarget 0|1; 0–100 %; 0–100 °C
Network / identity ssid, wifiPass, hostname SSID 1–32 chars; password 8–63; hostname [a-zA-Z0-9-]+
Display / misc rotation, invertscreen, displayTimeout, statsFrequency, overheat_mode timeout −1 (always on) to 71582 min; statsFrequency 0 disables; overheat_mode: 0 clears overheat protection

Switch the primary pool (values shaped like the spec’s own examples):

curl -s -X PATCH http://192.168.1.45/api/system \
  -H 'Content-Type: application/json' \
  -d '{"stratumURL":"stratum+tcp://pool.example.com","stratumPort":3333,"stratumUser":"worker1","stratumPassword":"x"}'

Enable Stratum V2 on the primary pool, keeping a V1 fallback (v2.14.0 added native SV2 with all four primary/fallback protocol combinations):

curl -s -X PATCH http://192.168.1.45/api/system \
  -H 'Content-Type: application/json' \
  -d '{"stratumProtocol":"SV2","stratumV2AuthorityPubkey":"<POOL_PUBLIC_KEY>","fallbackStratumProtocol":"SV1"}'

Two honest notes on the SV2 fields. First, the schema enumerates both standard and extended channel types — and at v2.14.2 both are implemented: extended is the default, and on BM1397 chips the firmware forces extended (the chip lacks hardware version rolling), so stratumV2ChannelType: "standard" is honored only on non-BM1397 chips (verified at the tag: main/tasks/stratum_v2_task.c). There is no Job Declarator client at this tag either way — SV2 on a Bitaxe means encrypted, binary-framed mining, not building your own block templates; even on extended channels the pool supplies the template. Second, the authority pubkey is your pool’s — copy it character-for-character from the pool’s own documentation, never from a third-party page. Step-by-step pool setup, endpoints and verification live in the companion guide: enable Stratum V2 on a Bitaxe. For tuning methodology — what to change, in what order, and how to validate it — use the advanced configuration guide; a restart (POST /api/system/restart) after settings changes is the reliable way to make sure every subsystem picks them up, and it is what the companion guide’s workflows do.

OTA endpoints: firmware and web UI

AxeOS updates are two files per release, and the API mirrors that: POST /api/system/OTA takes the firmware image (esp-miner.bin), POST /api/system/OTAWWW takes the web-UI image (www.bin). Both accept the raw binary as application/octet-stream and answer in plain text; the firmware endpoint validates the upload, switches the boot partition, replies "Firmware update complete, rebooting now!" and reboots. A bad file returns 400.

# from the directory holding the release assets for YOUR board:
curl -s -X POST http://192.168.1.45/api/system/OTAWWW \
  -H 'Content-Type: application/octet-stream' --data-binary @www.bin
curl -s -X POST http://192.168.1.45/api/system/OTA \
  -H 'Content-Type: application/octet-stream' --data-binary @esp-miner.bin

Workflow note: take both files from the same release so firmware and UI stay in step, and update the UI first, firmware second — the firmware upload reboots the device, the UI upload does not, so this order finishes cleanly in one pass. The device runs A/B partitions; runningPartition in the info object tells you which slot booted. Downloads and per-board file names are on the ESP-Miner releases page.

Theme endpoints

GET /api/theme returns {"colorScheme": ..., "accentColors": {...}}; POST /api/theme accepts the same shape and persists it. This is how the AxeOS UI stores its look; scripts rarely need it, but it is part of the registered surface and belongs in a complete reference.

WebSocket streams

Two WebSocket endpoints share one handler but stream different things (Verified in source — the registrations carry different stream types):

  • /api/ws — the log stream. Text frames containing device log output, drained from the on-device ring buffer only while at least one client is connected. This is the feed behind the AxeOS log viewer.
  • /api/ws/live — the telemetry stream. JSON frames shaped {"event": "update", "data": {...}} where data is a diff against the previously sent state, rate-limited to one update per 500 ms. First message after connect carries the full state; after that you only receive fields that changed. For dashboards this beats polling: no request overhead, and no wasted bytes on unchanged fields.
# websocat (or any WS client):
websocat ws://192.168.1.45/api/ws/live   # telemetry diffs
websocat ws://192.168.1.45/api/ws        # live logs

Monitoring recipe: /api/system/info → Prometheus → Grafana

The pattern that works: poll /api/system/info on an interval, translate the fields you care about into Prometheus metrics, graph in Grafana. What follows is a deliberately small sketch — the node_exporter textfile-collector version, no exporter daemon to maintain. Save as axeos-textfile.sh and run it from cron (or a systemd timer) every 30–60 seconds on a machine that already runs node_exporter with --collector.textfile.directory set:

#!/usr/bin/env bash
# axeos-textfile.sh - one Bitaxe -> node_exporter textfile collector. A sketch, not a product.
IP=192.168.1.45
OUT=/var/lib/node_exporter/textfile_collector/bitaxe.prom
J=$(curl -sf --max-time 5 "http://${IP}/api/system/info") || exit 1
{
  echo "bitaxe_hashrate_ghs $(jq .hashRate <<<"$J")"
  echo "bitaxe_temp_celsius $(jq .temp <<<"$J")"
  echo "bitaxe_vr_temp_celsius $(jq .vrTemp <<<"$J")"
  echo "bitaxe_power_watts $(jq .power <<<"$J")"
  echo "bitaxe_shares_accepted $(jq .sharesAccepted <<<"$J")"
  echo "bitaxe_shares_rejected $(jq .sharesRejected <<<"$J")"
  echo "bitaxe_uptime_seconds $(jq .uptimeSeconds <<<"$J")"
} > "${OUT}.tmp" && mv "${OUT}.tmp" "$OUT"

Honest footnotes to the sketch: the share counters reset on every reboot, so treat them in Grafana with reset-tolerant functions (increase() / resets()) rather than raw deltas; for multiple Bitaxes, add a label per device (bitaxe_hashrate_ghs{unit="45"} ...) and loop over IPs; and if you would rather stream than poll, /api/ws/live above is the better transport for a custom exporter. On-device history via /api/system/statistics is the zero-infrastructure alternative when all you want is a recent-past chart. Fleet-level scripting on this same API — batch tuning, comparison runs — is covered in the Bitaxe auto-tuning scripts guide, and the highest-value four lines of monitoring you can deploy remain the auto-restart watchdog.

Keep it on the LAN

Standard operating practice for an unauthenticated device API, stated once and calmly: never expose a Bitaxe to the internet. No port-forwards to it, no DMZ. The firmware’s private-range check refuses non-RFC-1918 callers, and that is a floor, not a security architecture — treat network placement as the real control. On a home network, an isolated IoT VLAN (or at minimum your router’s client-isolation for untrusted devices) keeps the miner reachable from your monitoring box and nothing else; if you need remote visibility, reach the LAN through your own VPN and call the API from inside — remembering the LAN rule means the API sees your VPN client’s source address, which must land in a private range to pass. The full hardening walkthrough (DHCP reservations, VLAN layout, remote-access patterns) is in the advanced configuration guide.

FAQ

Does the AxeOS API require a password or API key?

No. There is no authentication of any kind — by design, for a LAN device. The only gate is the firmware’s network-range check: requests (and browser Origins) must come from RFC-1918 private addresses. That is exactly why the API must never be reachable from the internet.

Why does the Bitaxe API return 401 Unauthorized?

Your request failed the private-range check in is_network_allowed: either the source IP or the browser’s Origin header resolves outside 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16. Typical causes: calling across a VPN or overlay network that presents a non-private source address, NAT that rewrites the source, or a proxied browser origin. Call from a machine on the same private LAN.

Which AxeOS endpoint should a dashboard poll?

GET /api/system/info — it carries hashrate (with 1 m/10 m/1 h averages), temperatures, power, shares, pool state and device health in one object. For push instead of poll, connect to the /api/ws/live WebSocket, which sends JSON diffs at most every 500 ms and costs nothing between changes.

Can I configure Stratum V2 through the AxeOS API?

Yes, on ESP-Miner v2.14.0 or later: PATCH /api/system with stratumProtocol: "SV2" and optionally your pool’s stratumV2AuthorityPubkey (base58, up to 52 characters); the fallback pool has mirrored fields and any V1/V2 combination works. Both channel types are implemented at v2.14.2 — stratumV2ChannelType defaults to extended, and BM1397 chips are forced to extended — and there is no Job Declarator client: SV2 on a Bitaxe encrypts and reframes your mining connection; it does not build block templates. Full setup: enable Stratum V2 on a Bitaxe.

Can I add or delete pools through the AxeOS API?

Not at v2.14.2. The PUT /api/system/pools/* and DELETE /api/system/pools/* pair appears on the master branch after the v2.14.2 tag and is not registered in the v2.14.2 build — on this release, change pools with PATCH /api/system. If your device runs a later release, check its own source or release notes before scripting against them.

Credits

This API is the work of the ESP-Miner maintainers and contributors — Skot, WantClue, mutatrum and the wider bitaxeorg community (GPL-3.0). The Stratum V2 fields exist thanks to @warioishere’s PR #1553; the network-origin protection traces back to the v2.5.0 CSRF patch by @benjamin-wilson. Open Source Miners United’s wiki maintained a community endpoint list before this page existed — credit where due, and it remains a good quick lookup. Endpoint-by-endpoint verification against the v2.14.2 tag by D-Central.

Record last verified: 2026-08-13. Every endpoint, field and constraint on this page was re-derived from the ESP-Miner source at the v2.14.2 tag (main/http_server/http_server.c, openapi.yaml, theme_api.c, websocket_api.c, websocket_log.c, main/tasks/stratum_v2_task.c) on that date.