Developers / API documentation
HillSignal API
Read-only access to the public records HillSignal joins: bills, executive orders, contract awards, upcoming purchases, congressional trades, committee seats, PAC money, news and government themes. Every record carries its source link and date. Companies are matched to tickers by exact match only; a miss comes back as a miss, never a guess.
Overview
One API key opens three ways in:
- REST API, for your own code: plain JSON over HTTPS at https://www.hillsignal.com/api/v1. Reference.
- MCP server, for AI assistants such as Claude, ChatGPT and Cursor: https://www.hillsignal.com/api/mcp. Setup.
- Data wire, for feeds and pipelines: every source as one stream of events with provenance. Endpoints.
A machine-readable description of the REST API (OpenAPI 3.1, for Postman, Insomnia or code generators) is at /api/v1/openapi.json.
Quickstart
- Create a free account (or sign in).
- Open Settings > API keys, name a key and press New key.
- Copy the key now. It starts hw_live_ and is shown only once.
- Make your first call:
shell
curl -s "https://www.hillsignal.com/api/v1/signals?event_type=contract_award&limit=3" \
-H "Authorization: Bearer hw_live_your_key_here"You get JSON back: the three newest contract awards, each titled with the recipient, amount and agency, with any matched tickers, a summary and a link to the record on USAspending.gov.
Authentication
Send your key in a header on every request. Either form works:
http
Authorization: Bearer hw_live_your_key_here
X-API-Key: hw_live_your_key_here- All requests must use HTTPS.
- Treat a key like a password. Keep it in an environment variable or a secrets manager, never in a public web page, a mobile app or a code repository.
- Do not put the key in the URL. Links end up in logs and browser history.
- A key that leaks can be turned off in Settings straight away; make a new one and swap it in.
- We keep only a fingerprint (SHA-256 hash) of each key, so we cannot show it to you again. If you lose it, make a new one.
Managing keys
In Settings > API keys you can:
- Make a key. Up to 3 active keys per account; use one per app so you can turn one off without breaking the others.
- See use. Calls today against your daily allowance, calls in the last 30 days, and when each key was last used.
- Turn a key off. It stops working within seconds and cannot be turned back on.
- Rotate. Make the new key, deploy it, then turn the old one off. There is no downtime.
Every key follows your account's plan. Upgrade to Pro and your existing keys get Pro limits within five minutes; no new key needed.
Plans, limits and pricing
API use is included in your HillSignal plan. There is no separate API bill and no charge for overage: when you reach a limit, calls are refused until it resets.
| Plan | Price | Per minute | Per day | Covers |
|---|---|---|---|---|
| No key | Free | 60 | 200 | MCP server only, per network address |
| Free account | Free | 60 | 1,000 | REST, MCP, wire |
| Pro | Included with Pro | 600 | 50,000 | REST, MCP, wire |
| Enterprise | Contact us | 6,000+ | By agreement | REST, MCP, wire, extra sources |
- Limits are per key. The minute limit is a rolling 60 seconds; the daily allowance resets at 00:00 UTC.
- Each HTTP request counts as one call, whatever its size. On the MCP server only tool calls count toward the day; the client's start-up handshake and tool list do not.
- Checking /usage does not use up your daily allowance.
- Need more than Pro gives? Tell us your use and we will set an enterprise limit.
Checking your usage
Every REST and MCP response carries your limits in standard headers:
| X-RateLimit-Limit | Calls allowed per minute |
| X-RateLimit-Remaining | Calls left this minute |
| X-RateLimit-Reset | Unix time the minute window resets |
| X-RateLimit-Daily-Limit | Calls allowed today |
| X-RateLimit-Daily-Remaining | Calls left today |
| Retry-After | On a 429 only: seconds to wait before trying again |
Or ask directly:
shell
curl -s https://www.hillsignal.com/api/v1/usage -H "Authorization: Bearer hw_live_your_key_here"json
{
"key": { "id": "u-1a2b3c4d-9f8e7d6c", "name": "My research notebook" },
"plan": "free",
"limits": { "per_minute": 60, "per_day": 1000 },
"used": { "this_minute": 1, "today": 214 },
"remaining_today": 786,
"day_resets_at": "2026-10-19T00:00:00.000Z",
"docs": "https://www.hillsignal.com/developers/api"
}REST API reference
Base URL https://www.hillsignal.com/api/v1. All endpoints are GET, read-only, and return JSON. GET https://www.hillsignal.com/api/v1 lists them.
GET /api/v1/signals
Bills, contract awards, executive orders and other government events
Each record is one government event with a summary, the sectors and tickers it touches (exact ticker matches only), an impact score and its source link. Newest first.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| event_type | string | bill, contract_award, executive_order, committee_meeting, foreign_agent, … e.g. contract_award |
| ticker | string | One or more tickers, comma-separated. e.g. LMT,RTX |
| sector | string | Sector name (contains, case-insensitive). e.g. Defense |
| min_impact | integer | Minimum impact score (1–10). e.g. 6 |
| since | string | Only events on or after this date (ISO 8601). e.g. 2026-10-01 |
| until | string | Only events before this date (ISO 8601). |
| bill_number | string | Exact bill number. e.g. HR10815 |
| q | string | Words in the title. e.g. shipbuilding |
| include | string | Extra fields, comma-separated: causal_chains, probability, analysis. |
Returns { signals: Signal[], total, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/signals?event_type=contract_award&min_impact=6&limit=5" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/trades
Stock trades reported by members of Congress
Each record is one Periodic Transaction Report filed with the House Clerk or Senate: the member, party, state, chamber, filing date, a suspicion score, and the trades it lists (ticker, buy or sell, amount range, transaction date) in `trades`.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| politician | string | Member name (contains, case-insensitive). e.g. Pelosi |
| party | string | R or D. |
| ticker | string | Only filings with a trade in this ticker. e.g. NVDA |
| chamber | string | house or senate. |
| min_tact | integer | Minimum suspicion score. |
| since | string | Filed on or after this date (ISO 8601). |
| until | string | Filed before this date (ISO 8601). |
| include | string | Extra fields, comma-separated: committees, reasons. |
Returns { trades: Trade[], total, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/trades?ticker=NVDA&limit=10" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/themes
Government themes: where policy, money and markets are moving together
Each theme joins bills, executive orders, purchase notices and contract awards into a dated timeline with a headline, why it matters and what to watch. Tickers come only from contracts that name the company.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Themes to return, 1–30. Default 15. |
| theme_id | string | One theme by id. e.g. critical_minerals |
| channel | string | Only themes with this kind of record: bill, contract, sam_notice, executive_order, patent, … |
| min_events | integer | Only themes with at least this many events in 90 days. |
| since | string | Only themes with evidence newer than this date (ISO 8601). |
Returns { themes: Theme[], count, notes }
curl -s "https://www.hillsignal.com/api/v1/themes?theme_id=critical_minerals" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/news
News items with political relevance
Wire and news headlines linked to tickers, with source, sentiment and publication time.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| ticker | string | Ticker. |
| source | string | Source name. |
| sentiment | string | positive, negative or neutral. |
| since | string | Published on or after (ISO 8601). |
| until | string | Published before (ISO 8601). |
| q | string | Words in the headline. |
Returns { news: NewsItem[], total, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/news?ticker=LMT&limit=10" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/committees
Congressional committee assignments and their jurisdiction
Which members sit on which committees, and the market sectors each committee oversees.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| committee | string | Committee name (contains). e.g. Armed Services |
| politician | string | Member name (contains). |
| bioguide_id | string | Exact Bioguide id. |
| chamber | string | house or senate. |
| include | string | gics to add sector jurisdiction. |
Returns { assignments: Assignment[], total, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/committees?committee=Armed%20Services" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/pac
PAC money and the trades of the members it funds
Correlations between a member's PAC funding by sector and their trading in that sector.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| politician | string | Member name (contains). |
| party | string | R or D. |
| min_score | number | Minimum correlation score. |
| high_confidence | boolean | true for high-confidence rows only. |
| significant_only | boolean | true for statistically significant rows only. |
Returns { correlations: Correlation[], total, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/pac?significant_only=true" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/watches
Spending chains being tracked toward an award
Programs where purchase notices and budget steps point to a coming contract, with a conviction score and status.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Rows per page. Default 50, maximum 200. e.g. 50 |
| offset | integer | Rows to skip, for paging. Default 0. e.g. 0 |
| min_conviction | integer | Minimum conviction, 0–100. |
| sector | string | Sector. |
| status | string | watching, building, probable, high or confirmed. |
| include | string | Extra fields: confirmations, playbooks. |
Returns { watches: Watch[], total, … }
curl -s "https://www.hillsignal.com/api/v1/watches?min_conviction=70" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/patterns
Detected trading patterns
Spouse trades, several members trading the same stock, committee conflicts, trades before legislation, and clusters.
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Default 100, maximum 500. |
| offset | integer | Rows to skip. |
| type | string | spouse_trade, multi_member, committee_conflict, pre_legislative or cluster. |
Returns { patterns, catalysts, total_patterns, total_catalysts, limit, offset }
curl -s "https://www.hillsignal.com/api/v1/patterns?type=pre_legislative" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/sectors
Momentum score for each market sector
How much government activity is building in each of the 14 sectors.
| Parameter | Type | Description |
|---|---|---|
| min_score | number | Minimum score. |
| sort | string | score (default) or name. |
Returns { sectors: Sector[], count }
curl -s "https://www.hillsignal.com/api/v1/sectors" -H "Authorization: Bearer hw_live_your_key_here"GET /api/v1/usage
Your key: plan, limits and use so far
Calls used this minute and today, what is left today, and when the day resets. Checking does not use up your daily allowance.
Returns { key, plan, limits, used, remaining_today, day_resets_at, plans, docs }
curl -s "https://www.hillsignal.com/api/v1/usage" -H "Authorization: Bearer hw_live_your_key_here"Requests and responses
- Paging. List endpoints take limit (default 50, maximum 200) and offset, and return total. Keep asking with a larger offset until offset reaches total.
- Dates. Send and receive ISO 8601 (2026-10-18 or 2026-10-18T09:20:00Z). Times are UTC.
- Order. Newest first unless an endpoint says otherwise.
- Empty results are a normal 200 with an empty list, not an error.
- Browsers. CORS is open (Access-Control-Allow-Origin: *), but do not put your key in a public page; call from your own server.
- Freshness. Sources update every 30 minutes to 24 hours, depending on the source. /api/wire/health shows how fresh each one is.
Code examples
Each example reads the key from an environment variable named HILLSIGNAL_API_KEY.
Python
python
import os, time, requests
API = "https://www.hillsignal.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['HILLSIGNAL_API_KEY']}"}
def get(path, **params):
"""GET a v1 endpoint, waiting and retrying once if rate limited."""
for attempt in range(2):
r = requests.get(f"{API}{path}", headers=HEADERS, params=params, timeout=30)
if r.status_code == 429 and attempt == 0:
time.sleep(int(r.headers.get("Retry-After", "60")))
continue
r.raise_for_status()
return r.json()
# Every contract award with impact 6+ since October 1, page by page
offset, rows = 0, []
while True:
page = get("/signals", event_type="contract_award", min_impact=6,
since="2026-10-01", limit=200, offset=offset)
rows += page["signals"]
offset += page["limit"]
if offset >= page["total"]:
break
print(len(rows), "awards")JavaScript (Node.js or browser)
javascript
// Node 18+ or any modern browser (keep the key on a server, not in public pages)
const API = "https://www.hillsignal.com/api/v1";
const KEY = process.env.HILLSIGNAL_API_KEY;
async function get(path, params = {}) {
const url = new URL(API + path);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));
const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") ?? 60);
throw new Error(`Rate limited, retry in ${wait}s`);
}
if (!res.ok) throw new Error((await res.json()).error);
console.log("Calls left today:", res.headers.get("X-RateLimit-Daily-Remaining"));
return res.json();
}
// Each record is one filing (Periodic Transaction Report) with its trades inside
const { trades } = await get("/trades", { ticker: "NVDA", limit: 10 });
console.table(trades.map(f => ({ member: f.politician_name, chamber: f.chamber, filed: f.filing_date, trades: f.trade_count })));Go
go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://www.hillsignal.com/api/v1/themes?limit=5", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("HILLSIGNAL_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
fmt.Println("error:", res.Status, "retry after:", res.Header.Get("Retry-After"))
return
}
var body struct {
Themes []map[string]any `json:"themes"`
}
json.NewDecoder(res.Body).Decode(&body)
for _, t := range body.Themes {
fmt.Println(t["label"], "-", t["headline"])
}
}R
r
library(httr2)
resp <- request("https://www.hillsignal.com/api/v1/signals") |>
req_headers(Authorization = paste("Bearer", Sys.getenv("HILLSIGNAL_API_KEY"))) |>
req_url_query(event_type = "bill", limit = 50) |>
req_retry(max_tries = 2, is_transient = \(r) resp_status(r) == 429) |>
req_perform()
bills <- resp_body_json(resp)$signals
length(bills)Spreadsheets and no-code tools
Tools that can call a web address with a header (Zapier, Make, n8n, Power Query, Google Apps Script) can use the REST API directly: set the URL to an endpoint above and add the header Authorization: Bearer your key.
MCP server (AI assistants)
The MCP server lets an AI assistant look things up in HillSignal while it answers you. It works without a key at the no-key limits; add your key for your plan's limits. Setup for Claude, ChatGPT, Cursor and others, and the full list of tools, is on the Developers page.
With a key: Claude Desktop
json
{
"mcpServers": {
"hillsignal": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://www.hillsignal.com/api/mcp",
"--header", "Authorization: Bearer hw_live_your_key_here"]
}
}
}With a key: any client or plain HTTP
Send the same Authorization header. The server speaks JSON-RPC 2.0 over POST (Streamable HTTP):
shell
curl -s https://www.hillsignal.com/api/mcp \
-H "Authorization: Bearer hw_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_awards","arguments":{"agency":"Navy","min_amount":100000000,"limit":5}}}'Data wire
The wire is every source as one stream of events, each stamped with where it came from, when we captured it and a content hash. Use it to keep your own copy current.
| GET /api/wire | Catalog of sources and the event format. No key needed. |
| GET /api/wire/firehose | Events, newest first or oldest first. limit (1–200), order (desc or asc), after / before (cursor), sources, tickers, sectors, categories. Returns { events, next_cursor, has_more }. |
| GET /api/wire/stream | Server-Sent Events push. Same filters plus interval (2–30 seconds). Reconnect with Last-Event-ID and nothing is missed. |
| GET /api/wire/entity/resolve | name (and optional parent) to a ticker, by exact match only. A miss returns ticker: null with the reason. |
| GET /api/wire/health | Freshness and capture delay per source, and how many records of each source are matched to a ticker. |
shell
# newest 50 events across every source
curl -s "https://www.hillsignal.com/api/wire/firehose?limit=50" -H "Authorization: Bearer hw_live_your_key_here"
# follow new events as they arrive (Server-Sent Events)
curl -N "https://www.hillsignal.com/api/wire/stream?sources=signals,insider_trades" -H "Authorization: Bearer hw_live_your_key_here"
# resolve a company name to a ticker, exact match only
curl -s "https://www.hillsignal.com/api/wire/entity/resolve?name=Lockheed%20Martin%20Corporation" -H "Authorization: Bearer hw_live_your_key_here"Errors
Errors come back as JSON with a plain-language error, a stable code to branch on, and a link to this section.
| Status | code | Meaning and what to do |
|---|---|---|
| 400 | — | A parameter is not valid. The message says which. |
| 401 | missing_key | No key was sent. Add the Authorization header. |
| 401 | invalid_key | The key is not known or has been turned off. Check it was copied whole, or make a new one. |
| 404 | — | No such endpoint, or the one record asked for does not exist. |
| 429 | rate_limited | Too many calls this minute. Wait Retry-After seconds (at most 60). |
| 429 | daily_quota_exceeded | Today's allowance is used up. It resets at 00:00 UTC (resets_at); upgrade for more. |
| 500 | — | Something failed on our side. Retry after a short wait; if it keeps happening, tell us. |
http
HTTP/1.1 429 Too Many Requests
Retry-After: 52714
X-RateLimit-Daily-Limit: 1000
X-RateLimit-Daily-Remaining: 0
{
"error": "Daily allowance used up",
"code": "daily_quota_exceeded",
"limit_per_day": 1000,
"used": 1000,
"resets_at": "2026-10-19T00:00:00.000Z",
"retry_after_seconds": 52714,
"upgrade": "Pro raises this to 600 a minute and 50,000 a day: https://www.hillsignal.com/checkout",
"docs": "https://www.hillsignal.com/developers/api#errors"
}Troubleshooting
401 even though I send a key. Check the header is exactly Authorization: Bearer hw_live_… with one space after Bearer, no quotes and no line break. Make sure the key is still on in Settings, and that you copied all of it (it is 48 characters).
429 rate_limited. You sent more than your per-minute limit. Space calls out, or wait the number of seconds in Retry-After. Running several scripts on one key shares the limit; give each its own key if they run at once.
429 daily_quota_exceeded. The day's allowance is spent. Use since to fetch only what is new instead of re-reading everything, page with limit=200, and cache results. Or upgrade.
An empty list. The filters matched nothing. Ticker and bill filters are exact; widen the dates or drop a filter to check.
A company has no ticker. We attach a ticker only when the name matches a listed company exactly. Private companies, and names we cannot match for certain, have none. That is deliberate.
Works with curl, fails in the browser. Most likely the key is missing from the browser request, or a browser extension is blocking it. Call the API from your own server instead.
Data looks out of date. Check /api/wire/health; it shows when each source last updated. Some sources update once a day.
My AI assistant cannot connect. See the client steps on the Developers page. The server URL is https://www.hillsignal.com/api/mcp and it needs no key to start.
Still stuck? Email support@hillsignal.com with the time of the call, the endpoint and the response. Never send your key.
Good practice
- Fetch only what changed: keep the newest date you have and pass it as since.
- Cache responses for at least a few minutes; most sources change no faster than every 30 minutes.
- On a 429, wait the Retry-After time, then try once more. Do not retry in a tight loop.
- Read X-RateLimit-Daily-Remaining to slow down before you hit the limit.
- Use one key per app or environment, and keep test and live keys apart.
Data, terms and support
- The underlying records are public government data; each record links to its official source. HillSignal's matching, scores, summaries and themes are HillSignal's work.
- If you publish what you find, credit “Data: HillSignal” and keep the source links.
- Nothing in the API is financial advice or a recommendation to buy or sell a security.
- Use is subject to the Terms of Service and Privacy Policy. We log each call (key, endpoint, time) to count usage; we do not keep what you send beyond that.
- Support: support@hillsignal.com.
Changelog
- 2026-10-18. Personal API keys work on the REST API, the MCP server and the wire. Daily allowances by plan, rate-limit headers, error codes, /usage and the OpenAPI spec added.
The API is versioned in the path (/api/v1). We add fields and endpoints without notice; we do not remove or rename them within v1. Any breaking change comes as a new version, announced here first.