Example API request
The monitoring API lets your software read your mining activity automatically: account hashrate, worker status, share statistics, fees, estimated earnings, and confirmed payout-output history. Access is read-only.
https://api.dmnd.work/api/v1This Python example reads the latest account hashrate. It uses the following credentials and permission:
- Monitoring token. A dedicated token created in the dashboard’s API Keys page for the exact account being monitored.
- Required permission. The token needs the
hashrate_readscope. - Secret storage. The token is stored in the monitoring system’s secret store and supplied through the secret manager as
DEMAND_MONITORING_TOKEN.
import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.dmnd.work/api/v1/user/hashrate",
headers={
"Authorization": "Bearer " + os.environ["DEMAND_MONITORING_TOKEN"],
"Accept": "application/json",
},
)
with urllib.request.urlopen(request, timeout=20) as response:
snapshot = json.load(response)
print(snapshot)
This example makes one request, sets a timeout, and prints the decoded response without logging the token. It does not retry. Before scheduling it to run repeatedly, add error handling and follow the polling guidance.
What you’ll get back: your account ID, the snapshot’s observed_at timestamp, and separate PPLNS, FPPS, and total rates. See the example response →
API keys & permissions
A monitoring token is a secret that tells DMND which account and data your tool may access. Send it in the Authorization header on every request.
Authorization: Bearer <token>
Create a dedicated monitoring key in the dashboard’s API Keys page, selecting the exact account and only the required scopes. Mining tokens and FPPS credentials won’t work here. Programmatic clients should send no session cookies; if both a session and a bearer token are supplied, the bearer token decides the account and permissions.
| Permission (scope) | What it lets you read |
|---|---|
hashrate_read | Current and historical PPLNS, FPPS, and total hashrate. |
workers_read | Worker names, online status, hashrates, and accepted/rejected counters. |
rejects_read | Accepted and rejected share counter changes over a time window. |
fees_read | Pool and broker fee rates. |
earnings_read | Generated FPPS BTC, projected PPLNS BTC, and confirmed payout history for recorded addresses. |
Keep the token private. Give each key only the permissions your tool needs. For new integrations, keep tokens out of request URLs: /api/v1 rejects query-string credentials. The legacy exception is explained below. Worker names can identify sites, and earnings can reveal business activity. Anyone with a watcher link can access the data it exposes, so treat the entire link as a secret. Don’t include it in support tickets or third-party link previews. Keep bearer headers, API secrets, watcher links, and responses that create or list credentials out of request logs, traces, and alerts.
Expiry, replacement keys, and older integrations
Tokens have no expiration by default. An owner can choose an explicit future expiry. Check the listed expiry for each key, since existing keys retain their stored dates. Each target account may have at most 100 active tokens.
Revoking a token prevents authentication on subsequent requests; requests already authenticated may finish. If a key expires or must be rotated, create a replacement, update the monitoring secret, check a successful read, and then revoke the old key.
Token creation and revocation use the signed-in dashboard, outside /api/v1.
The signed-in owner can retrieve available secrets and open or copy watcher links from API Keys. Only active dedicated monitoring-key records authenticate, including preserved historical keys.
The dashboard’s unversioned /api routes support existing watcher workflows. Existing unversioned monitoring GET URLs may use a single token query parameter for compatibility. Don’t use that pattern for new integrations: URLs can leak through logs and link previews. Combining a query credential with an Authorization header, supplying duplicate query credentials, or sending any query credential to /api/v1 is rejected.
Choose what you want to read
Each path below is relative to the base URL. All endpoints use GET to read the data described in this reference.
| I want to… | GET path | Permission |
|---|---|---|
| Check current hashrate | /user/hashrate | hashrate_read |
| Chart past hashrate | /user/hashrate/historical | hashrate_read |
| List live workers | /workers | workers_read |
| Include offline workers | /workers/all | workers_read |
| Check accepted / rejected shares | /user/share_stats | rejects_read |
| Look up my fee rates | /user/fees | fees_read |
| Read daily estimated earnings | /user/generated_btc | earnings_read |
| Read confirmed payout outputs | /user/payouts | earnings_read |
These routes read the token’s account. Earnings and payouts also include directly owned subaccounts. To select one exact account, use the subaccount routes.
Detailed PPLNS projections and the combined dashboard summary are dashboard interfaces outside the versioned monitoring contract. Mining telemetry, payment initiation, registration, and session management are also outside this API.
Hashrate
/user/hashrateRequires hashrate_read
Read the latest account snapshot, with separate PPLNS, FPPS, and total rates. This endpoint returns all three in raw hashes per second (H/s).
{
"account_id": "9223372036854775807",
"observed_at": "2026-09-08T10:00:00Z",
"pplns_hashrate": 150000000000000,
"fpps_hashrate": 50000000000000,
"total_hashrate": 200000000000000
}
Snapshots are normally saved every two minutes using fresh worker telemetry. A worker counts as online while its latest telemetry is less than ten minutes old. These are periodic snapshots, so they may differ from an instantaneous miner reading.
Check the timestamp as well as the rate. Use observed_at to detect stale collection separately from low hashrate. If there is no snapshot yet, observed_at is null and all rates are zero.
Looking back in time
/user/hashrate/historicalThis returns an array of snapshots in ascending observed_at order—oldest first. Supply from and to as RFC3339 timestamps or YYYY-MM-DD dates. Omitted bounds default to the last 24 hours.
| Parameter | How to use it |
|---|---|
from | Start timestamp (RFC3339) or UTC date, such as 2026-09-01. A date alone starts at 00:00:00 UTC. |
to | End timestamp or UTC date. A date alone ends at 23:59:59 UTC. |
Each request covers at most seven days and returns at most 6,000 points. Approximately 100 days are retained. To collect a longer history, request successive windows up to seven days each and remove duplicate boundary timestamps. Fix the range before your first request so its end does not move between pages. An empty array means there are no snapshots in that window.
Workers
/workers/workers/allRequires workers_read
Use /workers for live workers, or /workers/all to include offline workers. Results come in pages of 200 workers by default; use limit to request up to 1,000.
Responses have the shape {"workers": [...], "next_cursor": "..."}. Pass next_cursor back unchanged as cursor for the next page; it is opaque, so your client should not interpret it. When it is null, you’re done.
| Field | What it means |
|---|---|
name | The worker’s name. |
is_connected | Whether the worker is online. |
connected_at | A Unix timestamp in milliseconds, or null. |
hashrate, fpps_hashrate | Rates in raw H/s, including on subaccount worker routes. |
total_shares, rejected_shares | PPLNS share counters. The equivalent FPPS counter names begin with fpps_. |
Missing telemetry for a payout mode is null. Worker counters can reset, disconnected miners can affect changes between readings, and the worker list can change while you fetch pages.
Estimated earnings, day by day
/user/generated_btcRequires earnings_read · Includes directly owned subaccounts
Read daily generated FPPS BTC alongside projected PPLNS BTC. Results are an array ordered by entry_day, newest first. Every day is measured in UTC.
Estimated earnings are not confirmed payments. PPLNS projections can change and exclude amounts already earned from found blocks. The combined total isn’t a paid balance, payout guarantee, or settled accounting record. Use confirmed payout history to see transaction outputs.
| Parameter | How to use it |
|---|---|
date | One UTC day: YYYY-MM-DD. Don’t combine with before or limit. |
before | The last UTC day to include, inclusive. Defaults to today. |
limit | Number of calendar days, not rows. Defaults to 30; maximum 100. |
| Field | What it means |
|---|---|
entry_day | The work day, formatted YYYY-MM-DD in UTC. |
fpps_btc_generated | Generated FPPS earnings in BTC. |
pplns_btc_generated | Cached future PPLNS projected BTC attributed to this work day. |
btc_generated | The sum of the FPPS and projected PPLNS values. |
hashrate | The FPPS-derived daily hashrate in H/s; zero for a day with only PPLNS projections. |
pplns_hashrate | PPLNS hashrate from recorded accepted-share work, in H/s. null means unavailable. |
Collecting older earnings
Use the preceding calendar window to collect older data. Missing mining days are omitted, so a page with missing days does not mean older data is unavailable.
For both account and subaccount routes, a request for one specific date with no earnings returns one zero-earnings row. Its PPLNS hashrate still follows the independent recorded-work availability rules. The FPPS earnings accumulator normally runs every five minutes.
Why PPLNS hashrate can be null, and how it is calculated
Keep null distinct from zero. A covered day that was read successfully and had no recorded PPLNS work returns pplns_hashrate: 0. null means the rate is unavailable, whether due to age, incomplete coverage, or a temporary refresh problem. Unavailable rates are never reported as zero.
Coverage is limited to today and the previous 99 UTC days, starting no earlier than the first complete UTC day after the database’s explicit-UTC share-timestamp migration. Earlier timestamps cannot establish that a full day’s work was recorded with the correct date. Older earnings rows remain available with pplns_hashrate: null. Recorded work alone does not create an earnings row.
The rate uses accepted shares grouped by the UTC day the database recorded them. Delayed ingestion can place work in a later day; miner-supplied ntime does not set the day. For completed days the calculation is sum(difficulty) * 2^32 / 86400. Today uses elapsed whole seconds since UTC midnight. Rates are rounded to two decimal places.
This is separate from worker telemetry, the difficulty in the moving PPLNS reward window, and the FPPS-derived hashrate field.
For current monitoring, use ?date=YYYY-MM-DD with today’s UTC date every two to five minutes. For older data, use smaller windows and wait at least 60 seconds before retrying missing rates.
The supplemental rate is cached for 60 seconds, with up to 128 account/range combinations, one concurrent refresh per server, and three refresh attempts per target account per minute. Ownership is checked before cache lookup. Refreshes have a five-second SQL deadline; large histories can exceed it. Busy refreshes, exhausted budgets, errors, and invalid work values return null without failing or changing the earnings response. Failed refreshes are also cached for 60 seconds.
Compatibility note: unversioned dashboard earnings routes under /api return complete available history when these parameters are omitted, so dashboard history and exports do not need pagination. New integrations using /api/v1 use the bounded defaults above.
Confirmed payouts
/user/payoutsRequires earnings_read · Includes directly owned subaccounts
Read confirmed FPPS and PPLNS transaction outputs sent to your account’s recorded Bitcoin addresses, including inactive addresses. Unrelated recipients, transfers back to funding addresses, unrelated deposits, and unconfirmed transactions are excluded. This API only reads history; it never initiates payments or changes payout addresses.
This is history associated with addresses. Address records have no effective ownership dates, so even an unshared recorded address does not prove who owned it at the time of a historical payment. This is not an account ownership ledger. If an address is also recorded against an account outside the requested group, the entire request returns 409 with payouts-ambiguous-address. The API does not guess ownership or silently leave that address out.
| Parameter | How to use it |
|---|---|
from, to | Inclusive UTC dates, YYYY-MM-DD. No future dates. to defaults to today; from defaults to 29 days before it. Maximum 30 calendar days. |
limit | Outputs per page: 1–100. Defaults to 100. |
cursor | The previous next_cursor, unchanged. Keep the same explicit dates and target account across pages. |
https://api.dmnd.work/api/v1/user/payouts?from=2026-09-01&to=2026-09-09&limit=100
{
"payouts": [
{
"txid": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"output_index": 2,
"kind": "fpps",
"address": "<recorded Bitcoin address>",
"amount_sats": 125000,
"confirmed_at": 1788912000
}
],
"next_cursor": null,
"as_of": 1788912300,
"from": "2026-09-01",
"to": "2026-09-09"
}
amount_satsis an integer number of satoshis, never a floating-point BTC amount.txidand the zero-basedoutput_indextogether identify a unique transaction output. Each pair appears once.kindisfppsorpplns.confirmed_atis the block timestamp in Unix seconds, not the payment request time.as_ofis when the server retrieved the snapshot, also in seconds.next_cursor: nullmeans the last page. Rows are ordered descending by confirmation timestamp, then transaction ID and output index.
An empty page means there are no matching confirmed outputs in a fully retrieved window, or no recorded recipient addresses. A failed or incomplete provider read returns an error, never a successful empty or partial history.
When a payout snapshot changes
Bitcoin confirmations can change after a chain reorganization. If you receive 409 payouts-cursor-changed, restart without a cursor using the same fixed date window. Compare the replacement results with what you collected, using txid and output_index, so removed or changed outputs are reconciled.
A change to the funding snapshot, requested dates, target account, or recorded recipient addresses invalidates the cursor with 409 payouts-cursor-changed. A funding change can affect another recipient, even if your own payouts look unchanged.
Payout refresh limits and history coverage
Poll every few minutes, reuse collected pages, and backfill one fixed window at a time. Set a client timeout that allows for a 20-second provider deadline plus authentication and database work.
Chain snapshots are cached for at most 60 seconds, with up to four windows per server process. One provider fill runs at a time, with at most six uncached windows per fixed 60-second interval across all customers on that process. These limits are in addition to normal API budgets. On 429, follow Retry-After.
Provider reads are bounded to 20 pages per funding source, 1 MiB per upstream response, and 50,000 outputs per window. Older windows can exceed the scan-depth limit even with few payouts, because scans begin at the newest transaction.
Missing configuration, provider failures, timeouts, incomplete scans, or inconsistent data return 503. Respect Retry-After. After cache expiry, the service retrieves fresh history and does not serve stale data if that refresh fails.
Funding inputs must match configured FPPS/PPLNS sources or the historical FPPS source bc1qwk7rplr3ztrsvlcxujnxdvp77atpqawlh94wgw. Only outputs to authorized recorded recipient addresses are returned, without duplicates when several inputs match or an address is shared within the requested group.
Payout-history endpoints exist only under /api/v1. Dashboard payout-address discovery and watcher links are separate interfaces.
Monitoring subaccounts
A token can read its own account and directly owned subaccounts, within its selected permissions. A key created for a subaccount cannot read the master account or sibling subaccounts.
To read one exact account, replace {id} in the paths below with that account’s ID. Unlike the main earnings and payout routes, these routes do not include that target’s subaccounts.
Keep account IDs as strings. They are decimal strings and can be negative. Preserve the exact value; don’t convert it to a JavaScript number.
| GET path · exact target only | Permission |
|---|---|
/user/sub_account/{id}/hashrate | hashrate_read |
/user/sub_account/{id}/hashrate/historical | hashrate_read |
/user/sub_account/{id}/workers | workers_read |
/user/sub_account/{id}/share_stats | rejects_read |
/user/sub_account/{id}/fees | fees_read |
/user/sub_account/{id}/generated_btc | earnings_read |
/user/sub_account/{id}/payouts | earnings_read |
One important difference: hashrate units
Subaccount hashrate snapshots return each rate as a value/unit object, rounded to two decimal places, using TH/s or PH/s. This applies to current and historical hashrate routes. Worker rates still use raw H/s.
{
"account_id": "1234567890123456789",
"observed_at": "2026-09-08T10:00:00Z",
"pplns_hashrate": {"value": 150.0, "unit": "TH/s"},
"fpps_hashrate": {"value": 50.0, "unit": "TH/s"},
"total_hashrate": {"value": 200.0, "unit": "TH/s"}
}
Outputs to an address shared entirely within an aggregate account group can appear in that group’s history. Each (txid, output_index) appears once. The corresponding exact-account request is ambiguous and returns 409 payouts-ambiguous-address when another recorded owner is outside that exact target.
How often should I check?
Every two to five minutes is an appropriate interval for current statistics, matching the snapshot cadence. Check payouts every few minutes, too.
- Save collected data in your own monitoring store. Fetch only what you need instead of the entire history each time.
- Stagger requests across accounts and limit how many run at once.
- Set connection and request timeouts. Give payout requests enough time for their provider lookup.
| Budget | Requests | Shared by |
|---|---|---|
| Per token | 240 | All requests using that token. |
| Per token account | 600 | All tokens for the same account. |
| Per client IP | 1,200 | Requests from that IP across protected API paths. |
These limits apply per server process, not as a guaranteed cluster-wide allowance. Payout lookups have additional request budgets. Edge protection or temporary capacity limits may also reject traffic. Always obey a 429, even if your own request count is lower. The separate limits for supplemental PPLNS-rate refreshes return null when a refresh is unavailable, as described under estimated earnings.
If you’re asked to slow down: read the Retry-After header on a 429 response. Wait at least that many seconds, then add a small random delay.
Account responses use Cache-Control: private, no-store. Do not record bearer headers, API secrets, watcher links, or responses that create or list credentials in request logs, traces, or alerts.
When a request doesn’t work
Errors are JSON objects with code and message strings. Use the HTTP status to decide whether to retry. The message is for diagnosis and may change.
| HTTP status | What it means | What to do |
|---|---|---|
| 200 | The request worked. | Read the result. Empty data can be valid. |
| 400 | Invalid parameters, URL credentials, or an out-of-range limit. | Fix the request before trying again. |
| 401 | The key is missing, invalid, revoked, or expired. | Check the bearer header and the key in API Keys. Don’t retry unchanged. |
| 403 | The key lacks a scope or access to the target account. | Check the selected permissions and account. Don’t retry unchanged. |
| 404 | Unknown endpoint or unavailable resource. | Check the base URL, path, and requested resource. |
| 409 | Payout address attribution is ambiguous, or the pagination snapshot changed. | Check the error code and the guidance below. |
| 429 | A request or capacity budget was reached. | Wait for Retry-After seconds, plus a small random delay. |
| 500 / 503 | A backend failure or temporary unavailability. | For transient failures, retry with bounded exponential backoff. Respect Retry-After when supplied. |
Two payout errors to handle differently
payouts-ambiguous-address: an address is recorded against an account outside your requested group. The entire request returns 409 instead of guessing attribution or silently omitting that address.
payouts-cursor-changed: the pagination snapshot or request context changed. Restart the same fixed window without a cursor and reconcile your saved outputs with the replacement results.
For transient server errors, retry with bounded exponential backoff. Don’t keep retrying 400, 401, or 403.
Versioning and compatibility
Ignore response fields your client doesn’t recognize. New fields can be added within v1. Incompatible response or authentication changes require a new API version and a documented migration. This interface does not imply an availability SLA.
Reading the values
- Account and subaccount hashrate
- The account hashrate endpoint returns raw H/s. Subaccount hashrate routes return value/unit objects in TH/s or PH/s, rounded to two decimal places. Worker hashrates remain raw H/s on both account and subaccount routes.
- Estimated earnings and payout history
- Generated-BTC results combine generated FPPS BTC with cached future PPLNS projections. That combined amount is not a paid balance, payout guarantee, or settled accounting record. Payout history reports confirmed outputs associated with recorded addresses.
- Dates and timestamps
- Earnings days and payout date windows use UTC. Worker
connected_atvalues use Unix milliseconds ornull; payoutconfirmed_atandas_ofvalues use Unix seconds. - Missing PPLNS hashrate
- For earnings rows,
pplns_hashrate: nullmeans the rate is unavailable. A covered day that was read successfully and had no recorded work returnspplns_hashrate: 0. Unavailable rates are never reported as zero. Do not replacenullwith zero. - Pages of results
- Worker and payout responses use
next_cursorto continue to the next page;nullmarks the last page. Earnings use calendar-day windows instead.
Monitoring keys are managed in the dashboard.
The request example above uses a key with hashrate_read to read the account’s latest hashrate.