Developer API

Bring ODIN into your stack.

The ODIN developer API is in partner development. Its staged sandbox reads the live economic health index, prevailing conditions, and economic data series, and runs deterministic what-if simulations — over REST or the MCP connection, on the same key. Natural-language reasoning remains in development for external access.

Partner development. Access is enabled per organization for controlled sandbox work. This public page cannot see whether your organization is enabled. To check, sign in, open Settings → API keys. If the key controls appear, an owner or admin can create a key (choose a lifetime from 30 to 365 days) and store it in your secret manager. If the page says access is not enabled, email info@odinos.ca to request access.

Authentication

Every request is authenticated with a bearer key passed in the Authorization header. Keys are org-scoped and secret — treat them like a password; they are shown once at issuance and stored only as a hash. Every response carries an X-Odin-Version header and Cache-Control: private, no-store.

Header

Authorization: Bearer odn_...

Getting a key. Sign in at app.odinos.ca and open Settings → API keys. Organization owners and admins can create keys (choose a lifetime from 30 to 365 days), see when each key was last used, and revoke instantly. Revocation takes effect within a minute.

Rotating a key. Zero-downtime rotation is by overlap: create the replacement key, move your integration to it, then revoke the old one. Your organization can hold up to five active keys, so old and new can run side by side for as long as the migration needs.

Quickstart

Start with the deterministic health endpoint. It confirms authentication, scopes, metering, and data access without waiting on a language model. The self-serve sandbox does not include natural-language reasoning yet.

/bin/bash -c '
set -euo pipefail
read -rsp "ODIN key: " ODIN_KEY; printf "\n"
printf "Authorization: Bearer %s\n" "$ODIN_KEY" | curl -sS https://api.odinos.ca/v1/pulse/health-index -H @-
unset ODIN_KEY
'

Paste this block into a terminal, then enter the key at the hidden prompt. Do not paste the confirmation dialog's Copy quickstart command into a normal shell: it contains the cleartext key, which the shell can retain in its history.

Commercial-lending starter set

A worked path for commercial lending and credit advisory teams — anyone whose day is deal intake, structuring, and timing conversations across sectors like construction, manufacturing, distribution, and professional services. ODIN reads the economy at the sector and province level; it never analyzes, scores, or names individual companies, and nothing here is borrower-level input. The catalog tracks over a thousand series; these are the sixteen a credit desk actually starts with, plus the five calls that turn them into a working read. Everything on this page is served by the current API surface — no capability below is promissory.

Scopes. A self-serve key covers everything in this section: dashboard reads, data series, and deterministic what-if simulations. Plain-English reasoning (/v1/reason) is not in the self-serve set — it remains in development for external access on a staged schedule. When you mint a key through the app's key endpoint rather than the UI, the create call takes an expires field with preset lifetimes 30d, 90d, 180d, or 365d; the response identifies the new key as key_id, and revocation addresses that same key UUID.

The sixteen series

Each identifier works in the series lookup and batch endpoints, and all but the market-implied spread direction also trace through the transmission endpoint. Each row states its geographic coverage and how often its data refreshes. Sector insolvency series are national counts refreshed quarterly (their newest point is March 2026 until the mid-September refresh); province-level insolvency coverage is currently Ontario, and construction-cost coverage is currently Toronto.

