Account Freshness and Reconciliation
Account REST views combine presentation projections with authoritative nonce and risk data. A successful read means the endpoint answered; it does not by itself prove that every projection has incorporated the write you just saw.
Current production workaround
Until the explicit freshness metadata described below is deployed and verified on the public API, use the authoritative bootstrap route after a deposit, fill, cancel, nonce fence, reconnect, risk rejection, or uncertain write:
GET /api/v1/accounts/{account}/bootstrap?fresh=true
The path value is the 20-byte engine account. It is different from the 32-byte
accountIdHex used by trading-account control-plane endpoints.
Retain the receipt or private event that triggered the refresh. Compare its
cursor/batch context with the response asOf. As a temporary cross-check,
compare asOf.batchId with engine_truth_batch_id from GET /api/v1/health;
if either value is missing, stale, or cannot be shown to cover the receipt,
treat freshness as unknown. Inspect the returned nonce
window, balances, positions, open orders, stateVersion, projectionVersion,
and projectionLagMs when those fields are present. Older deployed responses
may omit newer projection metadata, so absence is unknown rather than fresh.
projectionLagMs is legacy presentation metadata and is not an authoritative
batch-backlog measurement.
If the view is behind, wait with jitter and repeat the fresh bootstrap read. Do not reuse a nonce reported by an older view, and do not place or size a new order from balances that have not incorporated the terminal event.
Where balances and outcome shares are
Both account routes are built from the same engine account snapshot and carry the same balance fields:
| Field | Meaning | GET /api/v1/accounts/{account} | GET /api/v1/accounts/{account}/bootstrap |
|---|---|---|---|
usdcFree, usdcLocked | Engine USDC, free and reserved by open orders (micro, decimal string) | Yes | Yes |
yesFree, yesLocked, noFree, noLocked | Outcome shares per market, as arrays of [marketId, "qtyMicro"] | Yes | Yes |
balances.freeUsdcMicro (also availableBalanceMicro), balances.lockedUsdcMicro (also inOrdersUsdcMicro) | Same USDC values under the balances map | Yes | Yes |
risk.freeUsdcMicro | Free USDC as seen by the risk view | Yes | Yes |
assetBalances | Spot assets (WETH, WBTC, ARB, …) | Yes | Yes |
positions, openOrders | Positions and open orders | Yes | Yes |
nonceFloor, nextNonce | Action nonce window | Yes | Yes, overwritten from the authoritative nonce window (bootstrap also carries nonceWindow) |
holds, ledger | Ledger detail | Yes | Yes |
pendingWithdrawals | Withdrawals in flight | Yes | No |
?fresh=true is a parameter of the bootstrap route only; the plain
account route ignores it. It does not bypass the account snapshot and it does
not drop fields: a fresh bootstrap includes the YES/NO share arrays just like
the default one. What it changes is the fallback: the default bootstrap may
fall back to the stored presentation projection (and its nonce hint and
order-history fallback) when the live build fails, while fresh=true does
not.
When released balance is usable
- For the next order: immediately in sequence. A cancel applied by the
engine moves an ask's shares from locked to free and releases a bid's USDC
in the same apply step, and later actions are applied against that state.
An order sequenced after the cancel can use the released balance. This
requires the cancel to be applied: a BSL cancel-all acknowledgement
(
ackIsTerminalState: false) only admits the sweep, so wait for the cancel reports or receipts before counting on the funds. - In read endpoints: later. The account snapshot is republished after the
batch is durably persisted; a batch without its own post-state snapshot is
flushed after
ACCOUNT_READ_SNAPSHOT_MAX_AGE_MS(at least 1,000 ms). The plain account route is served from a replicated follower and can lag further.fresh=truedoes not skip this. Do not size the next order from a read that has not incorporated the cancel; compare its batch cut with the cancel receipt'sbatchIdas described below.
Source of truth by question
| Question | Evidence to use |
|---|---|
| Was the HTTP request admitted? | Submit response or durable receipt for the same idempotency/action identity |
| Did the order fill, rest, cancel, or reject? | Terminal receipt plus private execution/drop-copy event |
| Which action nonce is usable? | Fresh bootstrap or nonce-state replay window, not a cached account card |
| What balance can size the next order? | Fresh bootstrap after the relevant terminal event is reflected |
| Is new placement allowed? | Readiness/status plus account reconciliation; HTTP 200 status alone is insufficient |
Freshness contract rollout
The next server contract adds these fields to account/bootstrap responses:
| Field | Meaning |
|---|---|
viewBatchId | Exact batch cut backing the returned balances and account view. |
authorityHeadBatchId | Current EngineTruth head from the existing short-lived health cache, or the local batch only on a genuine writer plane; null when authority is unavailable or stale. |
lagBatches | authorityHeadBatchId - viewBatchId only when both are coherent and authority is not behind the view; otherwise null. |
authorityHeadSource | engine_truth_cache, local_writer, core_bootstrap_authority, or unknown. core_bootstrap_authority means the platform bootstrap obtained the authority view through the narrow Core route. |
projectionAgeMs | Elapsed wall-clock time since the exact account snapshot and its viewBatchId were published together; null when unknown. It is publication age, not batch backlog. |
syncStatus | synced, degraded, pending, or unknown. pending covers an on-chain fallback that has not resolved yet and the Core-bootstrap pending_unknown_account state. |
The invariants are stricter than a timestamp heuristic:
syncedmeans both batch ids are known and equal, andlagBatchesis0;degradedmeans the authority head is ahead andlagBatchesis positive;unknownmeans the authority is unavailable, stale, or lower than the view, andlagBatchesisnull;projectionAgeMsalone never determinessyncStatus.
The candidate checks periodically while a newer verified account-read cut is
dirty and awaiting publication. The
operator setting ACCOUNT_READ_SNAPSHOT_MAX_AGE_MS defaults to 1,000 ms and is
clamped to 1,000–60,000 ms. It bounds the dirty-cut publication deadline; it
does not rebuild or republish a full snapshot continuously while state is
unchanged. This is a scheduling target, not a response-time SLA: checkpoint
creation, database work, failover, and client/network latency remain separate.
Each due refresh rebuilds the verified account-read snapshot across current
state, so lowering the interval increases background work.
For example, viewBatchId: 1472056, authorityHeadBatchId: 1472076,
lagBatches: 20, and syncStatus: "degraded" is a view that must not size the
next order. An unavailable authority is represented by a null authority and
lag, authorityHeadSource: "unknown", and syncStatus: "unknown".
These fields are not yet available on production. Use the current workflow above until the server returns them and public readback is verified.
Treat the new fields as usable only after all of these are true:
- the backend build containing the fields is deployed;
- the public route returns them for the target account;
- their batch/cursor advances past the receipt or event being reconciled; and
- the documented stale/unknown states have been exercised in acceptance tests.
Escalation
If a fresh view does not advance, stop dependent placement and send support the two request ids, UTC window, environment, receipt/event cursor, and sanitized freshness fields. Never send raw authentication headers, signatures, API secrets, private keys, or private request bodies. Use the template in Troubleshooting HTTP integrations.