Skip to main content
BlockForecast uses a wallet-signature authentication model: you prove ownership of an EVM address by signing a server-issued challenge, and the API returns a long-lived key tied to that address. Every subsequent request passes this key in the X-API-Key header. There is no username/password and no OAuth flow.
Easiest path: sign in at blockforecast.io/settings/api-keys and tap Generate key. Privy pops the wallet signature, the key is shown once, you copy and store it. Use the programmatic flow below only if you’re issuing keys from CI / a server / a script.

Issue a key (programmatic flow)

Three steps: request a challenge, sign the exact message it returns, post both back. Each nonce is single-use and bound to your wallet + the action (issue, revoke, regenerate), so an intercepted signature can’t be replayed for any other operation.
1

Request a challenge

POST /api/v2/auth/challenge returns a single-use nonce + the exact message your wallet must sign.
curl -X POST https://blockforecast.io/api/v2/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{ "address": "0xYourWalletAddress", "action": "issue" }'
Response shape:
{
  "success": true,
  "data": {
    "nonce":     "<48-hex-chars>",
    "message":   "BlockForecast API Key\nAction: issue\nAddress: 0x...\nNonce: <nonce>\nTimestamp: 1777408738",
    "expiresAt": "2026-04-28T20:43:58.000Z"
  }
}
The challenge expires in 5 minutes. The message format includes the action and your address so a signature for issue can’t be replayed for revoke or regenerate.
2

Sign the exact message

Use EIP-191 personal sign (personal_sign) with the wallet whose address you sent in step 1. Don’t construct your own message — sign the one the server returned verbatim.
from eth_account import Account
from eth_account.messages import encode_defunct
signed = Account.sign_message(encode_defunct(text=message), private_key=PRIVATE_KEY)
signature = signed.signature.hex()
3

Exchange for a key

POST /api/v2/auth/token with the address, signature, the message, the nonce, and an optional label.
curl -X POST https://blockforecast.io/api/v2/auth/token \
  -H "Content-Type: application/json" \
  -d "{
    \"address\":   \"$ADDRESS\",
    \"message\":   \"$MESSAGE\",
    \"signature\": \"$SIGNATURE\",
    \"nonce\":     \"$NONCE\",
    \"label\":     \"prod-bot\"
  }"
Response (the only time the raw apiKey is shown):
{
  "success": true,
  "data": {
    "id":        7,
    "apiKey":    "bf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "prefix":    "bf_xxxxxxxx...",
    "label":     "prod-bot",
    "rateLimit": 60,
    "createdAt": "2026-04-28T20:43:00.000Z",
    "note":      "Store this key securely. It cannot be retrieved again."
  }
}
The server only stores the SHA-256 hash of the key. Even with a database leak, the raw key cannot be reconstructed.

Use your key

Pass it in the X-API-Key header on every request to a /api/v2/public/* endpoint.
curl https://blockforecast.io/api/v2/public/markets \
  -H "X-API-Key: bf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

List your active keys

Returns prefixes (not raw keys), labels, rate limit, scopes, created/last-used timestamps. No auth required — a key’s prefix isn’t a secret. Used by the /settings/api-keys page.
curl "https://blockforecast.io/api/v2/auth/keys?address=0xYourWalletAddress"

Revoke a key

Suspected leak? Revoke instantly. The action is wallet-signed (same nonce flow with action: "revoke" and the key’s id).
# 1. Challenge
curl -X POST https://blockforecast.io/api/v2/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{ "address": "0x...", "action": "revoke", "keyId": 7 }'

# 2. Sign the returned message, then:
curl -X POST https://blockforecast.io/api/v2/auth/keys/7/revoke \
  -H "Content-Type: application/json" \
  -d '{ "address": "0x...", "message": "...", "signature": "0x...", "nonce": "..." }'
The key is invalidated synchronously — any request using it after this point returns 401.

Regenerate a key (atomic rotate)

Same scopes + rate limit, new key value. Useful when you want to rotate without losing your “key slot” or having to update label references downstream.
# 1. Challenge with action: "regenerate"
curl -X POST https://blockforecast.io/api/v2/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{ "address": "0x...", "action": "regenerate", "keyId": 7 }'

# 2. Sign + post — the server atomically revokes the old key and issues a new one
curl -X POST https://blockforecast.io/api/v2/auth/keys/7/regenerate \
  -H "Content-Type: application/json" \
  -d '{ "address": "0x...", "message": "...", "signature": "0x...", "nonce": "..." }'
The response shape matches POST /auth/token — the new key is shown once.

Caps

  • 5 active keys per wallet. Revoke or regenerate to free a slot.
  • Default 60 req/min per key. Higher quotas on request — see Rate Limits.
  • Persistent failed-auth attempts from a single IP are throttled at the edge.

Error responses

HTTPBody error fieldMeaning
400 Bad Requestaddress, signature, message, and nonce are requiredMissing required field on /auth/token etc.
400 Bad RequestNonce invalid, expired, or already used. Request a fresh challenge.Replay attempt or stale nonce — restart from /auth/challenge.
400 Bad RequestMaximum 5 active API keys per wallet…Revoke or regenerate to free a slot.
401 UnauthorizedInvalid or missing API key.Header missing, malformed, or key has been revoked.
401 UnauthorizedInvalid signatureSignature doesn’t verify against the recovered address.
401 UnauthorizedSignature does not match addressRecovered signer is a different wallet.
403 ForbiddenThis API key lacks required scope: <scope>Key is valid but lacks permission for the endpoint.
404 Not FoundKey not found or not owned by this walletBad keyId, or the wallet you signed with doesn’t own that key.
429 Too Many RequestsRate limit exceeded. Max 60 requests per minute.Wait per the Retry-After header.
429 Too Many RequestsToo many failed authentication attempts. Try again later.IP-level brute-force throttle.
All success responses are { "success": true, "data": {...} }; errors are { "success": false, "error": "<message>" }.

Security best practices

Never expose bf_… keys in client-side JavaScript, public repos, or build logs. Anyone with the key can trade and read account data on behalf of the wallet that issued it.
  • Store in your secrets manager (AWS Secrets Manager, Doppler, 1Password, env-file mounted at runtime). Never commit.
  • Use one key per environment (prod-bot, staging-bot, local-dev) — the label keeps you sane and revoke is single-key, not single-wallet.
  • Rotate via /keys/:id/regenerate on a schedule or after any suspected exposure. Old key dies the moment the new one is shown.
  • For server-to-server integrations, give each agent its own dedicated wallet so a leak doesn’t expose your trading wallet’s positions.
In CI / serverless where you can’t interactively sign, generate the key once from a local wallet (or via the /settings/api-keys UI) and inject the resulting bf_… value as a build secret. The key is what production uses; the signing wallet only needs to be online during issuance.