IdentifierWhat it isWhy a credit desk reads itCoverageUpdates
slos_business_lendingBusiness lending conditions (senior loan officer survey)Bank of Canada's quarterly survey of bank lending officers on business credit. Positive readings mean banks are tightening; negative means easing. The most direct read on how hard it is to get a business loan approved.CanadaQuarterly
slos_lending_conditionsOverall lending conditions (survey composite)The same survey's composite across business, mortgage, and consumer lending — one number for overall bank posture.CanadaQuarterly
bos_credit_conditionsFirms reporting easier credit (business outlook survey)The borrower's side of the table: the Bank of Canada's quarterly survey of firms on whether credit got easier or harder to obtain.CanadaQuarterly
boc_overnightBank of Canada policy rateThe anchor for every floating-rate facility. Stored as a monthly series; the rate itself moves only on the eight scheduled decision dates.CanadaMonthly
prime_rateBig-bank prime business rateThe posted prime rate at the large chartered banks — the base most variable-rate business loans are priced from. Published daily; it steps only when the policy rate moves.CanadaBusiness-daily
ca_business_loansBusiness loans outstanding at chartered banksTotal business lending on bank balance sheets, in millions of dollars. Whether credit is actually flowing, not just what surveys say.CanadaMonthly
ca_ig_spread_directionCorporate credit spread direction (market-implied)Whether investment-grade corporate borrowing spreads are widening or narrowing, computed from freely quoted Canadian bond-fund prices. This is a direction, not a spread level — it is never quoted in basis points, and the level is index-arbitrary; the direction of change carries the signal.CanadaMonthly
insolvency_rate_constructionBusiness insolvencies — constructionBusiness bankruptcies plus proposals in construction, from the federal insolvency regulator's open data. A raw filing count, despite the identifier's name — not a rate.CanadaMonthly data, refreshed quarterly; currently March 2026
insolvency_rate_manufacturingBusiness insolvencies — manufacturingSame measure for manufacturing — also a count, not a rate.CanadaMonthly data, refreshed quarterly; currently March 2026
insolvency_rate_retailBusiness insolvencies — retail and distributionSame count for retail trade — the closest served proxy for distribution-facing stress.CanadaMonthly data, refreshed quarterly; currently March 2026
insolvency_rate_professionalBusiness insolvencies — professional servicesSame count for professional services firms.CanadaMonthly data, refreshed quarterly; currently March 2026
insolvency_rate_accommodationBusiness insolvencies — accommodation and foodSame count for accommodation and food service.CanadaMonthly data, refreshed quarterly; currently March 2026
insolvency_filingsConsumer insolvency filings (Ontario)Monthly consumer bankruptcies plus proposals in Ontario — household-side stress in the largest provincial credit market. Ontario is the only province served today.OntarioMonthly
ccaa_filingsLarge-company creditor-protection filingsQuarterly count of filings under the federal large-company restructuring statute — the top end of the corporate distress spectrum.CanadaQuarterly
mortgage_arrearsMortgage arrears rateShare of residential mortgages 90+ days behind — the standard household credit-performance benchmark lenders watch.CanadaQuarterly
bcpi_toronto_highrise_compositeConstruction costs — Toronto high-rise compositeStatistics Canada's building construction price index for high-rise residential work in Toronto — input-cost pressure for construction borrowers. Toronto is the only market served today; use this exact identifier, as other identifiers sharing its prefix are national commodity prices, not construction costs.Toronto CMAQuarterly

The first five calls

1 — Prove the connection

Confirms your key, organization, and granted scopes without touching a data endpoint.

curl -s https://api.odinos.ca/v1/ping -H "Authorization: Bearer odn_your_key_here"

2 — The headline context read

The state-of-the-economy panel in three deterministic reads: the composite health score with its component pillars, the economic conditions currently prevailing, and the policy rates that anchor every facility — including the next scheduled rate decision date. Response shapes are documented under Endpoints.

curl -s https://api.odinos.ca/v1/pulse/health-index -H "Authorization: Bearer odn_your_key_here"
curl -s https://api.odinos.ca/v1/pulse/regime -H "Authorization: Bearer odn_your_key_here"
curl -s https://api.odinos.ca/v1/pulse/policy-rates -H "Authorization: Bearer odn_your_key_here"

3 — The credit-cycle batch

All sixteen series in one call, with history from 2024 onward — the input to a “should this borrower move now” view: bank posture from both sides of the table, the cost anchors, whether credit is flowing, and where sector stress is climbing. Each result carries its unit, its own freshness stamp, and a per-key status, so one unavailable series never fails the batch.

