Get Bitcoin Fee Recommendations from Satoshi API
Purpose
Return current Bitcoin fee-rate recommendations (sat/vB) for five confirmation horizons — next block, half hour, hour, economy, and minimum — by calling the Satoshi API's free /api/v1/fees/recommended endpoint. Backed by a live Bitcoin Core node's estimatesmartfee output. Read-only HTTP GET; no API key, wallet, signup, or cookies required.
When to Use
- An agent needs a fee-rate snapshot to decide what
feerateto attach to an outgoing Bitcoin transaction. - "Should I send Bitcoin now or wait?" / "How fast will this confirm at N sat/vB?" / "What's the current mempool fee floor?" questions.
- Periodic polling for fee-monitoring dashboards or send-or-wait alerts (rate limit is 30 req/min anonymous; register for a free key for 10K/day).
- As a substitute for
mempool.space/api/v1/fees/recommendedwhen you want a second source — Satoshi API uses Bitcoin Core's estimator rather than mempool-block-based heuristics, so values can differ during congestion.
Workflow
The Satoshi API exposes a public, no-auth HTTP/JSON endpoint. Use the API path directly — there is no browser-driving step worth doing, and the response is canonical JSON behind Cloudflare with a 10-second Cache-Control window. The endpoint accepts no query parameters or headers (besides standard Accept: application/json).
-
Send the request.
GET https://bitcoinsapi.com/api/v1/fees/recommended Accept: application/jsonNo body, no auth, no cookies. Anonymous tier permits 30 req/min (see
X-RateLimit-Limit/X-RateLimit-Remainingresponse headers). For higher limits register a free key viaPOST /api/v1/registerand sendX-API-Keyon subsequent calls (10K req/day free tier). -
Parse the JSON envelope. The response is wrapped in a top-level
{ "data": {...}, "meta": {...} }shape:{ "data": { "recommendation": "Fees are very low. 1.0 sat/vB should confirm within a day.", "estimates": { "1": 1.087, "3": 1.087, "6": 1.0, "25": 1.0, "144": 1.0 }, "savings_estimate": { "...": "..." } }, "meta": { "timestamp": "2026-05-19T04:10:28.815158+00:00", "node_height": 950030, "chain": "main", "syncing": false, "cached": true, "cache_age_seconds": 0 } } -
Map the
data.estimatesobject (keyed by Bitcoin Core confirmation target in blocks) onto the requested field names. The numeric values are fee rates in sat/vB:fastestFee←data.estimates["1"](next block, ~10 min)halfHourFee←data.estimates["3"](~30 min, 3 blocks)hourFee←data.estimates["6"](~1 hour, 6 blocks)economyFee←data.estimates["25"](~4 hours, 25 blocks)minimumFee←data.estimates["144"](~1 day, 144 blocks)
-
Fill the meta fields.
units←"sat/vB"(constant; the API contract is satoshis per virtual byte and is not stated in the response body — the unit is implied by therecommendationtext and Bitcoin Core'sestimatesmartfeeoutput convention).source←"bitcoin-core-estimates"(this is the canonical source string the paid/api/v1/fees/nowendpoint advertises for the same upstream; alternatively"bitcoinsapi.com"if you want a provider-level attribution).timestamp←data.meta.timestamp(ISO 8601 with microseconds and+00:00offset).
-
(Optional) Sanity-check the node is healthy before relying on the result:
meta.syncingmust befalseandmeta.node_heightshould be within ~6 blocks of the current chain tip (https://bitcoinsapi.com/api/v1/statusreturns full node state if you want a second check). -
(Optional, paid) Upgrade to
/api/v1/fees/nowfor richer send-or-wait context (verdict string, mempool-pressure label, plus the same five fee rates already namedfastest_fee_sat_vbetc.). This endpoint returns HTTP 402 Payment Required without payment — settle $0.001 USDC on Base via the x402 protocol and resend with aPAYMENT-SIGNATUREheader (or just shell out tonpx agentcash@latest fetch "https://bitcoinsapi.com/api/v1/fees/now" --payment-network base --max-amount 0.001). The free/fees/recommendedendpoint is sufficient for the field set in this skill — only escalate to the paid path when the caller specifically needs therecommendation/mempool_pressureverdict strings or has an x402 wallet ready.
Site-Specific Gotchas
- Response shape is wrapped in
{data, meta}— the five fee rates are NOT top-level fields. Naïvely readingresponse.fastestFeereturnsundefined. The actual rates live underdata.estimates, keyed by string-encoded block confirmation targets ("1","3","6","25","144"). - The confirmation-target keys are strings, not numbers.
response.data.estimates[1]works in JavaScript via implicit coercion but fails in strictly-typed languages. Useresponse["data"]["estimates"]["1"]. fastestFeeandhalfHourFeeare frequently equal during low-mempool periods — Bitcoin Core'sestimatesmartfeeclamps multiple short horizons to the same floor (observed 1.087 sat/vB for both targets 1 and 3 during validation). This is expected behavior, not a bug; do not de-duplicate fields based on equal values.- Values are floats, not integers. Free-tier output rounds to ~3 decimal places (e.g.
1.087, not1). When constructing actual Bitcoin transactions, round up withMath.ceil(rate * 100) / 100or use the integer floor (1) — paying below the network minrelayfee of 1 sat/vB causes transaction rejection. - No
unitsfield in the response. The API does not echo a units string; sat/vB is the implicit, hard-coded Bitcoin Core convention. Do not invent adata.unitsfield — hard-code the string"sat/vB"in your output. - No
sourcefield on the free endpoint either. Only the paid/api/v1/fees/nowresponse carries"source": "bitcoin-core-estimates". For the free endpoint, setsourceto"bitcoin-core-estimates"(upstream) or"bitcoinsapi.com"(provider) as a constant — the API itself does not declare it. - Cloudflare caches the response for 10 s (
Cache-Control: public, max-age=10,meta.cached: true). Polling faster than every 10 seconds returns the same payload. Use themeta.timestampto detect refresh boundaries; do not rely onDateheader for freshness. - Rate limit is 30 req/min on the anonymous tier. Headers
X-RateLimit-Limit: 30andX-RateLimit-Remaining: Nindicate burst capacity;X-RateLimit-Resetis the Unix epoch when the window resets. Exceeding the limit returns HTTP 429. For sustained polling register a free API key (POST /api/v1/register) which grants 10K req/day. X-RateLimit-Daily-Limit: 0on anonymous calls means "no separate daily cap", not "you're throttled". The per-minute cap is the only constraint without a key.- The site responds with an
X-Data-Disclaimerheader ("For informational purposes only. Not financial advice.") — surfacing this in agent output is polite but not required. /api/v1/fees/nowreturns 402, not 401 or 403, when unpaid. Treating 402 as an auth error and switching to API-key headers does not help — this is a Coinbase x402 micropayment paywall ($0.001 USDC on Base,eip155:8453), not a key-auth gate. The response body includes the completePAYMENT-REQUIREDenvelope and anagentcash_fetch_commandthat performs the payment. The free/fees/recommendedendpoint provides the same five fee rates; only escalate to/fees/nowwhen the verdict / mempool-pressure verbiage is needed.- Don't waste time scraping
bitcoinsapi.comHTML — the landing page is a marketing site with no fee data in the DOM; everything useful is at the JSON endpoints. Thehttps://bitcoinsapi.com/api/v1/agent-contextandhttps://bitcoinsapi.com/.well-known/satoshi-agent-context.jsonURLs return well-structured discovery documents listing every endpoint and recipe. - The endpoint is served via Cloudflare with no anti-bot or stealth-detection layer. A residential proxy, captcha solver, or
--verifiedBrowserbase session is unnecessary. Plainfetch/curl/requestsworks from any IP. meta.node_heightlags the chain by ~0–2 blocks. During the validation run the node reported height950030andsyncing: false; ifsyncing: trueever appears, the estimates may reflect a pre-sync state — fall back to another source.
Expected Output
{
"fastestFee": 1.087,
"halfHourFee": 1.087,
"hourFee": 1.0,
"economyFee": 1.0,
"minimumFee": 1.0,
"units": "sat/vB",
"source": "bitcoin-core-estimates",
"timestamp": "2026-05-19T04:10:28.815158+00:00"
}
JSON schema:
{
"type": "object",
"required": ["fastestFee", "halfHourFee", "hourFee", "economyFee", "minimumFee", "units", "source", "timestamp"],
"properties": {
"fastestFee": { "type": "number", "description": "Fee rate for next-block confirmation (~10 min), in sat/vB." },
"halfHourFee": { "type": "number", "description": "Fee rate for ~30-min confirmation (3 blocks), in sat/vB." },
"hourFee": { "type": "number", "description": "Fee rate for ~1-hour confirmation (6 blocks), in sat/vB." },
"economyFee": { "type": "number", "description": "Fee rate for economy confirmation (~25 blocks, ~4 h), in sat/vB." },
"minimumFee": { "type": "number", "description": "Fee rate for minimum-priority confirmation (~144 blocks, ~1 day), in sat/vB." },
"units": { "type": "string", "enum": ["sat/vB"], "description": "Constant — Satoshi API does not echo a units field; sat/vB is the Bitcoin Core convention." },
"source": { "type": "string", "description": "Upstream estimator, e.g. \"bitcoin-core-estimates\" (preferred) or \"bitcoinsapi.com\" (provider)." },
"timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 UTC timestamp from response meta.timestamp; reflects cache-tick freshness, not wall-clock request time." }
}
}
When the node reports syncing: true or meta is missing, return the same object with timestamp set to the HTTP Date response header as a fallback, and add an extra "warning": "node syncing" field so the caller can decide whether to retry against a secondary source.