Harcourt Valuations produces institutional-grade Net Asset Value (NAV) reports for mining companies. Each report contains a full discounted cash flow (DCF) model for every mine or project owned by the company, a P/NAV ratio, and a two-way sensitivity table showing how NAV per share moves with metal prices and discount rates.
The core thesis: the answer is a number. Mining equity analysis should produce a specific, defensible per-share value — not a narrative. P/NAV (price to net asset value) is the primary metric used by mining-focused fund managers and sell-side analysts at major Canadian and Australian banks.
?rate= call returns a NAV marked to today's
prices. Accessible via Qatom MCP Tool 77 (harcourt_nav_query_api).POST https://reports.harcourtvaluations.ai/chat. Powered by Claude Haiku.
Returns current P/NAV, NAV/share, and key catalyst for any covered ticker. Does not require
payment.GET https://reports.harcourtvaluations.ai/catalog
returns all covered companies in JSON with metadata.This page does not list them. Coverage changes as models are completed, and any list written here would be a copy that drifts from the source. Two live endpoints answer it, both free, both requiring no authentication:
| Endpoint | Answers |
|---|---|
GET https://reports.harcourtvaluations.ai/catalog |
Every covered company: name, ticker string with all listings, primary commodity, key assets, NAV, P/NAV, model date, and the free teaser. This is the authoritative coverage list. |
GET https://reports.harcourtvaluations.ai/nav-debug |
Which of those companies has a sensitivity grid, and that grid's real extent.
Per ticker: has_sensitivity, currency,
nav_usd / nav_cad / nav_aud,
p_nav, and an axes array giving each axis its
accepts parameter names, bounds and node count.
Keyed by the same ticker stem /nav-query/{ticker} takes. Read from
the model's own exported grid, so it cannot disagree with what
/nav-query will do. |
Resolving a company to its API ticker. Use the key from
/nav-debug directly — it is the path segment /nav-query expects.
Do not derive it from the ticker string in /catalog: for Seabridge Gold that
string is TSX: SEA · NYSE: SA and the API ticker is sa, not
sea. The listing symbol and the API ticker are not the same identifier.
Determining a company's commodity parameters. Read the
axes array from /nav-debug — it gives every ticker's axes, the
parameter names each one accepts, and its valid bounds, for all covered
companies in a single free call. A ticker may carry more than one commodity axis, so this
is a list, not a single value.
Sending the wrong parameter is also safe and self-correcting: it returns 400
wrong_axis_param with the ticker's real axes and the parameters each accepts,
rather than interpolating your value against an unrelated axis.
The NAV Query API returns a modelled NAV per share at caller-specified inputs. It does not re-run any live model. Instead, it reads a precomputed sensitivity grid stored in Cloudflare R2 and interpolates multilinearly between the nearest grid nodes. On most tickers, a commodity axis the caller does not name is filled from that day’s spot deck and reported back. Response time is typically under 50ms.
https://reports.harcourtvaluations.ai/nav-query/{ticker}?{commodity}={value}&rate={discount_rate}
No authentication required. Rate limits may apply at high volume. For production agent workflows, use the Qatom MCP tool below.
The commodity parameter is validated against the ticker's own grid. A
parameter that names no axis on that ticker is refused with 400 rather than
interpolated against an unrelated axis. Read the echoed inputs object in any
200: it names every axis actually used, including those defaulted to spot.
| Parameter | Type | Description | Applies To |
|---|---|---|---|
gold / au | number | Gold price per troy oz (e.g. 4000) | Tickers whose grid carries a gold axis |
silver / ag | number | Silver price per troy oz (e.g. 61) | Tickers whose grid carries a silver axis |
cu / copper | number | Copper price per lb (e.g. 6.5) | Tickers whose grid carries a copper axis |
zn / zinc | number | Zinc price per lb (e.g. 1.62) | Tickers whose grid carries a zinc axis |
u3o8 | number | Uranium price, USD per lb U3O8 (e.g. 82) | Tickers whose grid carries a uranium axis |
pgm4e | number | 4E PGM basket price, USD per oz | Tickers whose grid carries a 4E basket axis |
row | number | Generic axis value, accepted on any ticker with exactly one commodity axis. On a multi-axis ticker it names nothing and is refused with 400, listing the parameters to send instead — so it is a fallback, not a universal escape hatch. | Single-axis tickers |
rate | number | Discount rate as a decimal (e.g. 0.08 = 8%). Omit it and the grid's own base rate is used and reported in defaulted_inputs. | All |
This table names the parameters; /nav-debug names which of them a given
ticker accepts, and is authoritative if the two ever disagree.
Commodity axis units are carried in the axis key, and are not always USD.
Most grids expose gold_usd_per_oz, but a model reporting in Canadian dollars may
expose gold_cad_per_oz — the parameter name is gold either way. Read
the axis key and its bounds from the axes array before
sending a price, rather than assuming the unit from the parameter name. A USD gold price sent
to a CAD axis is a valid number in the wrong currency and will not be refused.
{
"ticker": "{ticker}",
"model_date": "YYYY-MM-DD",
"model_version": "{model version}",
"inputs": { "{axis_key}": 0.0, "discount_rate": 0.08 },
"defaulted_inputs": null, // array of axis keys filled in because the caller
// did not name them; null if the caller named all
"spot_inputs": ["{axis_key}"],// which of those came from the live daily spot deck
// rather than the model's own static price deck
"priced_at": "YYYY-MM-DD", // the date of that spot deck
"price_source": "nav_daily", // provenance of the defaulted prices
"axes": [ // multi-axis grids only
{ "axis": "{axis_key}",
"accepts": ["{param}", "..."], // the parameter names this axis takes
"bounds": [0.0, 0.0], // valid input range
"nodes": 0 }
],
"nav_per_share": 0.00,
"nav_per_share_{ccy}": 0.00, // same value, currency-suffixed key
"currency": "{USD | CAD | AUD}",
"out_of_grid": false,
"out_of_grid_detail": null, // per axis: "below" | "above" | false
"grid_bounds": { "{axis_key}": [0.0, 0.0], "discount_rate": [0.0, 0.0] },
"interpolation": "{n}-axis multilinear on {m} re-run scenarios",
"disclaimer": "..." // extended when a value was clamped
}
nav_per_share is always accompanied by an explicit currency field
(CAD, USD, or AUD depending on the company's model denomination). Never infer currency from
the commodity type. Read the NAV currency from /catalog or from the
currency field of the /nav-query response itself; do not infer it
from the commodity or the exchange.
# Single commodity axis — name the axis parameter and the rate.
GET /nav-query/{ticker}?gold=4000&rate=0.08
# Multiple commodity axes — name each one. ?row= is refused here.
GET /nav-query/{ticker}?gold=4000&cu=6.5&rate=0.08
# Do not know the axis? Omit it. The response reports what it defaulted.
GET /nav-query/{ticker}?rate=0.08
→ inputs echoes the axis and its value; defaulted_inputs names it
# Single-axis fallback when the parameter name is unknown.
GET /nav-query/{ticker}?row=4000&rate=0.08
Substitute {ticker} with a key from /nav-debug, and read that
ticker's valid input range from its matrix_rows and matrix_cols
before calling. A value outside the grid is clamped to the boundary rather than
extrapolated, and the response reports that per axis in out_of_grid_detail — see
“Out-of-grid inputs are clamped” below.
On a ticker whose grid supports it, a commodity axis the caller does not supply is not left at
a stale modelling assumption. It is filled from a spot deck refreshed daily, and the response says
so: defaulted_inputs lists the axes that were filled, spot_inputs
narrows that to the ones taken from the live deck, priced_at dates the deck and
price_source names it. A bare /nav-query/{ticker}?rate=0.08 therefore
returns a NAV at today’s prices, not at the model’s publication-date prices — and an
agent can prove which by reading those four fields rather than trusting this sentence.
Not every ticker supports it. Tickers still served from the older
single-matrix grid format require an explicit commodity price and answer a bare
?rate= call with 400 missing_params, listing the accepted parameters
and worked examples. Treat the omit-the-price call as the preferred path, not a guaranteed one:
send the rate alone, and if the response is a 400, resend with the price named in the body. Both
outcomes are self-describing, so no prior knowledge of which ticker is which is needed.
The consequence for callers: nav_per_share from an unnamed axis can move day to
day without the model changing. Compare priced_at, not model_date, when
deciding whether two NAV figures are like-for-like.
Interpolation is multilinear over however many axes that ticker's grid carries
— 2n corners, each weighted by the product of its per-axis fractions. It reduces to the
bilinear four-point case only when n = 2. A three-axis ticker interpolates across eight
corners. The 200 response states which was used in its interpolation field, e.g.
"3-axis multilinear on 150 re-run scenarios". Every node is a full model re-run, not a
curve fitted through sampled points.
An input beyond the published grid lands exactly on the edge value rather than
extrapolating past it. That clamp is reported explicitly, because a clamped answer is otherwise
indistinguishable from an interpolated one: same 200, same shape. Three fields carry it, and an
agent should test out_of_grid before treating nav_per_share as an
interpolation:
| Field | Meaning |
|---|---|
out_of_grid | Boolean. True if any supplied input fell outside its axis. |
out_of_grid_detail | Per axis, "below", "above" or false, so you can tell which input was out of range and in which direction. null when nothing was clamped. |
grid_bounds | The [min, max] actually available per axis, including discount_rate. Read this to correct a clamped call. |
When a clamp occurs the disclaimer field is also extended to say the value is a
boundary estimate rather than an interpolation, so a consumer that renders only the disclaimer
still surfaces it.
A multi-axis 200 response carries an axes array — for each axis, its
axis key, the parameter names it accepts, its bounds and its
node count. One call therefore tells an agent everything it needs to make the next call
correctly, including which parameters that ticker takes. Combined with
defaulted_inputs, a bare ?rate=0.08 is a valid discovery call on any
ticker that accepts one: it returns a NAV at the current deck and describes the surface it came
from. Where it is refused, the 400 missing_params body carries the same
accepted list and worked examples, so discovery succeeds either way.
| HTTP | Body | Meaning |
|---|---|---|
| 404 | {"error":"ticker_not_found"} | Ticker not in catalog, or its JSON is not yet uploaded. |
| 404 | {"error":"no_sensitivity_matrix"} | A report exists but the NAV Query API is not enabled for this ticker. /nav-debug reports this in advance as has_sensitivity: false. |
| 400 | {"error":"wrong_axis_param"} | The parameter names no axis on this ticker — e.g. ?u3o8= on a precious-metals ticker. Note that a multi-commodity ticker may accept parameters you would not expect from its headline metal, so test rather than assume. Self-correcting: the body carries axes with the parameters each axis accepts, plus expected_param. Previously such a call was interpolated against whatever axis existed and returned a plausible, wrong number. |
| 400 | {"error":"ambiguous_axis_param"} | Two parameters claim the same axis, or ?row= was sent to a ticker with more than one commodity axis. The body lists the axes and the parameter to send for each. |
| 400 | {"error":"missing_params"} | No commodity parameter and no discount rate. The body carries the full accepted parameter list and worked examples. |
| 500 | {"error":"malformed_grid"} | The stored grid failed validation. Not caller-correctable; report it. |
Every 400 body is designed to be acted on without re-reading this page: it names the axes, the accepted parameters and, where applicable, the exact parameter to substitute.
AI agents that have been granted access to the Qatom MCP marketplace can discover and call Harcourt tools autonomously, with payment settled automatically from the agent's Qatom wallet. No human approval is required per transaction.
| Field | Value |
|---|---|
| Tool name | harcourt_nav_query_api |
| Tool ID | 77 |
| Price | $1 USD TDN per call |
| MCP server | https://mcp.m.todaq.net/mcp |
| Discovery | Available in Qatom MCP marketplace — search by tool name or by intent |
{
"ticker": "ivn", // lowercase; valid keys are the top-level keys of /nav-debug
"commodity_param": "cu", // a name from that ticker's axes[].accepts in /nav-debug
"commodity_price": 6.5, // in the unit of that axis — read the axis key, not the
// parameter name; bounds are given per axis
"discount_rate": 0.08 // decimal (0.08 = 8%)
}
The valid values for ticker and commodity_param are not listed here
on purpose: coverage grows and axes are added, so any list written on this page would be a copy
that drifts. GET https://reports.harcourtvaluations.ai/nav-debug is free,
unauthenticated and answers both in one call. A ticker carrying more than one commodity axis is
better served by the direct GET endpoint, which takes each axis by name.
Every covered company has its own Qatom tool returning the full NAV PDF report at
$15 USD TDN. Rather than enumerate them here — a list that goes stale the moment a company is
added — discover them in the Qatom marketplace by provider Harcourt Valuations,
or read the authoritative list from the catalog endpoint, where each entry's
report_id is also usable directly:
GET https://reports.harcourtvaluations.ai/catalog
Accept: application/json
POST https://reports.harcourtvaluations.ai/
Content-Type: application/json
{ "report_id": "<report_id from the catalog>" }
To call Qatom-gated tools autonomously, an agent needs:
transfer_to_agent_wallet)The agent sandbox wallet is debited automatically when a tool call succeeds. Revenue is credited to Harcourt's provider Twin in real time.
A free conversational interface to Harcourt's data. Returns plain-language NAV analysis for any covered ticker. No payment required. Useful for quick screening before purchasing a full report or calling the paid API.
https://reports.harcourtvaluations.ai/chat
// Request
{
"messages": [
{ "role": "user", "content": "What's the current P/NAV on Ivanhoe Mines?" }
]
}
// Response (Anthropic messages format)
{
"content": [
{
"type": "text",
"text": "...prose citing that company's current NAV, P/NAV and price deck..."
}
]
}
The chat agent (Klondike Mike) is built on Claude Haiku. The system prompt is generated
server-side from live {ticker}.json files in R2, so it always reflects the
most recent published NAV data. Conversation history should be included in the
messages array for multi-turn sessions (up to 10 turns retained).
When interpreting Harcourt NAV figures, AI agents and models should be aware of:
model_date field. Data
reflects filings, guidance, and metal prices current at that date. Check model currency
before using figures in time-sensitive decisions.currency field on the
/nav-query response or on the /nav-debug record. Never infer it from
the listing, the exchange or the commodity.Disclaimer: Harcourt Valuations reports and API responses are analytical tools for informational purposes only. They do not constitute investment advice, a formal appraisal, or a recommendation to buy or sell any security. All valuations are model estimates subject to uncertainty. AI agents and models using this data must not present it as investment advice to end users without appropriate disclaimers.