curl -s https://api.odinos.ca/v1/signals/batch -H "Authorization: Bearer odn_your_key_here" -H "Content-Type: application/json" -d '{"keys":["slos_business_lending","slos_lending_conditions","bos_credit_conditions","boc_overnight","prime_rate","ca_business_loans","ca_ig_spread_direction","insolvency_rate_construction","insolvency_rate_manufacturing","insolvency_rate_retail","insolvency_rate_professional","insolvency_rate_accommodation","insolvency_filings","ccaa_filings","mortgage_arrears","bcpi_toronto_highrise_composite"],"date_from":"2024-01-01"}'

4 — Borrowing-cost transmission

How a policy-rate move actually reaches loan pricing: the channels leading out of the policy rate, each with the historical strength of the relationship and its typical lag in days. This is structure, not a forecast — the timing half of “the cut is real, but it reaches your renewal in weeks, not days.”

curl -s https://api.odinos.ca/v1/transmission -H "Authorization: Bearer odn_your_key_here" -H "Content-Type: application/json" -d '{"signal":"boc_overnight","max_depth":3}'

5 — The rate scenario

A quarter-point cut to the policy rate, propagated through recorded economic relationships to a six-month horizon: which indicators move, in what direction, by how much, through how many steps. Deterministic — no language model involved — so the same shock always returns the same projection. Term-sheet timing conversations start here.

curl -s https://api.odinos.ca/v1/counterfactual -H "Authorization: Bearer odn_your_key_here" -H "Content-Type: application/json" -d '{"shock":{"node":"boc_overnight","delta":-0.25},"horizon_months":6}'
What this set deliberately is not.Sector conditions and timing — never borrower diagnostics, credit scoring, lender matching, or approval input; that side of the fence belongs to your own systems, and ODIN's output is research context, not advice. No individual company is analyzed or named, by design. And the plain-English reasoning surface stays in development for external access until its staged rollout — the sixteen series and five calls above need none of it.

Endpoints

Base URL https://api.odinos.ca (keys issued earlier may reference the previous base URL, which continues to work). All responses are JSON. Each endpoint documents the identifier type it accepts: counterfactual requests use identifiers defined for what-if simulations, while signal-series endpoints accept signal keys. The two identifier types are not interchangeable.

GET/v1/ping

Authenticated liveness check. Confirms your key, and returns your organization, granted scopes, and rate tier — nothing else.

Response

{
  "status": "ok",
  "organization": "Your Organization",
  "scopes": ["pulse", "signals", "counterfactual"],
  "rate_tier": "self_serve",
  "request_id": "req_09e19b5f9f534205a23a08a9a2819706"
}
GET/v1/pulse/health-index

The current health of the Canadian economy as a single score out of 100, with six component pillars (growth, monetary, consumer, labor, trade, systemic), the best and worst areas, and the 30-day trend. Recomputed nightly. This is the leading example because it runs with the standard self-serve pulse scope.

Response

{
  "composite_score": 54.9,
  "pillars": {
    "growth":                  { "score": 51.2, "label": "Growth" },
    "monetary_health":         { "score": 44.0, "label": "Monetary" },
    "consumer_balance_sheets": { "score": 47.1, "label": "Consumer" },
    "labor_market":            { "score": 55.4, "label": "Labor" },
    "trade_external":          { "score": 42.8, "label": "Trade" },
    "systemic_stability":      { "score": 49.6, "label": "Systemic" }
  },
  "worst_pillar": "trade_external",
  "best_pillar": "labor_market",
  "trend": "stable",
  "trend_delta_30d": -0.7,
  "computed_at": "2026-07-13T05:42:00Z"
}
POST/v1/reason

Ask any economic question in plain English. ODIN reads live data, traces transmission through the twin, and returns a reasoned, cited answer. Plain-English output — no system jargon. This capability remains in development for external access and is not included on self-serve sandbox keys.

Required scope: reason, granted separately for controlled partner testing. A standard self-serve key correctly returns a 403 missing-scope response here.

grounding_status is grounded, degraded, or not_used. A degraded answer also carries a confidence note; citations expose only a bounded public URL and title, never check names, arguments, raw results, or provider internals. The exact number of additional valid citations beyond the 20-item response cap is reported in citations_omitted. A private or malformed URL, or a title that is multi-line, control-bearing, or written in internal machine vocabulary, is omitted and makes the answer degraded even when another public URL survives. If ODIN reaches its analysis budget after tracing the highest-leverage paths, the response is marked truncated: true and degraded rather than being presented as exhaustive.

