The trades data model
Every field on a Trade, the STOCK Act amount ranges, conflict-signal semantics, and the nullable-ticker caveat.
Every trade in the API went through the same pipeline: scraped from an
official House or Senate disclosure, extracted, human-reviewed, and only
then published. The Trade object is identical everywhere it appears — the
feed, politician profiles, ticker pages, and webhook payloads.
Trade fields
| Field | Type | Meaning |
|---|---|---|
id | string | Stable trade id. |
politician | object | The disclosing member: slug, name, chamber (house | senate), party, state (the last two nullable). |
ticker | string | null | Exchange symbol, uppercase. Nullable — see below. |
assetName | string | The asset as written in the filing. |
assetType | string | null | Asset class as disclosed (e.g. stock, bond, fund), when stated. |
side | string | buy, sell_full, sell_partial, or exchange. |
amountBucket | string | The statutory range key (e.g. 15k_50k) — see the table below. |
amountRange | object | The bucket resolved to { min, max, label }; max is null when unbounded. |
tradeDate | string | null | The transaction date (yyyy-mm-dd), when the filing states it. |
disclosedAt | string | ISO 8601 instant the trade was disclosed. Feed ordering key. |
filingUrl | string | null | The official source document URL. |
ownerType | string | null | Who holds the asset: self, spouse, joint, or child; null when not stated. |
conflictSignal | boolean | Whether the trade overlaps an industry the member oversees — see below. |
conflictReason | string | null | Why the signal fired; null when it did not. |
Amounts are ranges, not numbers
Members of Congress never disclose exact amounts — the STOCK Act
disclosure forms use checkbox ranges. amountBucket is the range key and
amountRange spells out the statutory bounds:
amountBucket | Range |
|---|---|
1k_15k | $1,001 – $15,000 |
15k_50k | $15,001 – $50,000 |
50k_100k | $50,001 – $100,000 |
100k_250k | $100,001 – $250,000 |
250k_500k | $250,001 – $500,000 |
500k_1m | $500,001 – $1,000,000 |
1m_5m | $1,000,001 – $5,000,000 |
5m_25m | $5,000,001 – $25,000,000 |
25m_50m | $25,000,001 – $50,000,000 |
over_50m | Over $50,000,000 (max: null) |
over_1m_spouse | Over $1,000,000, spouse/dependent holdings (max: null) |
Two consequences for consumers:
- Never treat
amountRange.min(or the midpoint) as "the amount" in analytics without labeling it as a bound — a1m_5mtrade could be $1,000,001 or $4,999,999. amountRange.maxisnullfor the two open-ended buckets; handle that before doing arithmetic.
The over_1m_spouse bucket exists because the House form has a distinct
"over $1,000,000" checkbox for spouse/dependent-held assets that doesn't
state an upper bound.
Conflict signals
conflictSignal is the platform's editorial flag: true when the traded
asset's industry overlaps a committee the member sits on (e.g. a Senate
Armed Services member trading a defense contractor). conflictReason
explains the overlap in one sentence.
How it's computed, so you know what you're consuming:
- Committee assignments and memberships come from public congressional datasets; tickers map to sectors/industries with curated overrides.
- A daily sweep recomputes the verdict for every trade — signals can appear or be corrected after first publication as committee data updates, so treat the pair as mutable metadata, not immutable history.
- It is an editorial signal of potential conflict of interest, not an allegation of illegality — STOCK Act trading is legal when disclosed. Present it accordingly.
The nullable-ticker caveat
ticker is null on a meaningful share of real trades: municipal bonds,
private funds, crypto, real-estate partnerships, and assets whose symbol
can't be resolved from the filing text. Two practical rules:
- Code that groups or joins by ticker must handle
null(useassetNameas the display fallback). - The feed's
ticker=filter only matches trades whose symbol resolved — it will never return thenull-ticker rows.
tradeDate and filingUrl are nullable for the same underlying reason: the
API reports exactly what the filing supports, never a guess.
CSV export
The trades feed can answer CSV for offline analysis (plans with CSV export;
otherwise 403 csv_export_not_available):
curl -H "Authorization: Bearer wch_YOUR_KEY" \
"https://www.wolfofcapitolhill.com/api/v1/trades?format=csv&limit=100" > trades.csvColumns, in order: id, politician_slug, politician_name, chamber,
party, state, ticker, asset_name, asset_type, side,
amount_bucket, amount_min, amount_max, trade_date, disclosed_at,
filing_url, owner_type.
Null fields are empty strings, values are RFC-4180 quoted, and pagination
works exactly as in JSON (limit + cursor still apply; the response is one
page, not the whole feed). Note the CSV omits conflictSignal /
conflictReason — use the JSON format when you need them.