Appearance
Quickstart for agents
The whole point of HardBasis's API is that an agent can go from nothing to a live, hedged, self-limiting trading loop without a human step. This page is that loop, end to end, as copy-pasteable curl. Every response is a decimal- string JSON body (see Wire encoding); pipe through jq.
Verified live 2026-08-11 against gateway 94f4623 — all 8 steps
This whole sequence was run end to end against the deployed testnet gateway on 2026-08-11 (gateway SHA 94f4623), withdrawal included via the Spark path (settled to state:"paid"). A Lightning payout is not exercisable on testnet's sim rail — see step 8 — so the withdrawal example uses Spark.
Set your base URL once:
bash
export HB=https://testnet.hardbasis.com1. Sign up (testnet self-serve)
Testnet lets you mint an account with no operator action. Signup returns a full-scope master key — shown exactly once, so save it.
bash
curl -fsS -X POST "$HB/v1/signup" \
-H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" -d '{}' | jq .
# → { "accountId": "...", "apiKey": "hb_...", ... }
export MASTER=hb_... # from the response; shown once, never recoverable2. Fund it from the faucet
The testnet faucet credits a fixed amount through the normal deposit path.
bash
curl -fsS -X POST "$HB/v1/faucet" \
-H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" | jq .3. See the market
Read the live market id straight from the list and keep it — every later call uses it, so nothing can drift from what the venue actually serves:
bash
curl -fsS "$HB/v1/markets" | jq '.[0] | {marketId, status, tickSizeQ8, priceDp}'
export MARKET=$(curl -fsS "$HB/v1/markets" | jq -r '.[0].marketId') # e.g. btc-usdpriceDp tells you how many decimal places prices carry; tickSizeQ8 is the minimum price increment (a q8 fixed-point integer — Wire encoding).
4. Stream prices (optional but recommended)
Prices, per-market stats, and your account events come over one WebSocket. See WebSocket for the full protocol.
bash
# with websocat: subscribe to the oracle feed, then the auth'd account channel
websocat "wss://testnet.hardbasis.com/v1/ws" <<'WS'
{"op":"auth","apiKey":"REPLACE_MASTER"}
{"op":"subscribe","channel":"prices","feedId":"btc-usd"}
WS5. Place an order
Size is an integer count of $1-notional contracts (a decimal string). The idempotency-key is the order id, so a retry is safe.
bash
curl -fsS -X POST "$HB/v1/orders" \
-H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" \
-d "{\"marketId\":\"$MARKET\",\"side\":\"buy\",\"type\":\"market\",\"contracts\":\"10\"}" | jq .Check the fill and your position:
bash
curl -fsS "$HB/v1/account" -H "x-api-key: $MASTER" | jq .
curl -fsS "$HB/v1/positions" -H "x-api-key: $MASTER" | jq .6. Delegate a trade-only key for the bot loop
Put the master key cold and run the loop on a trade+read key. A stolen delegate can lose you a position, never your balance — it cannot withdraw. See Authentication.
bash
curl -fsS -X POST "$HB/v1/sessions" \
-H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{"label":"bot","scopes":["read","trade"]}' | jq .
export BOT=hb_... # the delegate key, shown once7. Arm the dead-man's switch
Before the loop runs unattended, arm cancel-all-after: if your bot stops calling in, the engine cancels its resting orders on its own. Re-arm on a timer; disarm by sending a zero deadline.
bash
curl -fsS -X POST "$HB/v1/cancel-all-after" \
-H "x-api-key: $BOT" -H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" -d '{"timeoutMs":"60000"}' | jq .8. Withdraw
Withdrawals need a withdraw-scoped key and always work — they never gate on trading state or halts. Always quote first. The quote (a trade-scoped call) returns the fee before you commit and, for a Spark destination, whether the address is first-seen — a wrong Spark address is irreversible, so a first-seen destination at or above confirmThresholdMsat sets confirmRequired and returns a confirmToken you must echo into the withdrawal.
bash
# Spark (the native rail): quote by address + amount, then withdraw.
curl -fsS -X POST "$HB/v1/withdrawals/quote" \
-H "x-api-key: $MASTER" -H "content-type: application/json" \
-d '{"toAddress":"sprt1q…","amountMsat":"50000000"}' | jq .
# → { "path":"spark","feeMsat":"…","firstSeen":true,"confirmRequired":false,
# "confirmThresholdMsat":"…" } # first-seen ≥ threshold also returns confirmToken
curl -fsS -X POST "$HB/v1/withdrawals" \
-H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{"toAddress":"sprt1q…","amountMsat":"50000000"}' | jq .
# → { "railRef":"…","queued":false }Confirm it settled:
bash
curl -fsS "$HB/v1/withdrawals?limit=1" -H "x-api-key: $MASTER" \
| jq '.withdrawals[0] | {state, path, railRef, paidTsMs}'
# → { "state":"paid","path":"spark", … }Lightning payouts
The same endpoint takes a bolt11 instead of an address — {"invoice":"lnbc…"} on both the quote and the withdrawal. A real deployment on the Spark rail settles either path. On testnet's sim rail a Lightning payout is not exercisable (the sim rail exposes no payable-invoice source over the API, so every bolt11 comes back unknown payout invoice), which is why the runnable example above uses the native Spark path.
That is the whole loop: signup → faucet → market → subscribe → order → delegate a trade-only key → arm cancel-all-after → withdraw, no human in it. Endpoint-by-endpoint details — every field, every response — are in the generated REST reference.