For machines
Developer Portal
TariffWire exposes one authenticated feed. Mint a key, poll /v1/events with a cursor, and forward what you get to your own system. No webhooks to secure, no delivery to retry: your consumer owns its position in the log.
API keys
Keys are minted only by an operator holding the provisioning secret. The plaintext key is shown once; the server stores a hash.
Quickstart: pull the wire
The consumer contract is a single loop. Persist the cursor. Pass it back. Nothing is missed, nothing is double-processed.
Authenticate
Send the key as X-API-Key (or Authorization: Bearer). Confirm with GET /v1/whoami.
Poll with a cursor
GET /v1/events?since=<cursor>. Start at 0. Each page returns a new cursor and hasMore.
Persist and repeat
Save the cursor after processing a page. Loop while hasMore, then sleep and poll again. 10–30s is plenty.
# First poll — from the beginning of the log
curl -s "$TARIFFWIRE_URL/v1/events?since=0&limit=100" \
-H "X-API-Key: $TARIFFWIRE_API_KEY"
# Response
{
"events": [
{
"seq": 13,
"eventId": "evt_8e14617ea7094c298a13eddf792738a1",
"eventType": "TARIFF_RATE_CHANGED",
"category": "tariff",
"severity": "high",
"source": "tariffwire",
"timestamp": "2026-09-03T14:02:11Z",
"headline": "US raises Section 301 duty on Chinese EV batteries to 40%",
"payload": {
"imposingCountry": "US", "targetCountry": "CN", "sector": "HS-8507",
"previousRatePercent": 25.0, "newRatePercent": 40.0, "delta": 15.0,
"unit": "percent", "effectiveDate": "2026-10-02", "legalBasis": "Section 301"
}
}
],
"cursor": 13,
"hasMore": false
}
# Next poll — only what is new since seq 13
curl -s "$TARIFFWIRE_URL/v1/events?since=13&limit=100" \
-H "X-API-Key: $TARIFFWIRE_API_KEY"# mycel-monitor/sources/tariffwire.py
import os, time, httpx
BASE = os.environ["TARIFFWIRE_URL"] # e.g. https://tariffwire.example.com
KEY = os.environ["TARIFFWIRE_API_KEY"] # tw_live_...
client = httpx.Client(base_url=BASE, headers={"X-API-Key": KEY}, timeout=10)
def poll(cursor: int, on_event) -> int:
"""Fetch everything after `cursor`, hand each event to on_event, return the new cursor."""
while True:
r = client.get("/v1/events", params={"since": cursor, "limit": 200})
r.raise_for_status()
page = r.json()
for ev in page["events"]:
on_event(ev) # -> forward to Mycel main backend
cursor = page["cursor"]
if not page["hasMore"]:
return cursor
def handle(ev: dict) -> None:
if ev["eventType"] == "TARIFF_RATE_CHANGED":
p = ev["payload"]
print(f'{p["imposingCountry"]}->{p["targetCountry"]} {p["sector"]}: '
f'{p["previousRatePercent"]}% -> {p["newRatePercent"]}% ({p["delta"]:+}pp)')
elif ev["eventType"] == "CHOKEPOINT_STATUS_CHANGED":
print(f'{ev["payload"]["name"]} is now {ev["payload"]["status"]}')
cursor = load_cursor_from_db() # persist this between runs
while True:
cursor = poll(cursor, handle)
save_cursor_to_db(cursor)
time.sleep(15)// mycel-monitor/src/sources/tariffwire.ts
const BASE = process.env.TARIFFWIRE_URL!;
const KEY = process.env.TARIFFWIRE_API_KEY!;
type Page = { events: WireEvent[]; cursor: number; hasMore: boolean };
export async function pollTariffWire(cursor: number, onEvent: (e: WireEvent) => Promise<void>) {
do {
const res = await fetch(`${BASE}/v1/events?since=${cursor}&limit=200`, {
headers: { "X-API-Key": KEY },
});
if (!res.ok) throw new Error(`tariffwire ${res.status}`);
const page: Page = await res.json();
for (const ev of page.events) await onEvent(ev);
cursor = page.cursor;
if (!page.hasMore) break;
} while (true);
return cursor; // persist and pass back as ?since= next time
}Consumer API reference
All endpoints return JSON. Errors are { detail: { code, message } } with the appropriate HTTP status. Rate limits are not enforced in this deployment.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/whoami | Echo the key's id, owner and scopes. Use as a health check for credentials. |
| GET | /v1/events | Cursor-paginated event log. Filters: since, type, category, country, limit (≤500). |
| GET | /v1/events/{eventId} | Fetch a single event by id. |
| GET | /v1/tariffs | Current tariff matrix. Filters: imposing, target. |
| GET | /v1/chokepoints | Current status of tracked maritime chokepoints. |
Authentication
Header X-API-Key: tw_live_…
Missing or invalid key → 401. Revoked key → 401. Valid key without the needed scope → 403.
Ordering guarantees
Events are append-only and strictly ordered by seq. A cursor is simply the last seq you have seen.
Filters (type, category, country) do not change ordering; the cursor still advances over skipped events.
Severity
critical · high · elevated · low
Tariff severity is derived from the absolute delta: ≥25pp critical, ≥10pp high, ≥3pp elevated, else low.
Event types
Every event shares the envelope (seq, eventId, eventType, category, severity, source, timestamp, headline). The payload shape depends on eventType.
Types
- TARIFF_RATE_CHANGEDtariffA duty rate between two countries changed for a sector. Payload carries previous, new and delta.
- CHOKEPOINT_STATUS_CHANGEDchokepointA strait, canal or sea lane changed status (open → watch → degraded → restricted → closed).
- PORT_DISRUPTIONportTerminal congestion, strike or closure at a major port.
- SANCTION_IMPOSEDsanctionExport control or entity listing affecting a country or company.
- COMMODITY_PRICE_SHOCKcommodityAbnormal move in an input or freight index.
{
"imposingCountry": "US", // ISO-3166 alpha-2 (EU used for the bloc)
"imposingCountryName": "United States",
"targetCountry": "CN",
"targetCountryName": "China",
"sector": "HS-8507", // "ALL" | sector slug | HS chapter
"previousRatePercent": 25.0, // null when no prior rate was known
"newRatePercent": 40.0,
"delta": 15.0, // newRatePercent - previousRatePercent
"unit": "percent",
"effectiveDate": "2026-10-02", // ISO date or null
"legalBasis": "Section 301", // free text or null
"notes": null
}Admin API
Operator endpoints under /admin require X-Admin-Secret. This is the single hardcoded secret configured on the backend; without it nothing can be minted or published.
curl -s -X POST "$TARIFFWIRE_URL/admin/keys" \
-H "X-Admin-Secret: $ADMIN_PROVISIONING_SECRET" \
-H "Content-Type: application/json" \
-d '{"name":"Mycel monitor — prod","owner":"mycel-monitor","scopes":["events:read","tariffs:read"]}'curl -s -X POST "$TARIFFWIRE_URL/admin/tariffs" \
-H "X-Admin-Secret: $ADMIN_PROVISIONING_SECRET" \
-H "Content-Type: application/json" \
-d '{
"imposingCountry": "US",
"targetCountry": "CN",
"sector": "HS-8507",
"newRatePercent": 40,
"previousRatePercent": 25,
"effectiveDate": "2026-10-02",
"legalBasis": "Section 301"
}'Prefer a UI? The Operator Console wraps the same endpoints. Interactive OpenAPI docs are served by the backend at /docs.