Service Overview

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.

Two product tiers

Free access

Covered Companies

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:

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

NAV Query API

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.

Direct endpoint (free, no payment)

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

Parameters

ParameterTypeDescriptionApplies To
gold / aunumberGold price per troy oz (e.g. 4000)Tickers whose grid carries a gold axis
silver / agnumberSilver price per troy oz (e.g. 61)Tickers whose grid carries a silver axis
cu / coppernumberCopper price per lb (e.g. 6.5)Tickers whose grid carries a copper axis
zn / zincnumberZinc price per lb (e.g. 1.62)Tickers whose grid carries a zinc axis
u3o8numberUranium price, USD per lb U3O8 (e.g. 82)Tickers whose grid carries a uranium axis
pgm4enumber4E PGM basket price, USD per ozTickers whose grid carries a 4E basket axis
rownumberGeneric 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
ratenumberDiscount 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.

Response shape

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

Example calls

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

Unnamed axes are marked to daily spot

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 behaviour

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.

Out-of-grid inputs are clamped — and the response says so

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:

FieldMeaning
out_of_gridBoolean. True if any supplied input fell outside its axis.
out_of_grid_detailPer 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_boundsThe [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.

The response describes its own axes

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.

Error responses

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

Qatom MCP — For Autonomous Agents

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.

NAV Query tool

FieldValue
Tool nameharcourt_nav_query_api
Tool ID77
Price$1 USD TDN per call
MCP serverhttps://mcp.m.todaq.net/mcp
DiscoveryAvailable in Qatom MCP marketplace — search by tool name or by intent

Tool input schema

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

PDF report tools (one per covered company)

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

Agent wallet setup

To call Qatom-gated tools autonomously, an agent needs:

  1. A Qatom primary wallet (human-controlled, MFA-gated) — created at todaq.net
  2. An agent sandbox wallet (no MFA, funded from the primary wallet via transfer_to_agent_wallet)
  3. Sufficient TDN balance to cover the calls ($1 per NAV query, $15 per PDF report)

The agent sandbox wallet is debited automatically when a tool call succeeds. Revenue is credited to Harcourt's provider Twin in real time.

Free Chat API (Klondike Mike)

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.

POST 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).

Methodology Notes for AI Consumers

When interpreting Harcourt NAV figures, AI agents and models should be aware of:

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.