Request

POST /v1/reason
{
  "question": "How would a 50bp BoC cut transmit to housing?",
  "max_seconds": 120          // optional, default 120, max 150
}

Response

{
  "answer": "A 50bp cut lowers variable mortgage rates within weeks...",
  "confidence_notes": null,
  "grounding_status": "grounded",
  "citations": [
    { "url": "https://www.bankofcanada.ca/rates/interest-rates/canadian-interest-rates/",
      "title": "Policy interest rate" }
  ],
  "citations_omitted": 0,
  "elapsed_seconds": 65.8,
  "truncated": false,
  "request_id": "req_4948eba494704d4680b1a8a7fc0b014f"
}
GET/v1/pulse/regime

Which broad economic conditions currently prevail in Canada — each with a plain-English description and severity — plus the conditions that are currently dormant. An empty active_regimes list is a normal answer when all five current conditions are present under inactive_regimes: it means no monitored condition is dominant right now. The five rows must come from one detector run. The top-level as_of is that complete cohort's UTC timestamp and must be no more than 48 hours old; individual rows do not each carry the computation timestamp. A partial, mixed-run, future, stale, or failed read returns the stable 503 not_computed error envelope instead.

Response

{
  "active_regimes": [
    { "regime_key": "inflationary_regime", "label": "Inflationary Regime",
      "description": "Above-target inflation with...", "severity": "elevated" }
  ],
  "inactive_regimes": [
    { "regime_key": "liquidity_crisis_regime", "label": "Liquidity Crisis" },
    { "regime_key": "deflationary_regime", "label": "Deflationary Regime" },
    { "regime_key": "asset_bubble_regime", "label": "Asset Bubble Regime" },
    { "regime_key": "credit_cycle_regime", "label": "Credit Cycle" }
  ],
  "as_of": "2026-08-09T04:22:00Z"
}
POST/v1/counterfactual

Simulate a shock to one part of the economy and see the projected knock-on effects: which indicators move, in what direction, by how much, and through how many transmission steps. The shock must be a finite numeric change or an exact to:<finite number> target representable as an IEEE-754 number; nonzero targets too small to survive conversion are invalid. An absolute target also requires a current source value, otherwise ODIN returns 503 not_computed. Returned deltas are always finite. Deterministic — no language model involved.

Request

POST /v1/counterfactual
{
  "shock": { "node": "boc_overnight", "delta": -0.5 }, // finite number or "to:<finite number>"
  "horizon_months": 6,        // optional, default 6, 1-24
  "max_depth": 3              // optional, default 3, 1-4
}

Response

{
  "shock": { "node": "boc_overnight", "label": "BoC Overnight Rate",
             "delta": -0.5, "description": "..." },
  "horizon_months": 6,
  "impacts": [
    { "node": "mortgage_rate_5yr", "label": "5Y Mortgage Rate",
      "direction": "down", "projected_delta": -0.34, "hops": 1 }
  ],
  "pillar_summary": {
    "rates": { "net_delta": -0.8, "magnitude": 1.4, "node_count": 6,
      "direction": "down", "examples": [
        { "target": "5Y Mortgage Rate", "delta": -0.34 },
        { "target": "Prime Rate", "delta": -0.29 }
      ] }
  },
  "assumptions": "The simulation follows recorded economic relationships..."
}
GET/v1/signals/{key}

Look up one Canadian economic data series by identifier: description, unit, and up to five years of values. Optional date_from / date_to query parameters narrow the range. Series under restrictive data licenses are not served. For multi-geography series, the geography, values, and latest_ingested_at stamp all describe the same best-covered valid cohort inside that requested window. If the cohort exceeds 5,000 periods, ODIN returns its newest 5,000 in chronological order;series_total, series_returned, and series_omitted state the exact cohort denominator; the freshness stamp describes only those returned rows. Database-only non-finite, numeric-overflow and numeric-underflow values are excluded before cohort selection. When a series has a declared ODIN sanity envelope, out-of-envelope values are excluded by that same population before geography, counts and freshness are chosen; series without a declared envelope retain the finite, representable-number rule.

