Sable Private NAV
The NAV is public. The book does not have to be.
An issuer or fund administrator submits a private book — positions, valuations, liabilities, unit supply, and where each figure came from. Sable computes:
- gross asset value
- liabilities
- net asset value
- NAV per share/token
- supply
- the relevant valuation timestamp
- data freshness
- reconciliation status
…and returns a signed record that verifies through the same public endpoint that checks every Sable receipt. The submitted book is AES-GCM-sealed at rest, opened in-frame only to compute, and never published. Only the configured outputs are.
This is financial infrastructure, not investment advice.
The two halves
Private NAV has a deliberate auth split, and it is the shape of the product:
| Half | Endpoints | Auth |
|---|---|---|
| Write — submit a book, recompute it, publish a record | POST /v1/nav/calculate, POST /v1/nav/{asset_id}/refresh, POST /v1/nav/{asset_id}/publish | sk-sable_ API key |
| Read — the published NAV, its history, its provenance | GET /v1/nav/{asset_id}, .../history, .../evidence | Public. No account, no credential |
An LP, an exchange, or a regulator fetches the NAV and verifies its signature
with no relationship to you at all. An unpublished calculation is returned
once, to the caller that ran it, and is otherwise invisible: a missing asset,
one never calculated, and one calculated but not published all answer 404
identically, so an outsider learns nothing from the difference.
Units: integers, always
Every figure crossing this API is an integer.
- Money is micro-USD:
1_600_000is$1.60. - Quantities and supply are micro-units:
1_000_000is one whole share/token, so a fractional token is still an exact integer.
There is no floating point anywhere in the calculation. Every division
floors, and the record says so ("rounding": "floor"): a position's value
floors, and NAV per unit floors. Flooring never overstates what a holder owns,
and it is the rounding bias an issuer's counterparties can reasonably accept in
a figure the issuer publishes about itself.
Calculating
curl https://api.buildsable.com/v1/nav/calculate \
-H "Authorization: Bearer $SABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset_id": "va_1a2b3c4d5e6f7890",
"positions": [
{
"label": "T-bill ladder",
"quantity_micro_units": 1000000000,
"price_micro_usd_per_unit": 1000000,
"evidence": {
"source_kind": "custodian",
"source_ref": "Custodian statement 2026-09",
"retrieved_at": "2026-09-06T09:00:00Z",
"content_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
],
"liabilities": [
{ "label": "Management fee accrual", "amount_micro_usd": 400000000 }
],
"supply_micro_units": 1000000000,
"onchain_supply_micro_units": 1000000000,
"tolerance_bps": 25,
"max_age_secs": 86400
}'The response is the signed record:
{
"nav_id": "nav_ff89e1008d84453e",
"asset_id": "va_1a2b3c4d5e6f7890",
"seq": 1,
"status": "VERIFIED",
"receipt": "eyJ2IjoxLCJ0eXBlIjoibmF2LXJlY29yZCIsIm5hdl9pZCI6…",
"signature": "0x…",
"signer": "0xa1e5cff305147d761e1b03fc613c123e6359941d",
"payload": {
"gross_asset_value_micro_usd": 2000000000,
"liabilities_micro_usd": 400000000,
"total_nav_micro_usd": 1600000000,
"nav_per_unit_micro_usd": 1600000,
"supply_micro_units": 1000000000,
"rounding": "floor",
"methodology_version": "sable-nav/1",
"verifier_version": "sable-gateway/0.1.0",
"freshness": { "age_secs": 3600, "max_age_secs": 86400, "fresh": true },
"reconciliation": { "supply": "VERIFIED", "supply_delta_bps": 0, "…": "…" },
"evidence_references": [ { "source_kind": "custodian", "…": "…" } ],
"reasons": []
},
"verify": "POST /v1/receipts/verify with {receipt, signature}"
}
Status: the five answers
Every record carries one status, and the worst applicable condition wins — a report that mentions only the mildest problem is a report that hides the others.
| Status | Meaning | Are there figures? |
|---|---|---|
VERIFIED | Every material input carried provenance Sable could check, and nothing disagreed | Yes |
PARTIALLY_VERIFIED | Computed, but at least one input rests on the issuer's own declaration, or no independent supply reference was offered | Yes |
STALE | Inputs are past the freshness limit. They were used and labelled | Yes |
DISCREPANCY | Two inputs that should agree do not, beyond the tolerance | Yes, plus the disagreement |
UNAVAILABLE | A required input was missing | No. None at all. |
Three rules follow from that table, and they are the whole point of the feature:
Never fabricate a NAV. When a position has no price, or supply is missing,
the record comes back UNAVAILABLE and every numeric field is absent — not
zero, not a partial total. The database stores NULL, so a later reader cannot
mistake a failure for a zero-value fund.
Never silently substitute an old value. There is no code path that carries a previous price forward into a book that is missing one. The previous record is not even read during a calculation.
Never average away a disagreement. When two price sources for the same
position differ beyond tolerance_bps, the primary price stands and the record
says DISCREPANCY. Nothing is blended.
Each record also carries reasons — a closed vocabulary of codes
(missing_position_price, stale_input, supply_mismatch,
unchecked_input, price_source_disagreement, …) so a machine can act on the
outcome without parsing prose.
Evidence and provenance
Every material input can carry an evidence block:
{
"source_kind": "custodian",
"source_ref": "Custodian statement 2026-09",
"retrieved_at": "2026-09-06T09:00:00Z",
"content_sha256": "e3b0c442…"
}
source_kind is one of market_price, custodian, independent_valuation,
onchain_balance, issuer_declared, manual_input. The first four are
checkable — they name something outside the issuer that could be re-read. The
last two are the issuer's own word, and a book resting on them can never be
better than PARTIALLY_VERIFIED. "The issuer said so" is not a check, and the
record will not call it one.
content_sha256 is a digest of the input as submitted. It never travels as
a value, and the underlying statement never reaches Sable at all. What it
answers is: is this the same reading you showed me before, and is it the same
one the custodian would hand me? — which is the provenance question the
privacy contract leaves available.
GET /v1/nav/{asset_id}/evidence publishes those references for a published
record. References only: never a price, never a quantity, never a position
value.
Freshness and refresh
A book is exactly as current as its stalest input, so freshness is measured
against the oldest retrieved_at in the whole submission, not the newest.
max_age_secs (default 24 hours) sets the limit.
That is what POST /v1/nav/{asset_id}/refresh is for. It recomputes the
stored book against the current clock and appends a new versioned record —
so a VERIFIED record becomes an honest STALE one instead of quietly ageing
in place. You can also pass a newly observed onchain_supply_micro_units
without resubmitting the whole book. A refresh is metered exactly like a
calculation.
Reconciliation
Where the asset is tokenized, supply reconciles across:
Underlying NAV
↕
Calculated NAV per unit
↕
Token/share supply
↕
On-chain supply
Pass onchain_supply_micro_units (with its evidence) and Sable compares it
against your reported supply within tolerance_bps, reporting a signed drift
in basis points. Beyond tolerance is a DISCREPANCY, and the figures are still
published alongside it — the disagreement is disclosed, never hidden.
Omitting the on-chain reference is allowed, and it caps the record at
PARTIALLY_VERIFIED with the reason no_onchain_supply_reference. No
independent check was offered, so none is claimed.
Publishing
Nothing is public until you publish. POST /v1/nav/{asset_id}/publish marks
one record readable by anyone at GET /v1/nav/{asset_id} and on the public page
at buildsable.com/nav/{asset_id}. It is idempotent, and it is free — the
calculation was already paid for, and charging to publish would put a price on
an issuer's honesty. Nobody should have a financial reason to sit on a
DISCREPANCY.
Records are versioned and never overwritten. GET /v1/nav/{asset_id}/history
returns the full published series, newest first, each entry independently
signed. Every calculation also appends an event to the asset's
Vault hash chain, so the series is tamper-evident and rides the
same public anchoring.
What is disclosed, and what is not
By default the record carries the outputs only: gross asset value, liabilities, NAV, NAV per unit, supply, timestamps, statuses, and the evidence references. It does not carry your positions.
Set disclose_components: true and the record additionally publishes a
per-position breakdown that sums exactly to gross asset value. Most issuers
do not, and the NAV does not require it.
The book is sealed with AES-GCM using the gateway master key. It is kept — a
NAV engine cannot recompute or re-attest something it does not hold, and a
record that cannot be re-derived is not evidence of anything — but it exists at
rest only as ciphertext beside a book_commitment (a sha256 of the canonical
submission). That commitment lets you prove later that a given record came from
a given book, without revealing any of it.
Privacy modes
privacy_mode accepts standard (the default) and confidential.
standard runs under the ordinary Sable privacy contract:
sealed at rest, opened in-frame, never logged.
confidential is config-gated and off by default. Where the operator has
not enabled it, a confidential request is refused with 501 — it is never
quietly served under the standard contract, because a silent downgrade is
exactly the lie the confidential tier exists to make impossible.
Where it is enabled, the request additionally requires a live, verified Intel
TDX attestation at calculation time and fails closed without one.
Read the attestation block's own scope sentence before quoting it. It states
that Sable verified this deployment's confidential backend at calculation time;
the NAV arithmetic itself runs in the gateway process, not inside that enclave.
There is no zero-knowledge proof, MPC, or homomorphic encryption anywhere in Private NAV, and none is claimed. The trust model is stated in the signed payload itself.
Webhooks
Subscribe on the webhooks surface:
| Event | Fires when |
|---|---|
nav.calculated | Any calculation or refresh completes |
nav.published | A record is made public |
nav.stale | A calculation came back STALE |
nav.discrepancy | A calculation came back DISCREPANCY |
nav.source_changed | The set of evidence sources differs from the previous record |
nav.supply_changed | Reported supply differs from the previous record |
Payloads are content-free: a nav id, an asset id, a sequence, the status, and the reason codes — never a figure. A webhook body travels to a third-party URL, and an unpublished calculation is nobody's business but the issuer's. The subscriber reads the numbers from the published record.
Cost
A calculation (or a refresh) is metered like any other unit of Sable compute:
one usage_events row of kind nav_calculation, one idempotent ledger debit,
one pre-authorization hold, and every spend cap and the
circuit breaker binding as usual. Publishing is free.
The price is set by the operator (SABLE_NAV_MICRO_USD_PER_CALCULATION,
default $0.05). Payment never determines whether a result is marked
verified. The engine that decides a status takes no cost, no plan and no
previous record as an input — it is a pure function of the submitted book and
the clock, which is how that guarantee is enforced structurally rather than
remembered. Billing can decide whether a calculation runs; it can never
decide what the answer is.
Limits
| Positions per book | 500 |
| Liabilities per book | 200 |
max_age_secs | 60 … 31,536,000 |
tolerance_bps | 0 … 10,000 |
Record TTL (expires_at) | 24 hours |
What Private NAV is not
- Not an audit. Sable computes from a book the issuer submitted. It does not visit the custodian, and it cannot know whether the book is complete.
- Not an appraisal. Prices are the issuer's, from the sources listed on the record. Sable checks arithmetic, freshness and agreement — not whether a price is right.
- Not a zero-knowledge system. See above.
- Not investment advice.