Skip to content
Support status
0
FR BTC accepted Bitcoin accepted. See ways to pay.

AxeOS API Reference: Every ESP-Miner Endpoint at v2.14.2 and v2.15.0, 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 (published 2026-07-08) and re-verified against v2.15.0 (the current stable release, published 2026-08-21) — every v2.15.0 difference is marked v2.15.0+ inline. Every path, method, request field and response shape below was re-derived from the source at those tags — main/http_server/http_server.c (v2.15.0) and main/http_server/openapi.yaml (v2.15.0) — 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; on v2.15.0 and later the device also answers mDNS (PR #1240 by @camalolo), so its hostname resolves as http://<hostname>.local — factory images ship the hostname bitaxe (so http://bitaxe.local), a clash gets a MAC-derived suffix, and GET /api/system/info reports mdnsHostname / fullHostname; v2.14.x and earlier have no mDNS responder in the firmware — use the IP.
  • 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 and v2.15.0 (v2.15.0+ marks the three routes added in v2.15.0)

Method Path What it does
GET /api/system/info Full system status and telemetry — the object every dashboard polls
POST /api/system/boot v2.15.0+ — choose the app partition to boot next ({"partition":"factory"|"ota_0"|"ota_1"}), reset any custom web UI, reboot
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 (v2.15.0+: pools as a pools array — see below)
PUT /api/system/pools/<index> v2.15.0+ — write one pool entry (index 0–7) with its per-pool stratum fields
DELETE /api/system/pools/<index> v2.15.0+ — clear one pool entry; refused (400) while it is the selected primary or fallback
POST /api/system/OTA Upload a new esp-miner.bin (firmware OTA)
POST /api/system/OTAWWW Upload a www.bin web-UI image — v2.14.x: the standard UI update; v2.15.0+: the UI is embedded in esp-miner.bin, so this installs an optional custom UI and sets useCustomWWW
GET /api/theme Read the UI color scheme and accent color (accentColors at v2.14.2; primaryColor from v2.15.0)
POST /api/theme Set the UI color scheme and accent color (same shape as the GET of your release)
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 (at v2.14.2 also served for every route when the web-UI filesystem is missing; from v2.15.0 the UI is embedded, that catch-all is gone, and the page gains a Disable Custom Web UI button)
OPTIONS /api/* CORS preflight

Unmatched /api/* routes hit a catch-all handler, and every other GET serves the AxeOS single-page app — from the SPIFFS www partition at v2.14.2; from v2.15.0 from the image embedded in esp-miner.bin, unless useCustomWWW is set and a custom UI exists on the partition. Route count from the source: 21 httpd_uri_t registrations in http_server.c at v2.14.2 plus the two /api/theme routes from theme_api.c; 23 + 2 at v2.15.0.

Added in v2.15.0 (stable 2026-08-21; Verified at the tag): PUT /api/system/pools/*, DELETE /api/system/pools/* (a pool-list CRUD pair) and POST /api/system/boot are registered at v2.15.0 (http_server.c lines 1894, 1992 and 2000 at that tag) and not at v2.14.2 — on v2.14.2, pool changes go through PATCH /api/system. Removed in v2.15.0: the implicit GET /* → recovery catch-all that v2.14.2 installed when the www filesystem was missing (the UI is embedded now). Endpoints that appear in older documentation — POST /api/system/factoryreset, GET /api/system/statistics/dashboard — are registered at neither tag. 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 (counting unique key names written in system_api_json.c, nested objects included: 109 at v2.14.2, 131 at v2.15.0). 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
v2.15.0+: pools, primaryPoolIndex, secondaryPoolIndex, mdnsHostname, fullHostname, useCustomWWW, partitions The pool list (up to 8 entries, each with its own stratum fields incl. stratumV2RequireAuth) and which entries are primary/fallback; the mDNS name and <hostname>.local; whether a custom web UI is active; the app partitions with label / isCurrent / isFactory. The flat stratumURL… keys above are still present and mirror the selected pools.
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. v2.15.0+ changes the pool shape: the 24 flat stratum* / fallbackStratum* keys are no longer accepted by PATCH (they left the settings table — main/nvs_config.c carries 22 REST names at v2.15.0 vs 42 at v2.14.2); pools are written as a pools array of objects that each carry a numeric id (0–7) plus the per-pool fields (stratumURL and stratumUser required; stratumPort, stratumPassword, stratumProtocol, stratumV2ChannelType, stratumV2AuthorityPubkey, stratumV2RequireAuth, stratumTLS, stratumCert, stratumSuggestedDifficulty, stratumExtranonceSubscribe, stratumDecodeCoinbase), and primaryPoolIndex / secondaryPoolIndex select which entries mine; useCustomWWW (0|1) is new. Because unknown keys are not rejected, a v2.14.2 script that PATCHes a top-level stratumURL is silently ignored on v2.15.0 — migrate it to the array. (One wrinkle to the write-only rule: at v2.15.0 each pools[] item in GET /api/system/info carries a masked stratumPassword: "*****" — the value itself is still never returned.) Also new at v2.15.0: a PATCH that changes hostname answers with a JSON redirect hint ({"status":"success","redirect":{"url":"http://<new-hostname>.local","delay":2000,"message":"…"}}) instead of an empty body.

Group Keys Constraints (from the schema)
Primary pool (v2.14.2 flat keys; v2.15.0+: inside pools[]) stratumURL, stratumPort, stratumUser, stratumPassword port 1–65535
Primary protocol (v2.14.2; v2.15.0+: per pools[] entry, plus stratumV2RequireAuth) stratumProtocol, stratumV2ChannelType, stratumV2AuthorityPubkey protocol SV1 | SV2; channel type standard | extended; pubkey optional, base58, ≤52 chars
Fallback pool (v2.14.2; v2.15.0+: secondaryPoolIndex + useFallbackStratum) 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
Web UI (v2.15.0+) useCustomWWW 0|1 — serve the custom UI uploaded via /api/system/OTAWWW instead of the embedded one; 0 is what the recovery page’s Disable Custom Web UI button sends

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"}'

v2.15.0+ — the same two operations through the pool list (entry 0 primary on SV2, entry 1 a V1 fallback); every pools[] item needs its id, stratumURL and stratumUser:

curl -s -X PATCH http://192.168.1.45/api/system \
  -H 'Content-Type: application/json' \
  -d '{"pools":[{"id":0,"stratumURL":"stratum+tcp://pool.example.com","stratumPort":3333,"stratumUser":"worker1","stratumPassword":"x","stratumProtocol":"SV2","stratumV2AuthorityPubkey":"<POOL_PUBLIC_KEY>"},{"id":1,"stratumURL":"stratum+tcp://fallback.example.com","stratumPort":3333,"stratumUser":"worker1","stratumPassword":"x","stratumProtocol":"SV1"}],"primaryPoolIndex":0,"secondaryPoolIndex":1}'
# or one entry at a time (reply: {"message":"Pool updated successfully"}):
curl -s -X PUT http://192.168.1.45/api/system/pools/0 -H 'Content-Type: application/json' \
  -d '{"stratumURL":"stratum+tcp://pool.example.com","stratumPort":3333,"stratumUser":"worker1","stratumPassword":"x","stratumProtocol":"SV2","stratumV2AuthorityPubkey":"<POOL_PUBLIC_KEY>"}'

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

At v2.14.2 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). v2.15.0+: one file. The AxeOS web UI is embedded in esp-miner.bin (release notes, 2026-08-21), the release assets carry no www.bin, the factory image no longer includes one (merge_bin.sh at the tag), and the www partition (still declared at 0x410000) is only used if you upload a custom UI: POST /api/system/OTAWWW remains registered and now sets useCustomWWW to 1, while POST /api/system/boot and the recovery page’s Disable Custom Web UI button (PATCH {"useCustomWWW":0}) bring the embedded UI back. Both upload endpoints 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.

# v2.14.x — from the directory holding the release assets for YOUR board (two files):
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
# v2.15.0+ — esp-miner.bin alone; the OTAWWW call is only for an optional custom UI

Workflow note: on v2.14.x 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; on v2.15.0 and later there is only the firmware upload. 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": {...}} at v2.14.2 and {"colorScheme": ..., "primaryColor": "..."} from v2.15.0 (v2.15.0+ also applies the LAN rule to both theme routes — non-private callers get 401); POST /api/theme accepts the same shape as its release’s GET 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. On v2.15.0 and later the same fields live inside each pools[] entry (add stratumV2RequireAuth if your pool requires authenticated SV2), written through PATCH /api/system or PUT /api/system/pools/<index>. Full setup: enable Stratum V2 on a Bitaxe.

Can I add or delete pools through the AxeOS API?

Not at v2.14.2 — change pools with PATCH /api/system. Yes from v2.15.0 (stable 2026-08-21): PUT /api/system/pools/<index> (0–7) writes one entry (stratumURL and stratumUser required; reply {"message":"Pool updated successfully"}), DELETE /api/system/pools/<index> clears one and is refused with 400 while that index is the primary or fallback selection, and PATCH /api/system accepts a pools array plus primaryPoolIndex / secondaryPoolIndex. Up to 8 pools (MAX_POOLS).

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. The v2.15.0 additions are @mutatrum’s pool list (PR #1795), @camalolo’s mDNS (PR #1240) and @cbyam’s SV2 require-auth option (PR #1796). Endpoint-by-endpoint verification against the v2.14.2 and v2.15.0 tags 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. 2026-08-21: re-verified against the v2.15.0 tag (http_server.c, nvs_config.c, system_api_json.c, theme_api.c, openapi.yaml, merge_bin.sh, recovery_page.html, websocket.c — the WebSocket payloads are unchanged; websocket_api.c and websocket_log.c are byte-identical across the two tags); every difference is marked v2.15.0+ above. Not re-verified at v2.15.0: the SV2 channel-type behavior (main/tasks/stratum_v2_task.c was not re-read) — those statements remain pinned to v2.14.2.