Response

{
  "signal_key": "ca_unemployment_rate",
  "display_name": "Unemployment Rate (Canada)",
  "unit": "percent",
  "frequency": "monthly",
  "geography_level": "national",
  "geography": "CAN",
  "description": "...",
  "attribution": null,          // set only when the source license requires a credit line
  "series": [ { "period": "2026-06-01", "value": 6.5 } ],
  "truncated": false,
  "series_total": 1,
  "series_returned": 1,
  "series_omitted": 0,
  "latest_ingested_at": "2026-07-11T13:05:00Z"
}
GET/v1/signals

Browse the catalog of available series. Filter with search, category or geography_level; page with cursor and limit (default 50). Every identifier returned here can be passed straight to the lookup, batch and transmission endpoints.

Response

{
  "items": [{
    "signal_key": "ca_unemployment_rate",
    "display_name": "Unemployment Rate (Canada)",
    "unit": "percent",
    "category": "labour",
    "domain": "employment",
    "geography_level": "national",
    "description": "...",
    "attribution": null,
    "retired": false,
    "retired_at": null
  }],
  "count": 1,
  "next_cursor": "Y2FfdW5lbXBsb3ltZW50X3JhdGU",
  "has_more": true,
  "filters": {
    "search": null, "category": "labour",
    "geography_level": null, "include_retired": false
  }
}
POST/v1/signals/batch

Look up many series in one call instead of looping the single-series endpoint — the efficient path when you are hydrating a dashboard or a model.

Request

POST /v1/signals/batch
{
  "keys": ["ca_unemployment_rate", "ca_cpi_yoy"],  // 1-25, alias-aware
  "date_from": "2025-01-01",                        // optional, inclusive
  "date_to": null                                    // optional, inclusive
}

Response

{
  "requested": 2,
  "succeeded": 1,
  "failed": 1,
  "results": [
    {
      "requested_key": "ca_unemployment_rate", "status": "ok",
      "signal_key": "ca_unemployment_rate",
      "display_name": "Unemployment Rate (Canada)",
      "unit": "percent", "frequency": "monthly",
      "geography_level": "national", "geography": "CAN",
      "description": "...", "attribution": null,
      "series": [{ "period": "2026-06-01", "value": 6.5 }],
      "truncated": false,
      "series_total": 1, "series_returned": 1, "series_omitted": 0,
      "latest_ingested_at": "2026-07-11T13:05:00Z"
    },
    {
      "requested_key": "unknown_key", "status": "error",
      "error": { "code": "not_found", "message": "No signal with that identifier exists." }
    }
  ]
}
GET/v1/pulse/policy-rates

The policy rates that anchor Canadian conditions — the Bank of Canada overnight rate and its peers — plus buyer purchasing-power context, in one call. Every item carries its own latest official print date; unavailable tracked series are named.

Response

{
  "as_of": "2026-07-15",
  "rates": [{
    "signal_key": "boc_overnight", "label": "BoC Overnight Rate",
    "value": 2.25, "unit": "percent", "period": "2026-07-15",
    "ingested_at": "2026-07-15T14:05:00Z"
  }, {
    "signal_key": "buyer_purchasing_power", "label": "Buyer Purchasing Power",
    "value": 612400.0, "unit": "dollars", "period": "2026-07-01",
    "ingested_at": "2026-07-02T05:10:00Z"
  }],
  "unavailable": ["goc_2yr"],  // present only when a tracked series is unavailable
  "decisions": {
    "boc": { "last_decision": "2026-07-15", "next_scheduled_decision": "2026-09-02" },
    "fed": { "last_decision": "2026-07-29", "next_scheduled_decision": "2026-09-16" }
  },
  "note": "Each rate shows its own latest official print date in 'period'..."
}
POST/v1/transmission

