Getting started
From zero to your first congressional-trades request in a few minutes — with or without an API key.
1. Try it without a key
The API works anonymously at evaluation limits (10 requests/minute,
1,000/month, JSON only), so your first request needs nothing but curl:
curl "https://www.wolfofcapitolhill.com/api/v1/trades?limit=5"{
"data": [
{
"id": "trd_01j8...",
"politician": {
"slug": "jane-doe",
"name": "Jane Doe",
"chamber": "senate",
"party": "D",
"state": "CA"
},
"ticker": "NVDA",
"assetName": "NVIDIA Corporation",
"assetType": "stock",
"side": "buy",
"amountBucket": "15k_50k",
"amountRange": { "min": 15001, "max": 50000, "label": "$15,001 – $50,000" },
"tradeDate": "2026-06-24",
"disclosedAt": "2026-06-30T14:05:00.000Z",
"filingUrl": "https://efdsearch.senate.gov/search/view/ptr/...",
"ownerType": "self",
"conflictSignal": true,
"conflictReason": "Serves on the Senate Commerce Committee (technology oversight); traded a semiconductor issuer."
}
],
"nextCursor": "MjAyNi0wNi0zMFQxNDowNTowMC4wMDBafHRyZF8wMWo4"
}2. Create an account and mint a key
API keys are included in the Pack and Den plans (see Authentication & tiers for the exact limits).
- Sign up at wolfofcapitolhill.com/account.
- Subscribe to Pack or Den under Account → Billing.
- Mint a key under Account → API keys.
Keys look like wch_ followed by 43 URL-safe characters. The full secret is
shown exactly once at creation — only its hash is stored, so it cannot be
retrieved again. Store it in a secret manager and rotate by minting a new key
and revoking the old one.
3. Make an authenticated request
Send the key as a bearer token (or in an X-Api-Key header — see
Authentication):
curl -H "Authorization: Bearer wch_YOUR_KEY" \
"https://www.wolfofcapitolhill.com/api/v1/trades?ticker=NVDA&limit=25"The same request from JavaScript:
const BASE_URL = 'https://www.wolfofcapitolhill.com';
const response = await fetch(`${BASE_URL}/api/v1/trades?ticker=NVDA&limit=25`, {
headers: { Authorization: `Bearer ${process.env.WOCH_API_KEY}` },
});
if (!response.ok) {
// Errors are always { error, hint? } with the right status code.
const { error, hint } = await response.json();
throw new Error(`WoCH API ${response.status}: ${error}${hint ? ` — ${hint}` : ''}`);
}
const { data, nextCursor } = await response.json();
console.log(`${data.length} trades; remaining this minute:`,
response.headers.get('X-RateLimit-Remaining'));4. Walk the feed
Every list response includes nextCursor. Pass it back as cursor to get the
next page; null means you have reached the end:
async function* allTrades(params = {}) {
let cursor;
do {
const query = new URLSearchParams({ ...params, limit: '100' });
if (cursor) query.set('cursor', cursor);
const response = await fetch(`${BASE_URL}/api/v1/trades?${query}`, {
headers: { Authorization: `Bearer ${process.env.WOCH_API_KEY}` },
});
if (!response.ok) throw new Error(`WoCH API ${response.status}`);
const page = await response.json();
yield* page.data;
cursor = page.nextCursor;
} while (cursor !== null);
}
for await (const trade of allTrades({ chamber: 'senate' })) {
if (trade.conflictSignal) console.log(trade.politician.name, trade.ticker, trade.conflictReason);
}For incremental syncs, filter with since (an ISO 8601 instant with offset,
e.g. 2026-07-01T00:00:00Z) instead of re-walking history — or skip polling
entirely and let webhooks push new trades to you.
Where to go next
- Rate limits & pagination — budgets,
429handling, cursor rules. - The trades data model — what every field means (and the amount-range gotcha).
- Webhooks — signed pushes the moment trades publish.