Pool API · v1 · chain ID 1404

BlockDAG pool API

The pool, as JSON: everything this pool publishes about itself — read-only, no key, no sign-up, open to any origin. If you are building a dashboard, a bot or a chart on top of it, this is the surface to build on.

Try it

bash
curl https://dagcore.net/api/v1/pool

Every 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

AmountsDecimal strings in wei, 18 decimals. Strings, because JSON numbers cannot hold them exactly — parse them with a big-integer type, not Number.
TimesISO 8601, always UTC, always with a Z.
HashrateH/s, and always an estimate — see below.
ErrorsAlways 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 limit2 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-AgentPlease 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:

json
{
  "error": "bad_parameter",
  "message": "range must be one of 1h, 24h, 7d."
}

Over the rate limit — HTTP 429, with Retry-After:

json
{
  "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:

json
"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

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.

json
{
  "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.

FieldTypeMeaning
statusstringok; 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.
dataAgeSecondsnumberHow old the underlying figures are. null when nothing could be read.
responseMsnumberHow long this check itself took.
json
{
  "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.

FieldTypeMeaning
schemestringHow rewards are split. PPLNS.
fee.bpsnumberThe 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.percentstringThe same number as a percentage, for display.
stratum[]arrayHow 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.windowSecondsnumberHow often the pool settles.
payout.minimumWeistringBelow this, a miner's share is carried to the next settlement instead of being sent.
payout.confirmationsnumberConfirmations required before a block's reward is treated as final.
hashrate.currentestimateRecent pool hashrate. overSeconds says how long a window it came from.
hashrate.last24hestimateThe same over a day — steadier, and the one to quote.
miners.activenumberMiners that sent a share within activeWithinMinutes.
miners.seennumberMiners on record at all, including long-idle ones.
miners.connectionsnumberOpen stratum connections. One miner can hold several.
blocks.*numbersThree different counts. See the section below — they are not meant to agree.
blocks.canonicalTotalnumberfoundTotal 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.orphanedobject / nullWhy 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.totalWeistringEverything the pool has paid out across all miners since recordsSince — not since the pool began, if its records were rebuilt.
paid.payoutCountnumberHow many individual payments that was.
paid.minersPaidnumberHow many distinct miners have been paid.
recordsSincetimeWhen the pool's records begin. Totals cover this period, not all of time.
lastBlockobjectThe most recent block: height, hash, status, when it was found. null if none is on record.
json
{
  "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.

FieldTypeMeaning
idstringA 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.
windowstringThe window this response was computed over: 10m, 1h or 24h, as asked, 1h when not asked. hashrateWindowMinutes says the same in minutes.
ranknumberPosition by hashrate over the chosen window, 1 is largest.
namestring / nullThe 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.
addressSuffixstringThe last four characters of the address, so a miner can recognise itself in the list. Four characters collide; do not key on it.
hashrateestimateThat miner's estimated hashrate over hashrateWindowMinutes.
shareOfPoolnumberIts fraction of pool hashrate over the same window, 0 to 1.
difficultynumber / nullThe 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.
sharesPerMinutenumber / nullAccepted shares per minute over the same window. With VarDiff working, this stays near constant while difficulty moves instead.
hardwareobject / nullA 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.
statusstringmining, idle or offline, from how recently a share arrived.
lastShareAttimeWhen the pool last accepted a share from it.
readThroughtimeHow far the share sampler has read. Figures describe the pool up to this moment, not to generatedAt.
json
{
  "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.

FieldTypeMeaning
estimatedbooleanAlways true, for the reason in method.
points[].ttimeStart of the bucket.
points[].hashratenumberEstimated H/s for that bucket. A gap means no samples, which is not the same as zero hashrate.
json
{
  "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.

FieldTypeMeaning
totals.paidWeistringEverything paid since recordsSince.
hourly[].ttimeStart of the hour, UTC.
hourly[].weistringPaid during that hour.
hourly[].payoutsnumberHow many payments that was.
hourly[].partialbooleanTrue 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.
json
{
  "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.

FieldTypeMeaning
foundTotalnumberEvery block on record.
detailAvailableFornumberHow 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.
returnednumberHow many this call returned, after limit.
blocks[].rewardWeistringThe 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[].feesWeistringThe 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[].statusstringWhere the block is in confirmation, as the pool records it — for example MATURE.
blocks[].canonicalboolean / nullWhether 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.
json
{
  "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.

foundTotalEvery block on record, including ones imported from the pool's earlier database when it was rebuilt. The long-run count.
creditedBlocks 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.
rewardsReceivedRewards 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.
rewardUnitWeiWhat 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 id to 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.