Trace how a shock travels. Given a starting series, this enumerates the causal channels leading out of it — which series are reached, through how many steps, how strong the historical relationship is, and the typical lag in days. Distinct from the counterfactual endpoint: that one propagates a numeric shock and returns magnitudes; this one maps the structure itself.

Request

POST /v1/transmission
{
  "signal": "wti_oil",        // series to trace FROM (alias-aware)
  "max_depth": 3,             // optional, default 3
  "min_strength": 0.0         // optional, drop weak paths
}

Response

{
  "trigger": { "signal_key": "wti_oil", "label": "WTI Crude Oil" },
  "max_depth": 3,
  "min_strength": 0.0,
  "paths": [{
    "signal_key": "cpi_inflation", "label": "CPI Inflation",
    "depth": 2, "order": "2nd order",
    "cumulative_strength": 0.41, "transmission_lag_days": 62,
    "mechanism": "Energy prices feed goods and transport costs...",
    "chain": [
      { "signal_key": "wti_oil", "label": "WTI Crude Oil" },
      { "signal_key": "gas_price_proxy", "label": "Weekly Retail Gas Price — Toronto" },
      { "signal_key": "cpi_inflation", "label": "CPI Inflation" }
    ],
    "current_value": 3.1, "unit": "percent", "value_withheld": null
  }],
  "path_count": 14,
  "truncated": false,
  "as_of": "2026-07-30",
  "note": "Structural transmission channels with historical strength and typical lag — not a numeric forecast. For a what-if with magnitudes, use the counterfactual endpoint."
}
GET/v1/predictions/track-record

ODIN’s graded forecasting record, in full. Every forward-looking call is written to a ledger with a deadline and graded against what actually happened — this endpoint serves that record, unedited, with the same filters as the rows (domain, target, status, date range) applied to the summary, so the rate you read always describes the slice you asked for. Status accepts pending, validated, invalidated, inconclusive, expired or all; all removes the status filter.

Read the split before the headline. Calls that stated a direction are the falsifiable ones and run near a coin flip; calls that a value would stay above or below a level are easier by construction and run far higher. One number covering both overstates what reality has tested, so the payload names them separately — and names the credits deducted for not clearing their own bar rather than quietly folding them in.

Response

{
  "as_of": "2026-08-12",
  "record_scope": "forward_only",
  "methodology_version": "v2-forward-only",
  "effective_at": "2026-08-04T12:06:55Z",
  "licensing": { "restricted_rows_withheld_from_listing": 12, "withheld_tallies": { "correct": 2, "incorrect": 2, "pending": 6, "inconclusive_or_expired": 2 }, "note": "Withheld from this listing for licensing; counted in every rate above." },
  "filters": { "domain": null, "target": null, "status": null,
               "date_from": null, "date_to": null },
  "summary": {
    "total": 2464, "pending": 1636, "resolved": 828,
    "decided": 481, "correct": 297, "incorrect": 184,
    "inconclusive": 280, "expired": 64,
    "credited_without_clearing_their_bar": 11,
    "credited_on_a_broken_comparison": 2,
    "decided_hit_rate_pct": 61.7,
    "directional": {
      "decided": 268, "correct": 158, "hit_rate_pct": 58.9,
      "what_this_is": "calls that stated a direction — the falsifiable ones. Treat this as ODIN's forecasting rate."
    },
    "held_at_a_level": {
      "decided": 213, "correct": 191, "hit_rate_pct": 89.7,
      "what_this_is": "calls that a value would remain above or below a level. Easier by construction; read the directional rate before relying on the headline."
    },
    "record_span_start": "2026-04-11", "record_span_end": "2026-07-30"
  },
  "rows": [{
    "prediction_id": "...", "title": "...", "status": "validated",
    "category": "rates", "direction_called": "down", "confidence": 0.63,
    "target": { "signal_key": "boc_overnight", "label": "BoC Overnight Rate",
                "unit": "percent", "domain": "monetary" },
    "registered_on": "2026-06-02", "deadline": "2026-07-15",
    "resolved_on": "2026-07-15",
    "predicted_value": 2.25, "actual_value": 2.25
  }],
  "row_count": 1,
  "truncated": false
}
GET/v1/predictions

