Purpose
The Oracle publishes its reading of the Demos testnet's public seeds, taken from outside. When fewer than two seeds give their own height, validators that answer as listed stand in, and witnesses says so. It is strictly watch-only: it observes, interprets, and publishes. It does not advise, predict, or issue instructions.
Your agent reads the state and decides what to do with it. The Oracle never tells you.
Endpoint
GET /organism
The default context is GET /organism. It returns the 17 required top-level fields plus six added in 1.1 (observed_at, data_quality_reason, height_last_advanced_at, height_static_seconds, active_public_conditions, agreement_detail) and two added in 1.2 (witnesses, height_standstill_after_seconds). No fleet data. Every top-level field is always present. The 17 required fields are never null; the four time and reason fields added in 1.1 are null when there is nothing to report. risk_factors is an array of strings; agreement_detail and last_24h are small objects whose fields may be null (nothing compared, or history still accumulating). Refreshes every 20 seconds.
Machine-readable contract: /organism/schema — JSON Schema with stability guarantees (additive-only within api_version 1.x) and changelog. unknown is a published value, not an HTTP error. Breaking field removals or type changes increment api_version. Validate your integration against it.
{
"status": "unknown",
"trend": "unknown",
"risk": "elevated",
"data_quality": "insufficient",
"confidence": "uncertain",
"agreement": "unknown",
"active_incidents": 0,
"max_incident_severity": "none",
"summary": "Insufficient data: fewer than 2 public nodes answered.",
"status_reason": "Insufficient data: fewer than 2 public nodes answered",
"risk_factors": [
"Only 1 of 3 public nodes answered — limited cross-checking"
],
"confidence_reason": "No cross-check: fewer than 2 public nodes answered",
"agreement_reason": "Not compared: fewer than 2 public nodes answered",
"staleness_seconds": 2,
"last_updated": "2026-10-05T19:32:28.216Z",
"api_version": "1.2",
"last_24h": {
"sufficient": false,
"coverage_pct": 0,
"observed_cycles": 5,
"expected_cycles": 17280,
"message": "Insufficient observation in the last 24 hours — building baseline.",
"computed_at": "2026-10-05T19:32:30.174Z"
},
"data_quality_reason": "too_few_answers",
"observed_at": "2026-10-05T19:32:28.216Z",
"height_last_advanced_at": null,
"height_static_seconds": null,
"active_public_conditions": 1,
"agreement_detail": {
"aligned_nodes": null,
"total_nodes": 1,
"median_block": 395007,
"block_spread": null
},
"witnesses": {
"mode": "insufficient",
"counted": 0,
"public_seeds": {
"configured": 3,
"answered": 1,
"own_height": 1
},
"validators": null
},
"height_standstill_after_seconds": 1800
}
A response captured from this version of the agent running against test seeds, not from the live network. The test setup reads every 5 s, so expected_cycles is 17,280 here rather than 4,320. Note the honest-uncertainty values (unknown, insufficient, uncertain) — your parser must treat these as first-class states, not errors.
That capture is a start with one seed answering and no validator kept yet: no reading. Once two seeds have agreed on the validators list, the same outage of two seeds reads as follows (the fields of the reading that differ, from the same test setup; observed_at, last_updated, staleness_seconds and last_24h are left out):
{
"status": "stable",
"data_quality": "sufficient",
"confidence": "clear",
"agreement": "strong",
"summary": "One public seed reported its own height, and 4 validators that answer as listed are within 25 blocks of it.",
"status_reason": "Heights advancing; one public seed and 4 validators aligned",
"risk_factors": [
"Only 1 of 3 public nodes answered — limited cross-checking",
"one public seed reported its own height; 4 validators are within 25 blocks of it"
],
"confidence_reason": "Observed public signals agree",
"agreement_reason": "One public seed and the closest of 4 validators within ±25 blocks of it (spread: 0 blocks)",
"data_quality_reason": null,
"height_last_advanced_at": "2026-10-05T19:32:38.221Z",
"height_static_seconds": 0,
"agreement_detail": {
"aligned_nodes": 2,
"total_nodes": 2,
"median_block": 395010,
"block_spread": 0
},
"witnesses": {
"mode": "seed_and_validators",
"counted": 5,
"public_seeds": {
"configured": 3,
"answered": 1,
"own_height": 1
},
"validators": {
"read": 4,
"own_height": 4,
"counted": 4,
"list_agreed_at": "2026-10-05T19:32:33.227Z"
}
}
}
Field Meanings
status — what DNO saw this cycle of the public seeds, or of the validators standing in for a missing one. A reading that would be stable is degraded after 30 minutes without a new height. stable degraded unstable unknown
risk — the observed conditions that reduce the margin of this reading. low elevated high
confidence — whether the compared heights are within 50 blocks of each other, a seed's own height is among them, beside one seed more than half of the validators that gave a height are within 25 blocks of it, and DNO is not following lower heights after a start-over. clear uncertain
data_quality — whether there is a reading (two seeds reported their own height, or validators stood in for the missing one) in an observation at most 300 s old. sufficient insufficient
agreement — whether the heights compared sit within ±25 blocks of the published median (strong also needs a spread of at most 20 blocks). The heights compared are the seeds' own; with validators standing in, the one seed's and the closest validator's, or the counted validators'. The median is the upper median of the seeds' own heights; beside one seed it is that seed's height; with no seed it is the upper median of every validator height read, counted or not. strong moderate weak unknown
trend — direction of change. improving stable worsening unknown
active_incidents — count of active public incidents. DNO's own condition records are not in it.
active_public_conditions — count of DNO's active condition records (visibility, degraded, unstable). They follow status; they do not feed it.
max_incident_severity — worst active incident: none info warning critical
staleness_seconds — seconds since last observation. Use for freshness judgment.
status_reason — one-sentence explanation of the current status.
risk_factors — array of strings; the concrete observations behind the current risk level.
confidence_reason — why confidence is at its current state.
agreement_reason — why agreement is at its current state.
witnesses — what the reading rests on. mode: seeds_only seed_and_validators validators_only insufficient. counted, public_seeds (configured, answered, own_height) and validators (read, own_height, counted, list_agreed_at; null when no validator was read). Counts and one time only: no key, no address, and no height attributed to a validator. With validators in the reading, agreement_detail also uses their heights: beside one seed median_block is the seed's own height and block_spread the distance to the closest validator; with no seed both are the validators' (the median is the upper median of every validator height read, counted or not: when only two gave a height it is the higher). Without a reading aligned_nodes and block_spread are null, and median_block is null too unless one seed gave its own height in an observation that is not stale: then it is that one height, which nothing was compared with.
height_standstill_after_seconds — the standstill limit: once height_static_seconds reaches it, a reading that would be stable is degraded.
last_24h — rolling 24-hour observability summary (coverage, observed vs expected cycles, chain movement, incident counts). Fields inside it may be null while history accumulates.
The Minimal Triple
The simplest consumption pattern reads three fields:
status + risk + confidence
These give DNO's reading in three values. Add data_quality to see whether DNO had enough to compare, and active_incidents and active_public_conditions for open records.
Reading: the seeds answered and two of three own heights sit within ±25 blocks of the median (agreement moderate); all three are within 50 blocks of each other.
Before Relying on a Reading
These fields describe how complete this observation is. DNO informs context; it does not advise, predict, score, certify, or decide action.
data_quality — insufficient means fewer than two seeds reported their own height and no validator stood in, or the observation is stale.
witnesses.mode — what the reading rests on. seed_and_validators: one seed, and the validators within 25 blocks of it. validators_only: no seed; DNO does not check who runs those validators, and confidence is uncertain.
staleness_seconds — how old the observation is. Above 300 s, data quality is insufficient.
confidence — uncertain means the compared heights are more than 50 blocks apart, the reading rests on validators alone, half or more of the validators that gave a height are more than 25 blocks from the one seed, DNO has been following heights below a height it read earlier (it cannot tell a chain restarted lower, nodes catching up, and one answer far above the others apart), or nothing was compared.
data_quality is insufficient, status will be unknown and risk will be elevated. This is intentional — the Oracle treats missing data as a reason for caution.Polling Guidance
The Oracle observes every 20 seconds, so polling more often than every 10–30 seconds returns the same observation. /organism and /health send an ETag and Last-Modified set to the observation time; a request with If-None-Match gets 304 until a new observation exists.
observed_at is the time of that observation. data_quality_reason names why data quality is insufficient (no_observation, stale, too_few_answers, too_few_heights) and is null otherwise. height_static_seconds is how long DNO's count has run: the time without a new height, as DNO reads it. The count stands on the highest seed height of the round or, with no seed height, on the height more than half of the counted validators have reached. It starts again in the round that height is above every height DNO had read before that round; when the count steps up to a height that is not, it runs from the first reading of the highest height read before that round. So it never runs from before DNO first read the height it stands on, or a higher one. It is published in every reading (after a start, once the stored rounds have been replayed), and is 0 in the round the count starts (the first round, a height above every height read, a start-over). Beside one seed the count is the seed's, and the texts say so ("the seed has shown no new height for N min"). It is null when the latest observation had no reading or has gone stale, and while the host's clock is behind the count's start. height_last_advanced_at is when DNO saw the last new height arrive: a seed above its own last answer, at the highest seed height of its round, and above every height DNO had read before that round from a seed or from a validator it counted. So a seed that catches up, a node back at a height already read, a node read for the first time, and a change in which nodes answered show none. It is null in a round without a reading or with a stale observation, when the count did not start with such an arrival (the height was first read from a seed not read before, from a counted validator before a seed reached it, or from validators alone, from which DNO takes no arrival), once the highest height DNO reads in a round (a seed's, or a counted validator's) is more than 25 blocks below the height that arrived (DNO then no longer claims that arrival), and from a start-over until the first arrival after it has ended (the height given up is read again, or a higher one, or more than 24 hours have passed since the last start-over). While it is null, height_static_seconds is a lower bound of how long that height has stood, with one exception: when a height given up at a start-over is read again without the count's own height passing it, the count runs from that height's first reading, before the start-over, until the count's own height has passed it, or steps up after a higher height was read, or DNO gives it up again. That is one or two rounds at a 10 s block time when the nodes DNO follows reach it themselves; up to 13 rounds (25 blocks) when a validator that still stands on it is counted; and until the next start-over, ten minutes of rises or more, when a seed that still stands on it answers. The cause is not known from here. Of a seed, only its own height counts (too_few_heights means fewer than two seeds reported their own). last_24h.chain_movement.blocks_advanced is the median height at the newest round in the window minus the oldest, over rounds with a reading whose median is a seed's own height (a round that rested on validators alone is not part of it), as observed; it is null when there is no pair to compare or the median fell by more than 25 blocks between two rounds, and it implies no target rate. For 24 hours after the own-height rule starts or restarts on a server that holds rounds an older version recorded, chain_movement is unknown with reason own_height_record_shorter_than_window and blocks_advanced is null: the rounds recorded under that rule do not cover the window yet.
Respect the Cache-Control: public, max-age=5 header. Do not poll faster than every 5 seconds.
For less time-sensitive consumers, polling every 60 seconds is sufficient.
Prometheus scrape of observed public-RPC reachability and latency: GET /metrics. Families are emitted only when a sample exists. This is an export of observations, nothing more.
Consumption Examples
curl
curl -s https://demos-oracle.com/organism | jq '{status, risk, confidence}'
JavaScript
const res = await fetch('https://demos-oracle.com/organism');
const state = await res.json();
if (state.status === 'unstable' || state.risk === 'high') {
// your agent's own rule for this reading
}
Python
import requests
state = requests.get('https://demos-oracle.com/organism').json()
if state['data_quality'] == 'insufficient':
pass # DNO could not compare enough heights: your agent decides
Other Endpoints
/health — full diagnostic with agreement details, public nodes, witnesses, signals, and the on-chain validator counts. Its agreement object also has min_block and max_block, the lowest and highest of the heights compared: with validators in the reading they are validators' heights, unattributed. It has no summary. In signals_grouped, first_seen and duration_min count from the first /health request that showed the signal, not from when its condition began. On /incidents, detectedBlock and resolvedBlock are null when no block is attached to a record, as on every condition record.
/incidents — public incident log (?status=active|resolved, ?limit=1–500). Rows with kind: "condition" are DNO's own condition records (visibility, degraded, unstable); they are counted in active_public_conditions, not in active_incidents. A record may carry note: a note DNO added later (added: the day this server first showed it; text); the stored record is never edited.
/catalog — peer-listed identities: identities listed on the public seeds' peerlists, kept once two public peerlists have listed them (one peerlist brings at most 50 new identities into that count per crawl), with what those peerlists reported this cycle (?q= filters by the end of a display name or a truncated key; ?listed=now or ?listed=not by whether the latest crawl listed the row; ETag / 304 between observations). /catalog/lookup?key=0x… checks a full key exactly and returns only the sanitized row, and on_chain: where that key stands on the agreed validators list (status ACTIVE, UNSTAKING, other or not_listed, with seeds_agreed, seeds_configured and observed_at). status is null, with the list's reason, whenever on_chain_validators is not agreed (two seeds returning the same list, read in the last 300 s); on_chain is null for a key that is not valid. not_listed also covers a key that has exited or whose stake takes effect at a later block: the SDK describes getValidators as the validators in force at the block read. Peer-listed identities are never dialed. first_seen is the first public listing DNO counted toward keeping the identity (the count is kept in memory, so a restart starts it again) and last_listed the last public crawl that listed it; rows recorded before 1.1 (when DNO also read its own node's peerlist) appear once two public peerlists have listed them, like any other row. The reported block exists only for identities listed in the latest crawl: height and sync status come from the listing with the highest height, and the online flag is set when any listing peerlist reports it online. The peerlists' second status flag is not published: what it means has not been confirmed.
/health validator_growth keeps the two populations apart: discovered and discovered_online count catalog rows (online as reported in the latest crawl); monitored, monitored_online and monitored_at_head count the configured seeds DNO dials. network_head is the highest own height among those seeds in this round, not the chain's head; it is 0 when no seed gave one, which is not a height. monitored_at_head counts the seeds that answered fewer than 100 blocks below it, and a row's lag and sync_pct are measured against it (null when it is 0). online and synced add seeds and catalog rows together; mixed_fields names them, they are kept for 1.x compatibility, and they are not a count of either population. first_counted has today, week and month: the identities first counted in the last 24 h, 7 days and 30 days, each null while the count does not cover the window or when a gap in the count crosses the window's start (reason says why; several causes are joined with semicolons, each naming its window), and since, when the count of new identities started. That is one hour after DNO first counted an identity: the first hour is the starting set, what the peerlists already listed, and it is in no window. An identity that an older DNO record already held is in no window either, whenever the public peerlists come to list it. Nor is an identity that a peerlist lists in its first read after DNO starts, before DNO keeps it: it may have been listed there before, and DNO keeps the identities waiting for a second peerlist in memory only. The top-level today, week and month are the 1.0 fields: the same counts as numbers, partial where first_counted has null.
/health on_chain_validators is the on-chain validators table as the public seeds list it: listed, active, unstaking (null when the list has no UNSTAKING row), other_status, min_validator_stake (a raw whole-number string, as reported), seeds_configured, seeds_answered, seeds_agreed, observed_at and reason. From DNO's own record, only while the list agrees: first_agreed_today, first_agreed_week and first_agreed_month, the ACTIVE keys DNO first counted on an agreed list in the last 24 h, 7 days and 30 days, each null when the record does not cover the window (first_agreed_reason says why); first_agreed_as_of, the list they were computed at; and first_agreed_since, the record's first list (the first agreed list with an ACTIVE key). The keys on that first list never count as new, and the figures are not net of exits. Outside agreed every first-agreed figure is null and first_agreed_reason is the list's own reason. state is agreed, not_agreed (fewer than two seeds returned the same list; every count is null), stale (the last read is older than 300 s) or pending.
/health validator_watch counts what DNO saw when it dialed the address each ACTIVE validator published: watched, not_dialed with not_dialed_reasons (no_address, not_public_http, name_unresolved, seeds_differ, over_cap), no_answer, answered_other_key with other_key_shared (those whose published origin answered as another key listed on it) and other_key_shared_origins, answered_no_key, answered_as_listed with answered_as_listed_seeds (how many of them are public seeds: an overlap, never added), origins_dialed (distinct published origins dialed) and max_rows_per_origin (the most ACTIVE rows that published one of them), then at_seed_height, off_seed_height, height_not_reported and height_not_compared among those answers, every_round_last_hour with its window, and versions. state is observed, no_agreed_list, read_fault (a dial of that round ended in a fault of DNO's own read, so nothing is said of the validators it dialed), stale, disabled or pending; outside observed every count is null. at_seed_height is null when the seeds' median is not known. every_round_last_hour is null until DNO has watched for an hour with at least half the expected rounds counted, and in any round that did not count itself (window.counted_this_round false). Version groups are named only in a release form shared by at least two validators. A round runs once a minute. Neither object is in /organism, and neither ever carries an address, a key, a validator's own height or any validator's stake; reference_height is the seeds' median and min_validator_stake the network parameter. Up to 8 of the validators that answer as listed at the seeds' height are kept as candidates, and only those are read in a seed's place.
/health publicNodes[].error says why a seed gave no answer. timeout, connection failed, HTTP 503 (with the status the seed sent), invalid response and response too large are what DNO saw of the seed. internal error is a fault in DNO's own read (a TypeError or a ReferenceError inside it; a fault of another kind in DNO's own code would show under one of the other values): the reading still counts that seed as not answered, so status_reason can say "fewer than 2 public nodes answered" when the fault is DNO's.
/health publicNodes[].height_source says where a seed's height came from: self (its own peerlist entry) or first_peer. A first_peer height is shown on that row only; it is not the seed's height and is left out of data quality, agreement (total_nodes, aligned_nodes, the median, the spread), network_head and validator_growth.
/version — running is DNO's own version; nodeVersion the version label most answering seeds report (null when none reads as a release version); latestCommit, latestMessage, latestDate and checkedAt the newest commit on the master branch of DNO's public repository as GitHub gave it at checkedAt, which need not be the code this server runs.
/commerce/observations — commerce infrastructure reachability (Layer 2, independent from the core network assessment).
/methodology — how the Oracle makes assessments.
/commerce/methodology — how commerce observation works.
Error Handling
The Oracle is designed to always return valid JSON with all fields present. However, your agent should handle network and edge-case failures gracefully.
HTTP errors — The Oracle may return 502/503 during restarts (typically <30 seconds). Retry with exponential backoff.
Stale data — If staleness_seconds exceeds 120, the Oracle may be restarting or experiencing issues. The data describes the last observation, not the current moment.
Insufficient data quality — When data_quality is insufficient, the Oracle cannot see enough public nodes to form a reading. All other fields are still present and describe that limited view: status is unknown and risk elevated.
Timeouts — DNO's own endpoints normally answer in well under a second. If the Oracle does not respond, the last response received remains the latest observation available to your agent; observed_at in it says how old that observation is.
Edge Cases
These state combinations occur:
status: unknown + data_quality: insufficient
The Oracle cannot see enough public nodes to assess the network: fewer than two seeds gave their own height, and no validator stood in. This is not "the network is down" — it is "the Oracle cannot tell." data_quality_reason says why.
status: degraded + confidence: clear
The heights compared agree only moderately (at least 60% within ±25 blocks of the median, rounded up), a warning-level public incident is active, or DNO has counted 30 minutes without a new height (status_reason then begins "No new height for", or beside one seed "The seed has shown no new height for"); and the compared heights are within 50 blocks of each other.
agreement: unknown
Heights were not compared: fewer than 2 public seeds reported their own block height and no validator stood in, or the observation is stale (data_quality_reason says which). It always accompanies data_quality: insufficient. It does not mean the seeds disagree — it means the Oracle cannot observe enough to tell.
trend: unknown
Not enough recent history to compute a trend. Common after Oracle restarts or data gaps. Trend resets to unknown until enough consecutive observations accumulate. This is normal, not an error.
staleness_seconds > 60
The Oracle's last observation is more than a minute old. Staleness means DNO's own round did not complete: the process stopped or is restarting, or a round failed inside DNO. A seed that does not answer does not cause it. The data is the last observation; it is not fresh.
status: stable + risk: elevated (new in 1.2)
Validators stand in for a missing seed (witnesses.mode seed_and_validators or validators_only; before 1.2 this reading was unknown), or DNO is following heights below a height it read earlier (any mode, seeds_only included). risk_factors says which. Do not infer the mode from the pair: read witnesses.mode.
status: stable + confidence: uncertain (new in 1.2)
Validators alone give the reading; or, beside one seed, half or more of the validators that gave a height are more than 25 blocks from it; or DNO is following heights below a height it read earlier (seeds_only included). confidence_reason names the first of these that holds. DNO does not check who runs a validator.
status: degraded + agreement: strong, no incident (new in 1.2)
A standstill: the heights agree, and DNO has seen no new height for 30 minutes. status_reason starts "No new height for", or beside one seed "The seed has shown no new height for". It can read stable again for up to 30 minutes without a new block: when a node DNO had not read before, validators counted for the first time, or one answer of a seed far above the others shows a higher height. With no seed answering it can also be a chain that restarted at a lower height: with validators alone DNO gives no height up, and reports no new height while their heights rise below the one it holds: until a seed gives its own height again and has risen for 10 minutes, until a seed or the validators pass that height, or until the validators can stand in no longer (24 hours at most).
Production Python Example
import requests
import time
import logging
ORACLE_URL = "https://demos-oracle.com/organism"
MAX_STALENESS = 120 # seconds
POLL_INTERVAL = 20 # seconds
MAX_RETRIES = 3
logger = logging.getLogger("oracle_consumer")
def fetch_oracle_state():
# Fetch and validate Oracle state with error handling
for attempt in range(MAX_RETRIES):
try:
r = requests.get(ORACLE_URL, timeout=10)
r.raise_for_status()
state = r.json()
# Validate required fields exist
required = ["status", "risk", "confidence",
"data_quality", "staleness_seconds"]
for field in required:
if field not in state:
logger.warning(f"Missing field: {field}")
return None
return state
except requests.exceptions.Timeout:
logger.warning(f"Oracle timeout (attempt {attempt + 1})")
time.sleep(2 ** attempt)
except requests.exceptions.HTTPError as e:
logger.warning(f"Oracle HTTP {e.response.status_code}")
if e.response.status_code in (502, 503):
time.sleep(5) # Oracle restarting
else:
return None
except Exception as e:
logger.error(f"Oracle error: {e}")
return None
return None # All retries exhausted
def assess_reliability(state):
# Determine if the Oracle data is reliable enough to act on
if state is None:
return {"reliable": False, "reason": "Oracle unreachable"}
if state["data_quality"] == "insufficient":
return {"reliable": False,
"reason": "Oracle has limited visibility"}
if state["staleness_seconds"] > MAX_STALENESS:
return {"reliable": False,
"reason": f"Data is {state['staleness_seconds']}s old"}
if state["confidence"] == "uncertain":
return {"reliable": True, # your own rule decides; DNO says why
"reason": state.get("confidence_reason"),
"flagged": True}
return {"reliable": True, "reason": None}
# Usage
state = fetch_oracle_state()
check = assess_reliability(state)
if check["reliable"]:
# state["status"], state["risk"], etc. describe the latest observation
pass
else:
logger.info(f"Oracle data unreliable: {check['reason']}")
# Keep the last observation received
Combining Endpoints
The default context is GET /organism.
Optional context: GET /health (attestation: DAHR attempted on the cross-check RPCs, not on the seeds whose answers enter status; on_chain_publication); GET /peers (discovered set — crawl output, not a registry); GET /commerce (Layer 2 only; not an input to status, risk, or agreement).
Other endpoints add detail. They do not replace /organism.
# Minimal: just the state GET /organism # Detailed: state + diagnostic data GET /organism → status, risk, confidence GET /health → agreement details, node counts, signals GET /incidents → specific active problems # Commerce: attestation authority reachability GET /commerce/observations → authority states (Layer 2, separate from the core network assessment)
Important: /commerce/observations is Layer 2 data. It reports whether attestation authority endpoints are reachable. It does not influence and is not influenced by the network assessment in /organism. These are independent observation layers.
Commerce API (Layer 2)
The Oracle also monitors attestation authority endpoints used by the Demos agent commerce protocol. This is a separate observation layer.
GET /commerce/observations
{
"layer": "commerce_intelligence",
"network_context": "testnet",
"commerce_observability_state": "healthy",
"authorities_observed": 10,
"authorities_healthy": 10,
"authorities": [
{
"authority_id": "gleif",
"name": "GLEIF",
"category": "public_api",
"reachability": "healthy",
"freshness": "fresh",
"observed_at": "2026-05-18T..."
}
]
}
commerce_observability_state — overall: healthy degraded partial unavailable unknown
reachability — per authority: healthy degraded unavailable unknown
/organism status, risk, trend, agreement, confidence, or data quality. These are architecturally independent observation layers.Full commerce methodology: /commerce/methodology
HTTP Status Codes
200 — Success. Valid JSON response.
301 — Redirect (e.g., /home → /).
401 — Unauthorized. The one admin route, /private/commerce/status, needs the X-DNO-Admin-Token header. It is not part of the public API.
404 — Endpoint not found.
502/503 — Oracle restarting. Retry after 5–10 seconds.
Content-Type: application/json, except /federate and /metrics (Prometheus text), /badge (an SVG image) and the pages (HTML). All are served with Access-Control-Allow-Origin: * (CORS enabled). /docs lists every route.Design Principle
The Oracle publishes its core assessment. Your agent interprets it.
DNO informs context; it does not advise, predict, score, certify, or decide action.
