Resolver-shape lookup for ~23,000 active federal contracting officers. Name or email in, contact details out. JSON, no auth dance.
Most federal contact data tools sell you a directory: filter by agency, by NAICS, by location, scroll through results. That shape works for human researchers exploring the buyer landscape, but it's the wrong shape for code that already knows who it is looking for. A BD platform that just saw a name in a news article does not need to browse, it needs to resolve: name in, contact out.
This API is built for that shape. There is no listing endpoint, no pagination, no filter-only browse. You always start with something you already know (a name or an email), and the API returns the contact info to act on.
The tradeoff is intentional: 23,000 contact records is a small dataset. If we exposed an unlimited browse-and-paginate endpoint at $39/mo, a single customer could mirror the whole dataset in a few hundred calls and walk away. Capping at 5 results per call, requiring a name or email seed, and keeping the discovery layer (browse, awards history, colleague graph, AI dossiers) in the web app preserves the differentiated value while still serving the legitimate "I need to act on a contact" workflow that integrators actually have.
GET https://govconapi.com/api/v1/contacts/lookup
Authorization: Bearer YOUR_API_KEY
Two query modes. One of name or email is required.
| Parameter | Required? | Notes |
|---|---|---|
name | One of name/email | Substring match, case-insensitive. Min 2 characters. Returns up to 5 results. |
agency | Optional | Substring match against agency or department. e.g. Air Force, DLA, Veterans. |
state | Optional | 2-letter state or territory code. e.g. CA, DC, VA. |
email | One of name/email | Exact match for reverse lookup. Returns one record (email is unique key in the data). |
{
"data": [
{
"name": "Joseph Mendoza",
"current_agency": "ENVIRONMENTAL PROTECTION AGENCY",
"current_department": "ENVIRONMENTAL PROTECTION AGENCY",
"current_location_city": "San Francisco",
"current_location_state": "CA",
"email": "[email protected]",
"phones": ["(415) 972-3675"],
"last_seen": "2026-05-09",
"slug": "3066732d09cc"
}
],
"filters_applied": {"name": "Joseph", "agency": null, "state": null},
"result_count": 1,
"note": "Resolver lookup. ..."
}
Field coverage on the underlying dataset: name, email, slug, last_seen are all 100%. current_agency, current_department are 99.8%. current_location_city and current_location_state are 97%+. phones is 61% (some COs have multiple, some have none). role is deliberately not exposed because the underlying data is populated for only ~7% of contacts.
Successful responses include Cache-Control: public, max-age=3600. Data refreshes nightly so an hour of client-side caching is safe and saves your per-key quota when re-querying the same contact in tight loops.
You read sam.gov daily. A notice catches your eye. The point of contact is "Joseph Nemedy, DEPT OF THE AIR FORCE." You want to email him a teaming question. The slowest path is logging in to sam.gov and copy-pasting. The fast path is one HTTP call:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/contacts/lookup?name=Joseph%20Nemedy&agency=Air%20Force"
Returns the email and phone. If your CRM has an "outreach queue" you push the result straight in.
Your platform ingests press releases, government announcements, LinkedIn posts, vendor newsletters. NER pulls out names and agencies. You want to enrich each mention with verified contact info so a sales or BD user has something to act on.
import requests
def enrich(name, agency=None):
r = requests.get(
"https://govconapi.com/api/v1/contacts/lookup",
params={"name": name, "agency": agency} if agency else {"name": name},
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=5,
)
if r.status_code == 200:
return r.json()["data"]
if r.status_code == 404:
return []
r.raise_for_status()
Cache responses for an hour (the API itself signals this with Cache-Control). At 1,000 calls/hour and ~100ms each, this comfortably handles a steady stream of mentions without hitting limits.
Your firm is doing due diligence on a vendor whose SAM record names "Karen Chen, DLA" as the contracting POC for a recent award. You want to verify the email matches the agency on the record before any contact, and you want to know how recently the data was confirmed.
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/contacts/lookup?name=Karen%20Chen&agency=DLA"
The last_seen field tells you when this CO last appeared on a federal notice. If it's within the last 90 days (which is true for 73% of records), the data is current and the email is reliable. Older than a year and you should treat the email as needing manual confirmation.
Your CRM has 800 federal CO emails collected over years of bid responses. People move agencies. Job titles change. You want a nightly job that takes each email and pulls the current name, agency, phone:
for email in crm_emails:
r = requests.get(
"https://govconapi.com/api/v1/contacts/lookup",
params={"email": email},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
if r.status_code == 200:
record = r.json()["data"][0]
update_crm(email, record)
Email is the unique key in our data, so the lookup is exact 1:1. If a 404 comes back, the email is no longer associated with an active federal contracting role, which is itself a useful signal for your CRM hygiene.
A reporter or analyst sees a name referenced in a federal procurement story and wants to verify role and agency before quoting. Single lookup, done in seconds:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/contacts/lookup?name=Lyndon%20Paloma"
About 60 records in the dataset are shared role inboxes, not individual people. Examples: 23 rows for "BAA Coordinator" (each agency has its own), 7 for "TTO BAA Coordinator", 6 for "Solicitation Coordinator", 5 for the literal name "Contracting Officer". These exist because federal notices sometimes list a queue address rather than a named individual.
This is useful (those queues are the right inbound channel for vendor questions) but worth knowing: a search for a generic role term returns role accounts, not people. Add an agency filter to disambiguate, or look up by exact email when you need a specific record.
About 5% of names in the dataset have collisions across agencies (different people, same name). For "John Smith" or "Maria Garcia" you'll get 5 results from different agencies. Three ways to narrow:
?name=Smith&agency=Air%20Force matches the agency or department field. Substring, so "Air Force" hits "DEPT OF THE AIR FORCE" cleanly.?name=Smith&state=GA filters by 2-letter state code. Useful when you know the geo from the original mention.?email=... resolves exactly without the disambiguation problem.The maximum yield from any single name is 5 records. The maximum yield from name + agency is also 5. Combine name across all 124 agencies and you'd hit at most 5 × 124 = 620 records for the most common names, but only a handful of names actually populate that many agencies.
The following live in the GovCon Contacts web app, not in the API:
The split is intentional. The API gives you the resolver shape that integrates cleanly with code. The web app gives you the discovery and intelligence layer that humans use when they need to do research, not just resolve a known name.
| If you... | Use |
|---|---|
| Already have a name and need email/phone | API |
| Already have an email and need fresh agency/name | API (email reverse lookup) |
| Need to enrich N records in a batch job | API |
| Want to browse "who's in contracting at GSA Region 5" | Web app |
| Need award history or colleague mapping | Web app |
| Want AI buyer dossiers for outreach research | Web app |
| Building a feature inside your own product | API |
| Doing one-off human research | Web app |
Both the API and the web app are included with the Pro plan at $39/mo. There is no separate API-only or contacts-only billing. The Developer plan ($19/mo) includes 30 monthly profile views via the web app for evaluation, but does not include the programmatic Contacts API.
If you need higher rate limits, bulk historical exports, or custom data shapes (specific NAICS slices, agency dumps, AI/ML training sets), the Enterprise tier starts at $1,500/mo and is the right conversation.
Refreshed nightly from federal contract notices. 73% of contacts have appeared on a notice in the last 90 days; 99% in the last year. The last_seen field on every record tells you exactly how fresh that specific contact is.
The underlying data has a role column, but it's populated for only ~7% of contacts. Returning a field that's null 93% of the time would force every customer to write null-handling logic for almost no signal. We'd rather not promise data we don't reliably have. If population improves, we'll add it back.
Anti-enumeration plus disambiguation. With 23,000 records and a 1,000/hour rate limit, capping at 5 prevents the dataset from being trivially mirrored, while still handling the realistic "John Smith returns multiple matches, narrow with agency" workflow. If you need more matches for a specific name, narrow with agency or state.
A stable 12-character identifier per contact. Useful if you're caching records and want a key that doesn't change when the person moves agencies (their email might change, their slug doesn't). Currently not used as a deeplink to the web app, but reserved for that use case.
Not via this API. If your use case actually requires bulk (research, AI training, mass enrichment), talk to us about Enterprise. Custom slices and bulk exports ship in days, not weeks.
Federal only. State, local, and tribal procurement contacts are not in the dataset. International is outside the federal scope.
Yes, they come from federal contract notices, where the email is required for the notice to be valid. Once a CO leaves federal service, their email typically still bounces (federal IT phases addresses out within weeks). The last_seen field is your best freshness signal: anything seen in the last 90 days is current.
1,000 requests per hour per API key (standard Pro tier). 60 per minute per IP burst. If you need more, contact us about Enterprise.