Appearance
Wire encoding
The one thing a client must get right. Every bigint crosses the wire as a decimal string — money, prices, rates, timestamps, contract counts. A JSON number is never used for a monetary value, because an IEEE double cannot hold a satoshi-precise integer without silently rounding it.
So a field's JSON type is string, and it is validated against the pattern ^-?\d+$. Parse it into a big-integer type (BigInt in JS, int/decimal elsewhere) — never parseFloat.
Units: read them off the field
Each numeric field declares its unit in the machine-readable spec as x-hardbasis-unit, and the REST reference prints that unit beside every field. There are a handful:
| unit | meaning | example |
|---|---|---|
msat | money, in millisatoshi | "1000" = one sat |
sats | money, in whole satoshis | "50" = fifty sats |
q8 | a price, fixed-point 1e8 | "6724150000000" = $67,241.50 |
q9 | a rate, fixed-point 1e9 | "300000" = three bps |
ms | a timestamp or duration, in milliseconds | "1712345678000" |
seq | a monotonic sequence number / pagination cursor | "41207" |
contracts | an integer count of $1-notional contracts | "10" |
int | a dimensionless integer (decimal places, leverage, counts) | "2" |
Conversions are exact integer arithmetic:
text
1 sat = 1000 msat
1 BTC = 100000000 sats = 100000000000 msat
price = q8_value / 1e8 # dollars
rate = q9_value / 1e9 # fraction; ×100 for %, ×10000 for bpsAn SDK generated from /openapi.json keys off x-hardbasis-unit to emit a big-integer type rather than a float, so a well-generated client cannot make the number mistake.
Nulls and absence
null is emitted for a field that is present but has no value; a field that does not apply is omitted. A rolling aggregate that has no data yet is served as explicit null, never zero — absence over invention.
Timestamps
ms timestamps are milliseconds since the Unix epoch, UTC, as decimal strings. There is no timezone in the wire value; render in the reader's zone client-side.