The open book — forward-looking calls that have been registered but not yet graded, each with its deadline. Defaults to pending; filter by domain, target or status. Status accepts pending, validated, invalidated, inconclusive, expired or all; all removes the status filter.

Response

{
  "as_of": "2026-08-12",
  "record_scope": "forward_only",
  "methodology_version": "v2-forward-only",
  "effective_at": "2026-08-04T12:06:55Z",
  "filters": { "domain": null, "target": null, "status": "pending" },
  "licensing": { "restricted_rows_withheld_from_listing": 12, "note": "Some calls target data series whose licenses do not permit redistribution; those rows are withheld from this listing. Track-record rates on this API count them." },
  "rows": [{
    "prediction_id": "...", "title": "...", "status": "pending",
    "category": "rates", "direction_called": "down",
    "predicted_value": 2.5, "confidence": 0.63,
    "target": {
      "signal_key": "boc_overnight", "label": "BoC Overnight Rate",
      "unit": "percent", "domain": "monetary"
    },
    "registered_on": "2026-07-21", "deadline": "2026-09-02"
  }],
  "row_count": 1,
  "truncated": false,
  "boc_next_decision_call": {
    "status": "abstain",
    "reason": "No rate-decision call is currently on the record..."
  }
}
GET/v1/usage

Your own consumption read-back — requests and spend against the limits below. Call it as often as you like; it costs a read.

Response

{
  "organization": "Your Organization",
  "period": { "day": "2026-08-03", "month": "2026-08" },
  "requests": { "day": 18, "day_reason": 3, "day_reason_quota": 50, "month": 241 },
  "tokens": { "month_reason": 184220, "month_quota": 5000000 },
  "estimated_cost_usd": { "day": 0.42, "month": 8.17, "day_ceiling": 5.0 },
  "warnings": [],
  "resets": { "day_seconds": 12840, "month_seconds": 2430040 }
}

Errors

Errors return the matching HTTP status and a stable envelope. The code is the field to branch on; the message is plain English and safe to surface; request_id can be quoted to support.

{
  "error": {
    "code": "quota_exceeded",
    "message": "Daily request quota reached. Resets at 00:00 UTC.",
    "request_id": "req_..."
  }
}

During the final 14 days before a key expires, every authenticated response carries X-Key-Expires-In-Days and a plain-English X-Key-Expiry-Warning naming the exact expiry time and the rotation remedy. Expiry does not change: at that instant the response is the distinct 401 body below. Rotate the key in Settings → API keys, then update the integration with the newly issued key.

{
  "error": {
    "code": "key_expired",
    "message": "This API key expired on 2026-08-18T19:34:00Z. Rotate it in the app, then update this integration with the new key.",
    "request_id": "req_..."
  }
}
CodeHTTPMeaning
unauthorized401Missing or invalid key.
key_revoked401Key has been revoked.
key_expired401Key reached its stated expiry; rotate it in the app and update the integration.
org_not_enabled403Organization not enabled for API access.
scope_missing403Key lacks the scope for this endpoint.
licensing_restricted403Series is under a restrictive data license.
not_found404Unknown identifier.
signal_retired410Series has been retired.
invalid_request405 / 422Unsupported HTTP method or malformed request body.
personal_data_rejected422Obvious personal identifiers are not accepted by the reasoning boundary.
rate_limited429Per-minute burst exceeded.
quota_exceeded429Daily request or monthly token quota reached.
metering_unavailable503Usage metering could not safely admit or settle the request; retry after the stated delay.
database_busy503Database capacity is temporarily occupied; retry after the stated delay.
cost_ceiling429Daily spend circuit breaker tripped.
reasoning_timeout504Question needed more time than allowed — retry or raise max_seconds.
not_computed503Underlying value not yet computed.
internal_error500Unexpected error — quote the request id.

Rate limits & quotas

