Try it
curl https://dagcore.net/api/v1/poolEvery response the API produces is JSON and carries a generatedAt timestamp, errors
included. The machine-readable list of endpoints is at
/api/v1.
The promise
A field in v1 is never renamed and never changes meaning. If something has to
change in a way that would break code reading it, it is published as /api/v2 and v1
keeps running. New fields may appear — code that ignores what it does not recognise keeps
working.
The endpoints under /api/pool/ are not this. They serve this site's own pages, they
change without notice, and most of them are not reachable from outside at all. They are not
documented here on purpose.
Conventions
| Amounts | Decimal strings in wei, 18 decimals. Strings, because JSON numbers cannot
hold them exactly — parse them with a big-integer type, not Number. |
|---|---|
| Times | ISO 8601, always UTC, always with a Z. |
| Hashrate | H/s, and always an estimate — see below. |
| Errors | Always JSON, never an HTML page. error is a stable code for
your code to branch on, message is a sentence for a person. Codes:
not_found, bad_parameter, method_not_allowed,
rate_limited, unavailable.Two of them are produced by the web server in front of the API rather than by the API itself — rate_limited, and not_found for a path under
/api/pool/. Those carry error and message but
no generatedAt: nginx can only stamp local time with an offset,
and a timestamp that broke the UTC rule above would be worse than none. Branch on
error and the difference does not reach your code. |
| Rate limit | 2 requests per second, with a burst of 20. You may
spend 20 at once and then earn 2 a second back. Counted per IPv4 address, or per
/64 on IPv6 — a whole subscriber prefix shares one budget, because a single
IPv6 address is free to rotate. Over the limit you get 429 with
Retry-After: 1 and a JSON body. The Cache-Control on each endpoint says
how often it is worth asking; polling faster returns the same bytes. |
| User-Agent | Please send one that identifies your tool and a way to reach you, for
example bdag-dashboard/1.2 (+https://example.com). If something starts costing the
server real work, being able to write to you is better for both of us than the alternative,
which is blocking an address and leaving you guessing. |
When something goes wrong
Errors are JSON with the same two fields, whether they come from the API or from nginx refusing
the request in front of it. Branch on error; show message to a person.
A bad parameter — HTTP 400:
{
"error": "bad_parameter",
"message": "range must be one of 1h, 24h, 7d."
}Over the rate limit — HTTP 429, with Retry-After:
{
"error": "rate_limited",
"message": "Too many requests. Slow down and try again."
}When a number is an estimate, the field says so
Anything estimated is an object, not a bare number, so you cannot quote it without seeing that it is one:
"current": {
"value": 1704916.22,
"unit": "H/s",
"estimated": true,
"overSeconds": 639
}Hashrate is the estimate you will meet most. It is accepted shares multiplied by the difficulty
assigned to each connection, times 65,536 — derived from shares, not a count of hashes. Over a
short window it is noisy; over 24 hours it settles. The method field says this in the
response itself.
The endpoints
/api/v1the index, machine-readable/api/v1/healthanswering, and how stale/api/v1/poolpolicy, hashrate, blocks, totals/api/v1/minerswho is mining, pseudonymously/api/v1/hashratehashrate over time/api/v1/payoutswhat the pool has paid/api/v1/blocksblocks found
GET /api/v1cached 1 hour
The index: every endpoint, the conventions above, and what is deliberately absent. Start here if you are exploring; it is machine-readable, so a client can discover the rest.
{
"generatedAt": "2026-09-25T19:27:31.877Z",
"version": "v1",
"documentation": "https://dagcore.net/api.html",
"units": {
"amounts": "Decimal strings in wei, 18 decimals. Strings because JSON numbers cannot hold them exactly.",
"hashrate": "H/s",
"time": "ISO 8601, UTC"
},
"contract": "Fields in v1 are never renamed and never change meaning. A change that would break a reader is published as /api/v2, and v1 keeps running.",
"endpoints": [
{
"path": "/api/v1/health",
"description": "Whether this API is answering and how old its data is.",
"cacheSeconds": 0
},
{
"path": "/api/v1/pool",
"description": "Pool-wide state: policy, hashrate, blocks, totals paid.",
"cacheSeconds": 10
},
"\u2026"
],
"absent": {
"addresses": "This API never publishes miner addresses, in full or in part beyond the last four characters. The chain does \u2014 see reason."
}
}GET /api/v1/healthnever cached
Whether this API is answering and how stale its figures are. For a monitor, not for data.
| Field | Type | Meaning |
|---|---|---|
| status | string | ok; degraded — answering, but the
figures are over two minutes old; down — answering, but the data behind it cannot
be read at all. An endpoint that replies is never unreachable; if it were, you would get
no response to read this in. |
| dataAgeSeconds | number | How old the underlying figures are.
null when nothing could be read. |
| responseMs | number | How long this check itself took. |
{
"generatedAt": "2026-09-25T19:27:31.911Z",
"status": "ok",
"dataAgeSeconds": 8,
"responseMs": 31
}GET /api/v1/poolcached 10 s
The pool in one object: what it charges, how it pays, how to point a miner at it, how fast it is hashing, what it has found and what it has paid.
| Field | Type | Meaning |
|---|---|---|
| scheme | string | How rewards are split. PPLNS. |
| fee.bps | number | The pool fee in basis points — 100 is 1%. Read from the pool's own configuration when you ask, so it cannot drift from what is actually charged. |
| fee.percent | string | The same number as a percentage, for display. |
| stratum[] | array | How to point a miner here: url,
host, port, login (what goes in the username field — the
payout address), password ("x") and workerNames. The pool ignores the
password — any value, an empty one, or none at all is accepted; it is given as a value rather
than null because a config generator that sees null omits the field
and some miners will not start without it. note says both of these in the response
itself. workerNames is false: this
pool rejects address.worker at authorization, so a miner configured that way fails
to connect with nothing on screen saying why. Put the address on its own. |
| payout.windowSeconds | number | How often the pool settles. |
| payout.minimumWei | string | Below this, a miner's share is carried to the next settlement instead of being sent. |
| payout.confirmations | number | Confirmations required before a block's reward is treated as final. |
| hashrate.current | estimate | Recent pool hashrate. overSeconds
says how long a window it came from. |
| hashrate.last24h | estimate | The same over a day — steadier, and the one to quote. |
| miners.active | number | Miners that sent a share within
activeWithinMinutes. |
| miners.seen | number | Miners on record at all, including long-idle ones. |
| miners.connections | number | Open stratum connections. One miner can hold several. |
| blocks.* | numbers | Three different counts. See the section below — they are not meant to agree. |
| blocks.canonicalTotal | number | foundTotal without the blocks that are not on
the canonical chain: found by the pool, then dropped by a reorg, so their reward never existed. This is the
number the site shows as “Blocks found”. |
| blocks.orphaned | object / null | Why the two differ. verified: blocks
checked against the chain, which has no block by the pool's coinbase at their time.
inferredFromMissingRewards: before checkedSince — the oldest block the pool
still holds detail for — an orphan can only be seen as a window where one reward fewer arrived than
blocks were credited, and it is counted that way. Blocks imported from an earlier database cannot be
judged and stay counted. |
| paid.totalWei | string | Everything the pool has paid out across all miners
since recordsSince — not since the pool began, if its records were rebuilt. |
| paid.payoutCount | number | How many individual payments that was. |
| paid.minersPaid | number | How many distinct miners have been paid. |
| recordsSince | time | When the pool's records begin. Totals cover this period, not all of time. |
| lastBlock | object | The most recent block: height, hash, status, when it was
found. null if none is on record. |
{
"generatedAt": "2026-09-25T19:27:23.827Z",
"scheme": "PPLNS",
"fee": {
"bps": 100,
"percent": "1.00",
"source": "sidecar configuration"
},
"stratum": [
{
"url": "stratum+tcp://stratum.dagcore.net:3334",
"host": "stratum.dagcore.net",
"port": 3334,
"login": "payout address",
"password": "x",
"workerNames": false,
"note": "Worker names are not supported: the username must be the payout address on its own. address.worker is rejected at authorization. The password is ignored \u2014 any value, an empty one, or none at all is accepted; it is given here as a value rather than null because a config generator that sees null omits the field and some miners will not start without it."
}
],
"payout": {
"windowSeconds": 300,
"minimumWei": "1000000000000000000",
"confirmations": 12
},
"hashrate": {
"current": {
"value": 3387142.1087253625,
"unit": "H/s",
"estimated": true,
"overSeconds": 624
},
"last24h": {
"value": 13286402.375154823,
"unit": "H/s",
"estimated": true,
"overSeconds": 86424
},
"method": "\u2026"
},
"miners": {
"active": 6,
"seen": 15,
"connections": 6,
"activeWithinMinutes": 10
},
"blocks": {
"foundTotal": 648,
"canonicalTotal": 637,
"orphaned": {
"verified": 5,
"inferredFromMissingRewards": 6,
"checkedSince": "2026-09-24T08:04:25.000Z"
},
"last24h": 126,
"perHour": 5.25,
"perHourOverHours": 24,
"credited": 532,
"rewardsReceived": 522,
"pending": 1,
"rewardUnitWei": "162248406320000000000",
"note": "\u2026"
},
"paid": {
"totalWei": "101963211476600458843038",
"payoutCount": 1601,
"minersPaid": 15
},
"recordsSince": "2026-09-16T10:44:31.619Z",
"lastBlock": {
"height": "17559049",
"hash": "1ba02422b29c2031288125b4544a8a233c815bff07df2ada9379787b02000000",
"status": "MATURE",
"foundAt": "2026-09-25T19:25:20.151Z"
}
}GET /api/v1/minerscached 30 s
Who is mining, pseudonymously, ordered by hashrate. No addresses and no amounts — see what is left out.
window picks the span every hashrate figure here is estimated over: 10m,
1h (the default) or 24h; anything else is a 400. The pool figure
and the rows come from the same span, so the rows add up to poolHashrate, and
10m is the same span as hashrate.current in /pool. Shorter is
noisier: the estimate counts accepted shares, and ten minutes holds a sixth of an hour's, so luck moves
it about 2.5 times as much.
| Field | Type | Meaning |
|---|---|---|
| id | string | A stable handle for one miner, 12 hex characters. It stays the same between calls so you can follow a miner as its rank moves or its name changes, and it reveals nothing about the address. It is not an address and cannot be turned into one. |
| window | string | The window this response was computed over: 10m,
1h or 24h, as asked, 1h when not asked.
hashrateWindowMinutes says the same in minutes. |
| rank | number | Position by hashrate over the chosen window, 1 is largest. |
| name | string / null | The name the miner set by signing a message with the key
that owns the address. null when unset. Chosen by the miner — do not treat it as
identity, that is what id is for. |
| addressSuffix | string | The last four characters of the address, so a miner can recognise itself in the list. Four characters collide; do not key on it. |
| hashrate | estimate | That miner's estimated hashrate over
hashrateWindowMinutes. |
| shareOfPool | number | Its fraction of pool hashrate over the same window, 0 to 1. |
| difficulty | number / null | The share difficulty VarDiff assigned this miner,
recovered exactly from the share records. null when it sent no shares in the window.
This is what it was asked to mine against, not what any hash achieved. |
| sharesPerMinute | number / null | Accepted shares per minute over the same window. With VarDiff working, this stays near constant while difficulty moves instead. |
| hardware | object / null | A guess, marked as one.
{ class, inferred: true, basis }. The pool cannot see hardware; this is banded from
estimated hashrate alone: cpu below 300 KH/s, gpu from there to
30 MH/s, asic above. The edges come from measured machines on this algorithm —
one RTX 3080 is about 1.6 MH/s, an eight-card rig about 13 MH/s, a large GPU farm
reaches about 20 MH/s, and the weakest ASIC seen in practice does about 49 MH/s. They
are edges, not facts: a rig of many small cards looks the same as one large one, and a throttled
machine looks like a smaller class. A hint for grouping, never a fact
about someone's setup. |
| status | string | mining, idle or
offline, from how recently a share arrived. |
| lastShareAt | time | When the pool last accepted a share from it. |
| readThrough | time | How far the share sampler has read. Figures describe the
pool up to this moment, not to generatedAt. |
{
"generatedAt": "2026-09-25T19:27:23.819Z",
"readThrough": "2026-09-25T19:26:24.142Z",
"window": "1h",
"hashrateWindowMinutes": 60,
"poolHashrate": {
"value": 2826992.776526228,
"unit": "H/s",
"estimated": true,
"overMinutes": 60
},
"method": "\u2026",
"count": 15,
"miners": [
{
"id": "a711bcf19e82",
"rank": 1,
"name": "RTX PRO 4500",
"addressSuffix": "8f87",
"hashrate": {
"value": 1408188.7853530422,
"unit": "H/s",
"estimated": true,
"overMinutes": 60
},
"shareOfPool": 0.4981225268935445,
"difficulty": 32.15229794167217,
"sharesPerMinute": 40.09776658861601,
"hardware": {
"class": "gpu",
"inferred": true,
"basis": "estimated hashrate, not detected hardware"
},
"status": "mining",
"lastShareAt": "2026-09-25T19:25:55.481Z"
},
{
"id": "3c0f1129ffaa",
"rank": 2,
"name": "RTX 3080TI",
"addressSuffix": "6E28",
"hashrate": {
"value": 1258763.949138584,
"unit": "H/s",
"estimated": true,
"overMinutes": 60
},
"shareOfPool": 0.4452660649120359,
"difficulty": 27.92204951304452,
"sharesPerMinute": 41.27321721941359,
"hardware": {
"class": "gpu",
"inferred": true,
"basis": "estimated hashrate, not detected hardware"
},
"status": "mining",
"lastShareAt": "2026-09-25T19:26:21.260Z"
}
],
"note": "\u2026"
}GET /api/v1/hashratecached 30 s
Pool hashrate over time. range is 1h, 24h (default) or
7d; anything else is a 400.
| Field | Type | Meaning |
|---|---|---|
| estimated | boolean | Always true, for the reason in method. |
| points[].t | time | Start of the bucket. |
| points[].hashrate | number | Estimated H/s for that bucket. A gap means no samples, which is not the same as zero hashrate. |
{
"generatedAt": "2026-09-25T19:27:31.931Z",
"range": "24h",
"estimated": true,
"method": "\u2026",
"points": [
{
"t": "2026-09-24T19:40:00.000Z",
"hashrate": 2562678.6199704916
},
{
"t": "2026-09-24T20:00:00.000Z",
"hashrate": 3424817.6820538025
},
"\u2026"
]
}GET /api/v1/payoutscached 60 s
What the pool has paid, aggregated across every miner. Per-miner amounts are not published.
| Field | Type | Meaning |
|---|---|---|
| totals.paidWei | string | Everything paid since recordsSince. |
| hourly[].t | time | Start of the hour, UTC. |
| hourly[].wei | string | Paid during that hour. |
| hourly[].payouts | number | How many payments that was. |
| hourly[].partial | boolean | True for the hour still in progress. Charting it next to finished hours draws a drop that is not real — either leave it out or mark it. |
{
"generatedAt": "2026-09-25T19:27:31.937Z",
"recordsSince": "2026-09-16T10:44:31.619Z",
"totals": {
"paidWei": "101963211476600458843038",
"payoutCount": 1601,
"minersPaid": 15
},
"hourly": [
{
"t": "2026-09-24T20:00:00.000Z",
"wei": "804085706585945730510",
"payouts": 15,
"partial": false
},
{
"t": "2026-09-24T21:00:00.000Z",
"wei": "963755533582379854460",
"payouts": 19,
"partial": false
},
"\u2026"
],
"note": "\u2026"
}GET /api/v1/blockscached 30 s
Blocks the pool found. limit is 1–200, default 50.
| Field | Type | Meaning |
|---|---|---|
| foundTotal | number | Every block on record. |
| detailAvailableFor | number | How many the pool still holds height and reward
for. It keeps them only while a block is being settled, so this is far smaller than
foundTotal, and the list is a recent tail, not the history. Summing
it does not give what the pool has earned. |
| returned | number | How many this call returned, after limit. |
| blocks[].rewardWei | string | The nominal reward the pool credits for
this block — not what arrives in the wallet. The two differ substantially: this figure
is around 231.8 BDAG while what actually lands is blocks.rewardUnitWei in
/api/v1/pool, about 162.8. Do not multiply this by a block
count to get earnings. |
| blocks[].feesWei | string | The pool's own fee on this block, not
transaction fees. It is exactly rewardWei × the fee in
/api/v1/pool — 1% today — and it is already included in rewardWei, not
additional to it. The name is kept because renaming a field would break v1; what it means is
this. Transaction fees are not recorded separately: the pool stores one reward figure per block,
with whatever fees the block carried folded into it. |
| blocks[].status | string | Where the block is in confirmation, as the pool
records it — for example MATURE. |
| blocks[].canonical | boolean / null | Whether the block is on the canonical chain.
false: dropped by a reorg — the pool still calls it MATURE, but no reward came
from it. null: not checked yet, which is the case for a block under two minutes old or while
our node is behind. Checked again for an hour after it is found, because a later reorg can still drop
it. |
{
"generatedAt": "2026-09-25T19:27:31.943Z",
"foundTotal": 648,
"detailAvailableFor": 161,
"returned": 2,
"blocks": [
{
"height": "17559049",
"hash": "1ba02422b29c2031288125b4544a8a233c815bff07df2ada9379787b02000000",
"rewardWei": "231783437600000000000",
"feesWei": "2317834376000000000",
"status": "MATURE",
"foundAt": "2026-09-25T19:25:20.151Z",
"canonical": null
},
"\u2026"
],
"note": "\u2026"
}Three counts of blocks, and why they differ
They look like they should be the same number and they are not. None of them is wrong.
| foundTotal | Every block on record, including ones imported from the pool's earlier database when it was rebuilt. The long-run count. | |
| credited | Blocks the pool was credited for inside its payout accounting — what the miners' shares were divided against. It covers the period the current accounting has been running, so it is smaller. | |
| rewardsReceived | Rewards that actually arrived in the pool wallet. When a
credited block's reward never lands, this is lower than credited. The gap is a real
thing that happened, not a rounding error, and it is published rather than smoothed away. | |
| rewardUnitWei | What one block's reward is worth when it lands, measured
from the wallet. Well below the rewardWei printed on a block in
/api/v1/blocks, which is the nominal figure. To estimate
what the pool earned, multiply this, not that. |
What is deliberately missing
- Miner addresses. This API never publishes them, in full or in
part beyond the last four characters — the chain does, as the note below says. Use
idto follow a miner here. - Per-miner earnings, balances and amounts waiting. Pool-wide totals are published; the per-miner versions are not.
- Per-miner payout history. Aggregated hourly payouts are published for the pool as a whole.
- Peak hashrate. Hashrate here is an estimate, and the peak of a noisy estimate is mostly noise: it reports the luckiest minute, not the machine. Averages over a stated window are published instead.
- Per-miner connection counts. The pool's own counter is pool-wide, and the per-miner view reconstructed from its log over-counts connections that were never seen closing. Publishing it would mean publishing a number known to be wrong.
- The miner list with addresses. The list on this site is built from
/api/v1/miners, and the endpoint that carried addresses and totals is no longer reachable from outside.
Not because payments are private — they are not. This pool pays from a wallet whose address it publishes, so every payment it has ever made, to whom and for how much, is already on the chain. Anyone can open that wallet in the explorer and read the lot. Nothing here is hidden that is not a click away, and the pool does not pretend otherwise.
What this API declines to do is assemble it for you: one request returning every miner's address beside their running total is a different object from the same facts scattered across a chain, and it is the convenient form that gets saved, sorted and reused. The per-address lookup on the mining page still works — it needs the address, which is the point.
And to be straight about the names: a name is public because a miner chose to publish it, and it sits next to four characters of their address. That is enough to connect a name to an address, given the payments on the chain. The name form says so before anyone signs. This API does not publish the pairing, but it would be dishonest to call it unlinkable.
Fair use
No key, no quota, no sign-up. Honour Cache-Control — asking more often than an
endpoint changes gets you the same bytes and nothing else. If you are building something that needs
more than the rate limit allows, or you want a field that is not here, say so on the
contact page; it is easier to add a field than to have you scrape a
page.