Skip to main content

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:

FieldMeaningGET /api/v1/accounts/{account}GET /api/v1/accounts/{account}/bootstrap
usdcFree, usdcLockedEngine USDC, free and reserved by open orders (micro, decimal string)YesYes
yesFree, yesLocked, noFree, noLockedOutcome shares per market, as arrays of [marketId, "qtyMicro"]YesYes
balances.freeUsdcMicro (also availableBalanceMicro), balances.lockedUsdcMicro (also inOrdersUsdcMicro)Same USDC values under the balances mapYesYes
risk.freeUsdcMicroFree USDC as seen by the risk viewYesYes
assetBalancesSpot assets (WETH, WBTC, ARB, …)YesYes
positions, openOrdersPositions and open ordersYesYes
nonceFloor, nextNonceAction nonce windowYesYes, overwritten from the authoritative nonce window (bootstrap also carries nonceWindow)
holds, ledgerLedger detailYesYes
pendingWithdrawalsWithdrawals in flightYesNo

?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=true does 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's batchId as described below.

Source of truth by question​

QuestionEvidence 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:

FieldMeaning
viewBatchIdExact batch cut backing the returned balances and account view.
authorityHeadBatchIdCurrent 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.
lagBatchesauthorityHeadBatchId - viewBatchId only when both are coherent and authority is not behind the view; otherwise null.
authorityHeadSourceengine_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.
projectionAgeMsElapsed wall-clock time since the exact account snapshot and its viewBatchId were published together; null when unknown. It is publication age, not batch backlog.
syncStatussynced, 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:

  • synced means both batch ids are known and equal, and lagBatches is 0;
  • degraded means the authority head is ahead and lagBatches is positive;
  • unknown means the authority is unavailable, stale, or lower than the view, and lagBatches is null;
  • projectionAgeMs alone never determines syncStatus.

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".

Rollout status

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.