Daily and monthly quotas are enforced per organization. Per-minute burst controls are currently worker-local and therefore best-effort across a multi-worker deployment; an organization-global burst contract is still in development. The reasoning endpoint is the token-heavy one and has its own tighter budget; the read endpoints are generous. Rate headers are outcome-scoped, not universal: authentication and organization failures, missing-scope failures, request validation errors, unknown paths or methods, and failures before quota evaluation do not carry guaranteed rate headers.

Once an authenticated, in-scope request completes daily quota evaluation, its response carries X-RateLimit-Limit-Day, X-RateLimit-Remaining-Day, and X-RateLimit-Reset-Day, including later handler errors. A burst block carries the matching -Minute headers and Retry-After. A daily quota, monthly-token, or cost block carries the day headers and Retry-After. Remaining values are ledger snapshots, not concurrency reservations.

The outcome ledger still records every attributed attempt for operations and support. Daily request quota counts successful calls, deliberate authorization or safety-policy rejections. It does not count 429 burst, quota, or cost-ceiling denials, invalid_request validation failures, key_expiredfailures, or any 5xx response. This keeps ODIN’s contract and server failures off a partner’s allowance without making valid-but-forbidden traffic free or charging rejected work.

OutcomeGuaranteed rate headers
Quota evaluation completedX-RateLimit-Limit-Day, X-RateLimit-Remaining-Day, X-RateLimit-Reset-Day
Burst 429X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Reset-Minute, Retry-After
Quota or cost 429Day headers above, plus Retry-After
Pre-auth, scope, validation, unknown route/method, pre-evaluation failureNone guaranteed
ClassBurstDaily
reason5 / minPer-key request quota (default 50) + monthly token budget + daily spend ceiling
shared simulation pool: counterfactual + transmission10 / min combined200 / day combined
reads (health-index, regime, signals, policy-rates, predictions, predictions/track-record, ping, usage)30 / min2,000 / day per class

Counterfactual and transmission calls consume the same shared pool: every call to either endpoint counts toward the combined 200-per-day organization limit. Call GET /v1/usage any time for your own consumption read-back.

Privacy & retention

The declared retention period for api_v1_requests metadata is 24 months. The ledger records organization/key attribution, endpoint class, outcome, latency and usage. It has no request-body or response-body column; for /v1/reason, it stores only the question length and a SHA-256 fingerprint, not the question text.

Current enforcement status: not active. The ledger is append-only and database triggers block updates and physical removal. A default-off monthly scheduler stub performs no database work and removes no rows. The operator must separately approve an auditable, append-only-compatible expiry mechanism before the 24-month policy is physically enforced; until then, rows are retained beyond the policy target.

MCP server

ODIN’s native Model Context Protocol connection is live for organizations with API access enabled. Point your client at https://api.odinos.ca/mcp/v1with the same bearer key — no separate credential, nothing extra to integrate. It speaks JSON-RPC 2.0 (protocol 2025-06-18, stateless). For a staged self-serve key, tools/list discovers four tools: odin_economy_health, odin_regime, odin_counterfactual, and odin_signal. A controlled-partner key with the separately granted reason scope also discovers odin_reason. The list always reflects the scopes on your key.

POST /mcp/v1
{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "odin_economy_health",
    "arguments": {}
  }
}

Embed the conditions widget (free)

A live, ODIN-branded snapshot of Canadian economic conditions — the composite health score, the conditions currently prevailing, and key indicators — that any site can drop in with one iframe. No key, no signup; the widget fetches fresh data itself. Append ?theme=dark for a dark version.

Snippet

<iframe src="https://app.odinos.ca/embed/conditions" width="380" height="460" frameborder="0" title="ODIN — Canadian economic conditions"></iframe>

Keyed widget embeds are coming.

A note on what you get back.

ODIN answers in plain English and shows its work — when the evidence isn’t there, it says so rather than fabricating. Where a figure comes from outside ODIN’s own data, the answer carries the source it was retrieved from.

Interpretation is what this engine is for: reading conditions, tracing transmission, and telling you which channels are live. Its forward-looking calls are published and graded in the open — pull /v1/predictions/track-record and judge them yourself before you lean on them. Output is informational, not investment advice.