Integration Guide
Headless Oracle provides a cryptographically verifiable defensive execution layer for autonomous agents operating across global markets.
Chirindo Witness quickstart
An operator sends signed chain checkpoints to the witness, which records each one and signs a receipt saying when it saw it. The witness attests only “at time T, I received this checkpoint, validly signed by the key with this thumbprint”. It does not attest who owns the key, nor that the records are true.
What it adds: a cut-off tail, or a rewrite by the key holder (edit, delete or reorder, then re-sign and re-link), of any history up to the last witnessed checkpoint, made after that checkpoint was witnessed, is detected when the chain is compared with the witness's receipts. Records after the last witnessed checkpoint, and a history rewritten before it was first witnessed, are not. The full list is in the Witness spec, under honest_limits.
The free pool needs no account and no key: anonymous checkpoints share one daily pool (figure in the plan table below). Because anyone can submit without an account, anyone can use up the rate limit or the daily cap until it resets; paid plans get their own key and quota.
Send your first checkpoint
Witness accepts only a checkpoint signed with an Ed25519 key, so the request reads its body from a file. This script makes a key and a one-entry checkpoint and writes checkpoint.json. It needs Node 20 or later and nothing else.
checkpoint.mjs
import { generateKeyPairSync, sign, createHash } from "node:crypto"; import { writeFileSync } from "node:fs";
const { privateKey, publicKey } = generateKeyPairSync("ed25519"), x = publicKey.export({ format: "jwk" }).x;
const sha = (s, enc) => createHash("sha256").update(s).digest(enc), kid = sha(`{"crv":"Ed25519","kty":"OKP","x":"${x}"}`, "base64url");
const cp = { count: 1, kid, last_entry_hash: "sha256:" + sha("first entry", "hex"), session_id: "try-" + Date.now(), ts: new Date().toISOString(), type: "checkpoint", v: "evidence.action/1" };
cp.sig = sign(null, Buffer.from(JSON.stringify(cp)), privateKey).toString("base64url");
writeFileSync("checkpoint.json", JSON.stringify({ checkpoint: cp, public_key_jwk: { kty: "OKP", crv: "Ed25519", x } }));
node checkpoint.mjs curl -sS https://api.headlessoracle.com/v1/witness/checkpoints \ -H 'Content-Type: application/json' \ --data-binary @checkpoint.json
Witness answers 201 with a receipt signed by Headless Oracle. The checkpoint's keys are written in sorted order, so JSON.stringify gives the exact bytes the spec signs. With the gate, the count and the head hash come from your log; here they stand in for one placeholder entry.
With the Chirindo gate in front of your MCP server, checkpoints come from your real log: see /witness for npx -y @headlessoracle/chirindo init, proxy and verify.
Verify a receipt
- Fetch the receipts from Witness yourself. Public, no auth:
GET https://api.headlessoracle.com/v1/witness/checkpoints?kid=<thumbprint>&session_id=<id>. Verifying against a sidecar file trusts the operator who supplied it. Only querying the witness is independent of the operator. - Take the Witness public key. Use
public_key(hex, 32 raw bytes) of thekeys[]entry in GET https://api.headlessoracle.com/v5/keys whosekey_idequals the receipt'spublic_key_id, and pin it. - Check the signature. The signed bytes are all receipt fields except
signature, keys sorted,JSON.stringifywith no whitespace, UTF-8; the signature is Ed25519, hex. - Compare the log with the receipts. In your browser at /verify, or offline with @headlessoracle/receipt-verify and
chirindo verify. A log shorter than a witnessed count, or different from what Witness saw at that count, fails, and the check names the count where it fails.
Plans
| Plan | Plan id | Price | Quota |
|---|---|---|---|
| Free pool | witness_free | $0 | 2,000 new checkpoints per UTC day, shared by every anonymous caller |
| Evidence Starter | custody_90d | $49/month | Up to 1,000 new checkpoints per UTC day, your own key |
| Evidence | custody_1y | $199/month | Up to 3,000 new checkpoints per UTC day, your own key |
Buy from /pricing, or start the checkout yourself. POST /v5/checkout returns a transaction_id for Paddle checkout and a claim_token; after payment, POST /v5/claim with the claim_token answers {"state":"ready","key":"…"} once the payment is recorded. Until then it can answer 404 or carry retry_after_seconds, so poll.
curl -sS -X POST https://headlessoracle.com/v5/checkout \
-H 'Content-Type: application/json' \
-d '{"plan":"custody_90d"}'
curl -sS -X POST https://headlessoracle.com/v5/claim \
-H 'Content-Type: application/json' \
-d '{"claim_token":"<claim_token from the checkout response>"}'
Machine-readable: the Witness spec (request format, every check in order, receipt format, signing and its limits) and the prices at /v5/pricing.
28 exchanges. Global 24/7 coverage.
The scheduling complexity a timezone library was not built to solve.
67
exchange-specific holidays
different per country, non-overlapping
18
early close days
NYSE closes at 1:00 PM, not 4:00 PM
8
DST transitions
incl. 3-week US/EU phantom window
490
lunch break sessions
Tokyo 11:30–12:30, HK 12:00–13:00
728
weekend days across 28 venues
per calendar year
A timezone library handles zero of these. Headless Oracle handles all of them.
Think it's simple?
Common assumptions about market schedules, and what the data actually shows.
| What you assume | What actually happens |
|---|---|
| NYSE closes at 4pm ET | Except Good Friday (1pm), day before July 4th (1pm), Christmas Eve (1pm), and emergency halts at any time |
| Tokyo is open 9am–3pm JST | There's a lunch break 11:30–12:30 every trading day. Your bot thinks it's open. It's not. |
| "Just use a timezone library" | pytz handles DST. It doesn't handle that NYSE closes for Juneteenth but LSE doesn't, or that Hong Kong closes for Lunar New Year but Singapore doesn't. |
| "I'll hardcode the holidays" | Which year? 2026 holidays differ from 2027. When a holiday falls on a weekend, some exchanges shift to Friday, others Monday, others don't shift. |
| "I'll check once at market open" | Circuit breaker at 2:47pm. Your cached "OPEN" is now wrong. Your bot trades into a halted market. |
Quick Start
Using Claude Code? Add Headless Oracle to Claude Code in 3 steps →
Copy one .mcp.json file and your agent has pre-trade verification instantly.
Try the public demo endpoint instantly — no API key needed:
curl https://headlessoracle.com/v5/demo # Any supported exchange: curl "https://headlessoracle.com/v5/demo?mic=XLON" curl "https://headlessoracle.com/v5/demo?mic=XJPX"
For production use, fetch a signed attestation with your API key:
curl -X GET "https://headlessoracle.com/v5/status?mic=XNYS" \
-H "X-Oracle-Key: YOUR_API_KEY"
Check when the next session opens (no auth required):
curl "https://headlessoracle.com/v5/schedule?mic=XNYS"
Verify Oracle's signing infrastructure is live:
curl "https://headlessoracle.com/v5/health"
Supported Exchanges
28 exchanges across 7 regions (including derivatives and 24/7 crypto). All DST transitions are handled server-side using IANA timezone data — no hardcoded UTC offsets.
| MIC | Exchange | Local Hours | Timezone | DST |
|---|---|---|---|---|
| XNYS | New York Stock Exchange | 09:30 – 16:00 | America/New_York | Mar 8 |
| XNAS | NASDAQ | 09:30 – 16:00 | America/New_York | Mar 8 |
| XBSP | B3 (São Paulo Stock Exchange) | 10:00 – 17:00 | America/Sao_Paulo | Brazil (Nov) |
| XLON | London Stock Exchange | 08:00 – 16:30 | Europe/London | Mar 29 |
| XPAR | Euronext Paris | 09:00 – 17:30 | Europe/Paris | Mar 29 |
| XSWX | SIX Swiss Exchange | 09:00 – 17:30 | Europe/Zurich | Mar 29 |
| XMIL | Borsa Italiana (Milan) | 09:00 – 17:30 | Europe/Rome | Mar 29 |
| XHEL | Nasdaq Helsinki | 10:00 – 18:30 | Europe/Helsinki | Mar 29 |
| XSTO | Nasdaq Stockholm | 09:00 – 17:30 | Europe/Stockholm | Mar 29 |
| XIST | Borsa Istanbul | 10:00 – 18:00 | Europe/Istanbul | Mar 29 |
| XSAU | Saudi Exchange (Tadawul) Sun–Thu | 10:00 – 15:00 | Asia/Riyadh | None |
| XDFM | Dubai Financial Market Sun–Thu | 10:00 – 14:00 | Asia/Dubai | None |
| XJSE | Johannesburg Stock Exchange | 09:00 – 17:00 | Africa/Johannesburg | None |
| XSHG | Shanghai Stock Exchange | 09:30 – 15:00 (lunch 11:30–13:00) | Asia/Shanghai | None |
| XSHE | Shenzhen Stock Exchange | 09:30 – 15:00 (lunch 11:30–13:00) | Asia/Shanghai | None |
| XHKG | Hong Kong Exchanges and Clearing | 09:30 – 16:00 (lunch 12:00–13:00) | Asia/Hong_Kong | None |
| XJPX | Japan Exchange Group (Tokyo) | 09:00 – 15:30 (lunch 11:30–12:30) | Asia/Tokyo | None |
| XKRX | Korea Exchange (Seoul) | 09:00 – 15:30 | Asia/Seoul | None |
| XBOM | BSE Ltd (Bombay Stock Exchange) | 09:15 – 15:30 | Asia/Kolkata | None |
| XNSE | National Stock Exchange of India | 09:15 – 15:30 | Asia/Kolkata | None |
| XSES | Singapore Exchange | 09:00 – 17:00 | Asia/Singapore | None |
| XASX | Australian Securities Exchange (Sydney) | 10:00 – 16:00 | Australia/Sydney | Oct (AU) |
| XNZE | New Zealand Exchange (Auckland) | 10:00 – 16:45 | Pacific/Auckland | Oct (NZ) |
Also covered, not listed in the table: XCBT, XNYM, XCBO, XCOI and XBIN.
All times are local to the exchange. Retrieve the full exchange directory programmatically at /v5/exchanges.
2026 DST Risk Events
Any bot using hardcoded UTC offsets will compute incorrect hours after these dates:
- Mar 8 — US clocks spring forward (EST → EDT). Affects XNYS, XNAS.
- Mar 29 — UK/EU clocks spring forward (GMT/CET → BST/CEST). Affects XLON, XPAR.
- Oct 25 — UK/EU clocks fall back. Affects XLON, XPAR.
- Nov 1 — US clocks fall back. Affects XNYS, XNAS.
Headless Oracle handles all transitions automatically.
Available Endpoints
GET /v5/status Authenticated
Primary endpoint for production market status checks.
Header: X-Oracle-Key: YOUR_API_KEY Query: mic=XNYS (or any of the 28 supported MIC codes — see /v5/exchanges)
GET /v5/demo Public
Returns a signed receipt for any exchange. No auth required. Use this for integration testing and verification development.
Query: mic=XNYS (optional, defaults to XNYS)
GET /v5/schedule Public
Returns the next scheduled open and close times for any exchange. Includes lunch_break for exchanges with midday breaks (XJPX, XHKG). Does not reflect real-time halts or manual overrides.
{
"mic": "XJPX",
"name": "Japan Exchange Group (Tokyo)",
"timezone": "Asia/Tokyo",
"queried_at": "2026-03-10T01:00:00.000Z",
"current_status": "OPEN",
"next_open": "2026-03-10T03:30:00.000Z",
"next_close": "2026-03-10T06:30:00.000Z",
"lunch_break": { "start": "11:30", "end": "12:30" },
"note": "Times are UTC. lunch_break times are local exchange time (see timezone field)."
}
// Exchanges without a lunch break return lunch_break: null
// { ..., "lunch_break": null, ... }
GET /v5/exchanges Public
Returns the full directory of supported exchanges with MIC codes, names, and timezones.
GET /v5/keys Public
Returns the current Ed25519 public key registry and the canonical payload specification for independent signature verification.
{
"keys": [{
"key_id": "key_2026_v1",
"algorithm": "Ed25519",
"format": "hex",
"public_key": "03dc27993a2c90856cdeb45e228ac065f18f69f0933c917b2336c1e75712f178",
"valid_from": "2026-01-01T00:00:00Z",
"valid_until": null
}],
"canonical_payload_spec": { ... }
}
// valid_until is null when no key rotation is scheduled.
// It will be set in advance of any planned rotation.
GET /v5/batch Authenticated
Fetch signed status receipts for multiple exchanges in one request. Each receipt is independently signed and verifiable in isolation.
Header: X-Oracle-Key: YOUR_API_KEY Query: mics=XNYS,XNAS,XLON (comma-separated, deduplicated)
All MICs are validated up front — one invalid MIC returns 400 for the whole request. Tier 3 signing failure fails the whole batch.
GET /v5/health Public
Signed liveness probe. Use this to distinguish Oracle is down from market is genuinely UNKNOWN. A valid signed response means the signing infrastructure is alive.
{
"receipt_id": "uuid-v4",
"issued_at": "2026-03-10T13:00:00.000Z",
"expires_at": "2026-03-10T13:01:00.000Z",
"status": "OK",
"source": "SYSTEM",
"public_key_id": "key_2026_v1",
"signature": "hex_string"
}
// 200 + valid signature = Oracle is alive
// 500 CRITICAL_FAILURE = signing system offline
Response Schema
{
"receipt_id": "uuid-v4",
"issued_at": "ISO-8601-Timestamp (UTC)",
"expires_at": "ISO-8601-Timestamp (UTC, issued_at + 60s)",
"mic": "XNYS",
"status": "OPEN", // OPEN | CLOSED | HALTED | UNKNOWN
"source": "SCHEDULE", // SCHEDULE | OVERRIDE | SYSTEM
"schema_version": "v5.0",
"public_key_id": "key_2026_v1",
"signature": "hex_string"
}
// Do not act on a receipt whose expires_at has passed.
Status Values
- OPEN: The venue is in an active trading session. Single-name halts do not change venue status; see /halt-gate.
- CLOSED: Market is outside trading hours — weekend, holiday, or after close.
- HALTED: Market has been manually halted (circuit breaker or emergency override). Treat identically to CLOSED.
- UNKNOWN: Oracle cannot determine status. Treat as CLOSED. Halt all execution.
Source Values
- SCHEDULE: Status derived from the exchange holiday/hours calendar.
- OVERRIDE: Status set manually. Active during circuit breakers or emergency halts. Includes a
reasonfield. - SYSTEM: Internal fallback — returned when an error occurred in the primary logic. Always paired with UNKNOWN.
Verification Logic
To confirm a receipt is authentic, reconstruct the signed payload and verify the Ed25519 signature against the published public key.
1. Reconstruct the Payload
The signature covers a fixed allowlist of receipt fields enumerated in canonical_payload_spec at /v5/keys (union of receipt_fields, override_fields, health_fields). Filter the response to that allowlist, sort the keys alphabetically, then minify with no whitespace. Decoration the worker added to the response after signing — extensions, a duplicated receipt wrapper, discovery_url — must be excluded so the message bytes match what was signed.
// 1. Fetch the canonical field spec from /v5/keys
const { keys, canonical_payload_spec: spec } =
await fetch("https://headlessoracle.com/v5/keys").then(r => r.json());
// 2. Build the allowlist (union of all receipt-type field lists)
const canonicalFields = [
...spec.receipt_fields,
...(spec.override_fields || []),
...(spec.health_fields || []),
].filter((v, i, a) => a.indexOf(v) === i).sort();
// 3. Filter the receipt to the allowlist (drops signature + decoration)
const filtered = {};
for (const key of canonicalFields) {
if (key !== "signature" && key in receipt) filtered[key] = receipt[key];
}
const canonical = JSON.stringify(filtered); // no spaces, alphabetical keys
2. Implementation Samples
Python (PyNaCl)
import json, requests
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError
ORACLE_BASE = "https://headlessoracle.com"
def verify_and_check(mic="XNYS"):
# Fetch the receipt and the public key + canonical field spec
receipt = requests.get(
f"{ORACLE_BASE}/v5/status?mic={mic}",
headers={"X-Oracle-Key": "YOUR_KEY"},
timeout=4,
).json()
keys_doc = requests.get(f"{ORACLE_BASE}/v5/keys", timeout=4).json()
public_key_hex = keys_doc["keys"][0]["public_key"]
spec = keys_doc["canonical_payload_spec"]
# Build the allowlist (union of receipt / override / health fields)
canonical_fields = sorted(set(
spec["receipt_fields"]
+ spec.get("override_fields", [])
+ spec.get("health_fields", [])
))
# Filter the receipt to the allowlist — drops signature + decoration
# (extensions, discovery_url, duplicate `receipt` wrapper)
filtered = {k: receipt[k] for k in canonical_fields
if k != "signature" and k in receipt}
# Canonical message: alphabetical sort, no whitespace, UTF-8
canonical = json.dumps(filtered, sort_keys=True, separators=(",", ":"))
sig = bytes.fromhex(receipt["signature"])
try:
VerifyKey(bytes.fromhex(public_key_hex)).verify(canonical.encode(), sig)
except BadSignatureError:
return False # fail closed
return receipt["status"] == "OPEN"
JavaScript (Web Crypto API)
const ORACLE_BASE = "https://headlessoracle.com";
async function verifyReceipt(receipt) {
// Fetch the public key + canonical field spec
const { keys, canonical_payload_spec: spec } =
await fetch(`${ORACLE_BASE}/v5/keys`).then(r => r.json());
// Build the allowlist (union of receipt / override / health fields)
const canonicalFields = [
...spec.receipt_fields,
...(spec.override_fields || []),
...(spec.health_fields || []),
].filter((v, i, a) => a.indexOf(v) === i).sort();
// Filter the receipt to the allowlist — drops signature + decoration
const filtered = {};
for (const key of canonicalFields) {
if (key !== "signature" && key in receipt) filtered[key] = receipt[key];
}
const canonical = JSON.stringify(filtered);
const keyBytes = hexToBytes(keys[0].public_key);
const sigBytes = hexToBytes(receipt.signature);
const msgBytes = new TextEncoder().encode(canonical);
const cryptoKey = await crypto.subtle.importKey(
"raw", keyBytes, { name: "Ed25519" }, false, ["verify"]
);
return crypto.subtle.verify({ name: "Ed25519" }, cryptoKey, sigBytes, msgBytes);
}
function hexToBytes(hex) {
return new Uint8Array(hex.match(/.{2}/g).map(b => parseInt(b, 16)));
}
Or use the browser-based verifier — paste any receipt and verify the Ed25519 signature instantly with zero server calls. An independent client-side verifier on a separate trust boundary is also live at verify.headlessoracle.com.
Reference implementations: github.com/headlessoracle/demo-agent — ~100-line Node.js reference verifier (MIT). Architectural context: github.com/headlessoracle/essays — including The Trust Primitive: Why Agent Commerce Needs Environment-State Attestation (v1.6.4, April 2026, CC BY 4.0).
Fail-Closed Architecture
The oracle uses a three-tier safety cascade. If anything fails at any tier, the response always defaults to UNKNOWN — never a false OPEN.
| TIER 0 | Manual override active — returns HALTED or CLOSED with signed reason. |
| TIER 1 | Schedule-based calculation — checks holidays, weekends, trading hours, half-days, lunch breaks. |
| TIER 2 | Fail-closed safety net — any Tier 1 error returns a signed UNKNOWN receipt. |
| TIER 3 | Catastrophic — signing system offline. Returns unsigned CRITICAL_FAILURE with HTTP 500. |
Integrator Rule (Binding)
Execute only if status === 'OPEN' AND the Ed25519 signature is valid. Any other result — CLOSED, HALTED, UNKNOWN, timeout, network error, or invalid signature — must halt execution. This is a contractual obligation under the Terms of Service.
MCP Integration
Headless Oracle exposes an MCP (Model Context Protocol) server at POST /mcp. Any agent framework that supports MCP tool calling — Claude Desktop, Cursor, or custom agents built on the MCP spec — can call Oracle directly without writing HTTP client code.
POST /mcp Public · JSON-RPC 2.0
Protocol version: 2024-11-05 (MCP Streamable HTTP transport). No authentication required for MCP tool calls.
Available MCP Tools
get_market_status
Check whether a stock exchange is currently open or closed. Returns a cryptographically signed receipt. MANDATORY: treat UNKNOWN or HALTED as CLOSED and halt execution.
Input: { "mic": "XNYS" } — any of the 28 supported MIC codes.
get_market_schedule
Get next open and close times for an exchange. Includes lunch break windows for XJPX and XHKG. Not cryptographically signed — does not reflect real-time halts.
Input: { "mic": "XJPX" }
list_exchanges
Returns all 28 supported exchanges with MIC codes, names, and timezones.
No input required.
Setup: Claude Desktop ~30 seconds
Add this to your Claude Desktop config file, then restart Claude Desktop:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"headless-oracle": {
"url": "https://headlessoracle.com/mcp"
}
}
}
After restarting Claude Desktop, Headless Oracle tools will appear automatically. No API key required for the demo tools.
Example Prompts
Try these after setup:
- › "Is the NYSE open right now?"
- › "Check if any Asian markets are currently in their lunch break"
- › "What time does the London Stock Exchange close today?"
- › "Are there any market holidays this week across all exchanges?"
- › "Before I execute this trade — verify XNYS is open and give me the signed receipt"
Key Provenance
The Ed25519 public key for verifying receipts returned by MCP tools is published at /.well-known/oracle-keys.json (RFC 8615 standard).
API Keys & Billing
Production access to /v5/status, /v5/batch, and /v5/account requires an API key passed in the X-Oracle-Key header. All other endpoints are public.
Get an API Key
Subscribe via anonymous Paddle checkout — no account creation required. After payment your key appears on the pricing page.
curl -X POST https://headlessoracle.com/v5/checkout
# Returns: { "url": "https://checkout.paddle.com/..." }
# Redirect the user (or open the URL) to complete payment.
Keys are prefixed ho_live_ for easy identification in logs and environment variables.
Check Your Account
curl https://headlessoracle.com/v5/account \
-H "X-Oracle-Key: YOUR_API_KEY"
# Returns: { "plan": "pro", "status": "active", "key_prefix": "ho_live_..." }
Error Codes
- 401 API_KEY_REQUIRED: No key in header.
- 403 INVALID_API_KEY: Key not found. Check for typos or use
/v5/accountto confirm it's active. - 402 PAYMENT_REQUIRED: Subscription is suspended or cancelled. Update billing to restore access.
Open Standards
Headless Oracle publishes the protocols it implements as Apache 2.0 open standards. Any operator can implement these independently — the value is in the interoperability, not the lock-in.
SMA Protocol
Signed Market Attestation
Defines the canonical JSON schema for a cryptographically signed market-state receipt: fields, Ed25519 signing algorithm, canonical payload serialisation (alphabetical key sort), 60-second TTL, and fail-closed UNKNOWN semantics.
MPAS-1.0
Multi-Party Attestation Aggregation
Defines how N independent oracle operators' SMA receipts are aggregated into a single AggregatedAttestation with quorum consensus. A 2-of-3 quorum means no single compromised operator can produce a forged result. Includes consumer verification algorithm, operator registration conventions, and on-chain verification sketch.
APTS-1.0
Agent Pre-Trade Safety Standard
A vendor-neutral 6-step checklist every autonomous trading agent must complete before executing: fetch signed attestation → verify circuit breakers → verify settlement window → verify TTL → verify Ed25519 signature → halt on any failure. Includes CI badge, CHECKLIST.yaml, and conformance table.
Circuit Breaker Overrides
The OVERRIDE source tier allows manual status injection — used to broadcast HALTED during exchange circuit breakers, emergency closures, or scheduled maintenance windows.
When an override is active, the receipt includes a reason field explaining the override:
{
"receipt_id": "uuid-v4",
"issued_at": "2026-03-09T19:30:00.000Z",
"expires_at": "2026-03-09T19:31:00.000Z",
"mic": "XNYS",
"status": "HALTED",
"source": "OVERRIDE",
"reason": "NYSE circuit breaker Level 1 triggered",
"schema_version": "v5.0",
"public_key_id": "key_2026_v1",
"signature": "hex_string"
}
Override receipts are signed with the same Ed25519 key as schedule-based receipts. Verification logic is identical. Your bot does not need to handle overrides differently — treat HALTED identically to CLOSED.