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.

01

API keys

Keys are minted only by an operator holding the provisioning secret. The plaintext key is shown once; the server stores a hash.

Checking session…
02

Quickstart: pull the wire

The consumer contract is a single loop. Persist the cursor. Pass it back. Nothing is missed, nothing is double-processed.

01

Authenticate

Send the key as X-API-Key (or Authorization: Bearer). Confirm with GET /v1/whoami.

02

Poll with a cursor

GET /v1/events?since=<cursor>. Start at 0. Each page returns a new cursor and hasMore.

03

Persist and repeat

Save the cursor after processing a page. Loop while hasMore, then sleep and poll again. 10–30s is plenty.

curl — first and subsequent polls
# 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"
Python — Mycel monitor source
# 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)
TypeScript — Mycel monitor source
// 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
}
03

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

base: /v1
Consumer API endpoints
MethodPathDescription
GET/v1/whoamiEcho the key's id, owner and scopes. Use as a health check for credentials.
GET/v1/eventsCursor-paginated event log. Filters: since, type, category, country, limit (≤500).
GET/v1/events/{eventId}Fetch a single event by id.
GET/v1/tariffsCurrent tariff matrix. Filters: imposing, target.
GET/v1/chokepointsCurrent 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.

04

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.
TARIFF_RATE_CHANGED payload
{
  "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
}
05

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.

Mint a consumer key
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"]}'
Publish a tariff change
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.