Developer documentation
The API base is https://api.trueodd.xyz. Interactive schema: https://api.trueodd.xyz/docs · OpenAPI JSON.
Discovery
Fetch GET https://api.trueodd.xyz/v1/service-metadata for the service catalog, identifiers, endpoints,
and payment configuration. GET /v1/supported-markets lists venues, families, and series.
| Service | Method & path | Billing |
|---|---|---|
rwoo.check_market | POST /v1/check-market | Paid |
rwoo.cross_venue_edge | POST /v1/cross-venue-edge | Paid |
rwoo.get_calibration | GET /v1/calibration | Free |
Payments — OKX Agent Payments (x402)
Paid endpoints use the HTTP 402 flow. An unpaid request receives a 402 whose body is the x402
challenge (x402Version + accepts[]) with WWW-Authenticate: Payment. Re-send the
identical request body with an X-PAYMENT header; on success you receive 200, a
PAYMENT-RESPONSE header, and a receipt whose payment_reference links the settlement.
- The payment is bound to service, request hash, amount, recipient, asset, network, and expiry.
- A used nonce is rejected (
PAYMENT_REPLAYED); anIdempotency-Keyretry never double-charges. - No buyer private key is ever accepted, and no signing flow is invented — your agent wallet produces the payload.
Prices, asset, network, and recipient are set by the operator and appear in
/v1/service-metadata. Until configured, paid endpoints run in free mode or fail closed.
Call it
curl -sS -X POST https://api.trueodd.xyz/v1/check-market \
-H 'Content-Type: application/json' \
-d '{"market": {"venue": "kalshi", "market_id": "KXHIGHNY-26JUL12-B85"}}'
# A paid deployment answers 402 with an x402 challenge first;
# re-send the identical body plus an X-PAYMENT header to receive 200.
import httpx
BASE = "https://api.trueodd.xyz"
req = {"market": {"venue": "kalshi", "market_id": "KXHIGHNY-26JUL12-B85"}}
r = httpx.post(f"{BASE}/v1/check-market", json=req, timeout=30)
if r.status_code == 402:
challenge = r.json() # x402: pay with your agent wallet,
# then re-POST the same body with the X-PAYMENT header.
print(r.json())
const BASE = "https://api.trueodd.xyz";
const req = { market: { venue: "kalshi", market_id: "KXHIGHNY-26JUL12-B85" } };
const r = await fetch(`${BASE}/v1/check-market`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
});
// On 402, read the x402 challenge and retry with an X-PAYMENT header.
console.log(await r.json());
{"market": {"venue": "kalshi", "market_id": "KXHIGHNY-26JUL12-B85"}}
Refusals vs errors
A market the oracle cannot safely price is a refusal: HTTP 200,
status: "refused", a stable reason_code, an explanation, the missing capability, and a
receipt. Malformed requests, unknown services, and payment failures are transport errors (4xx)
with a stable JSON shape: { "error": { "code", "message" }, "request_id" }.
| Code | HTTP | Meaning |
|---|---|---|
INVALID_REQUEST | 400 | Body failed schema validation or exceeded limits. |
MARKET_NOT_FOUND | 404 | The venue has no such market id. |
UNSUPPORTED_VENUE | 400 | Venue is not one of kalshi, polymarket, limitless. |
UNSUPPORTED_MARKET | 422 | No wired engine covers this market shape (refusal). |
ENTITY_UNBOUND | 422 | The entity/location/strike could not be bound (refusal). |
YES_SIDE_UNBOUND | 422 | Which side YES prices could not be determined (refusal). |
SOURCE_UNAVAILABLE | 503 | A required upstream source could not be reached. |
SOURCE_STALE | 422 | Source data is older than the freshness limit (refusal). |
SOURCE_CONFLICT | 422 | Sources disagree beyond tolerance (refusal). |
MODEL_MISSING | 422 | The engine declined to emit a probability (refusal). |
FEE_UNKNOWN | 422 | The venue fee term is not quantified (refusal). |
RATE_LIMITED | 429 | Too many requests, or an upstream rate limit. |
PAYMENT_REQUIRED | 402 | A paid endpoint was called without payment (x402 challenge). |
PAYMENT_INVALID | 402 | The presented payment failed verification. |
PAYMENT_REPLAYED | 402 | The payment nonce was already used. |
UPSTREAM_TIMEOUT | 504 | An upstream source timed out. |
INTERNAL_ERROR | 500 | An unexpected error (no stack trace is exposed). |
Semantics you must not confuse
Idempotency
Send an Idempotency-Key header to make a retry return the same result and receipt without re-charging.
Rate limits & timeouts
Requests are bounded and time-limited; a slow upstream returns UPSTREAM_TIMEOUT rather than hanging.
Confidence is not calibration
Model agreement (confidence) is the spread of the ensemble now. Calibration is the measured, precommitted historical hit rate. A high confidence with zero resolved events is not evidence — the API always reports the independent sample count.
Missing data stays missing
An unknown entity, unbound strike, or unverified fee never becomes a silent zero — it becomes a refusal.
Versions & changelog
Every forecast carries its model_version. Current engine versions:
| Family | Model version |
|---|---|
| weather.temperature | weather-ensemble-v2 |
| weather.precipitation | weather-hurdle-v2 |
| economics.core_cpi | core-cpi-official-ensemble-v2 |
| economics.headline_cpi | headline-cpi-official-ensemble-v2 |
| economics.gdp | gdp-official-ensemble-v2 |
| economics.labor | labor-official-history-v1 |
| economics.fed_rates | fed-hold-only-v1 |
| economics.recession | spf-recession-v1 |
| sports.world_cup | world-cup-live-bracket-elo-v2 |
| sports.tennis | tennis-uts-elo-v1 |
| sports.nba | nba-point-differential-v1 |
| sports.mlb | mlb-season-elo-v1 |
| sports.club_soccer | clubelo-match-v1 |
- v1.0.0 — Initial ASP surface: three services, receipts, calibration evidence, and the OKX Agent Payments (x402) 402 flow. Funded execution remains disabled.