GovCon API Documentation

Federal contracts API for developers. Clean JSON: federal opportunities, FPDS prime contracts, SAM Award Notices, federal subawards, the SAM entity registry (~873K registered firms), exclusions, contracting-officer contacts, and full notice descriptions.

For interactive API testing, visit /docs. Machine-readable spec at /openapi.json; single-file bundle at /docs/all.md; LLM discovery file at /llms.txt.

Recent additions and capability changes are tracked on the changelog. Each capability also lives in its permanent section below.

Quick Start:

1. Get a free API key (emailed instantly):

curl -X POST "https://govconapi.com/api/v1/keys?email=YOUR_EMAIL&plan=free"

2. Confirm it works with /api/v1/me (needs no other parameters; returns your plan, rate limit, and search window):

curl -H "Authorization: Bearer YOUR_API_KEY" "https://govconapi.com/api/v1/me"

3. Make your first search. Note: /opportunities/search requires at least one filter (a bare call returns 400):

curl -H "Authorization: Bearer YOUR_API_KEY" "https://govconapi.com/api/v1/opportunities/search?naics=541512&limit=5"
Building with AI tools or generating clients?
Coverage: Comprehensive coverage from FY2025 (October 2024) onward, refreshed daily from SAM. Sparse historical sample from prior backfills going back to 2008. FFATA subaward records (/subawards/*) cover FY2025+FY2026 from USAspending, refreshed daily; older fiscal years available on request. Use /api/stats or /meta for current row counts.

All example counts and response values in this documentation are illustrative. Use /api/stats or /meta for current numbers.

The active field is always "Yes" by design.
Every record we store carries active: "Yes". We never archive — that's the "Never Archived" promise. The stored field is NOT a SAM source flag and should not be used to determine whether a notice is currently open.

To filter for currently-open opportunities, use the convenience parameter ?active_only=true on /opportunities/search. We compute this at query time as archive_date_detailed > CURRENT_DATE. Equivalent to filtering on the date yourself; the parameter is just sugar.
Plan restrictions. All search filters are available on every plan, including the free trial. CSV export requires the Developer plan.

Plans & Limits

Plan Price Rate Limit Results / Request CSV Exports / Day Endpoints Included
Free Trial (14 days) $0 25 / day 50 0 Opportunities (all filters), Awards, Exclusions, Entities (UEI / CAGE / name lookup), Utilities
Developer $19 / mo 1,000 / hour 1,000 15 Everything in Free Trial + CSV export + larger page size + higher rate limit + no expiry. SAM Entity registry lookup by UEI/CAGE/name available on this tier. Includes 30 GovCon Contacts profile views/month via the web app (no programmatic Contacts API access).
Pro $39 / mo 1,000 / hour 1,000 15 Everything in Developer + Companies (search, profile, awards, peers) + Entities multi-filter search (DSBS replacement) + Entities expiration radar + Vendor Risk (single, bulk) + Contacts API (resolver lookup) + GovCon Contacts web app + Subawards (FFATA: search, single report, prime→sub, sub→prime reverse lookup, plus subaward enrichment blocks on awards, companies, entities, exclusions, and vendor-risk endpoints)

Endpoints with the Pro badge below require Pro. Pricing details and full plan comparison: govconapi.com/#pricing.

Search Window

Our core value is fresh, normalized federal procurement data, refreshed daily. Plan tiers are sized around that. Developer and Pro plans cover a rolling recent window appropriate for daily operations, year-over-year analysis, and bidder past-performance lookups. Deep multi-year historical research is a different product — for that, see Enterprise.

Lookups by ID are not gated: if you have a notice_id, UEI, or solicitation number, the record returns regardless of age.

Plan / surfaceWindowEarliest searchable date
Free Trial — opportunities / awards90 days~3 months ago
Developer — opportunities / awards365 days~12 months ago
Pro — opportunities / awards730 days~24 months ago
All plans — subawards (any /subawards/* surface)FY2025 + FY2026 today (plan window 1,825 days)2024-10-01

Search endpoints (window applies): /opportunities/search, /awards/search, /export.csv, /subawards/search, /companies/{uei}/subawards, /companies/{uei}/prime-relationships.

Lookup endpoints (window does NOT apply): /opportunities/{notice_id}, /companies/{uei}, /companies/{uei}/awards, /companies/{uei}/peers, /entities/*, /exclusions/*, /subawards/{subaward_sam_report_id}. If you have a record ID, you can fetch the record regardless of age.

What happens when you pass an older date

Date filters older than your plan's window are clamped to the window floor. No 400, no surprise: /opportunities/search returns a top-level window block on every response that tells you exactly what happened, so you can show users why the result set is bounded.

Concrete example, Developer plan (365-day window) on 2026-05-18:

# request:
GET /api/v1/opportunities/search?naics=541511&posted_after=2024-01-01&limit=20

# response (200 OK):
{
  "data": [ ... 20 rows, all with posted_date >= 2025-05-19 ... ],
  "pagination": { "limit": 20, "offset": 0, "total": ..., "has_next": true },
  "filters_applied": {
    "naics": "541511",
    "posted_after": "2024-01-01",
    "date_from": "2025-05-19"        // ← effective lower bound after clamp
  },
  "window": {
    "plan_window_days": 365,
    "earliest_searchable": "2025-05-19",
    "clamped": true,
    "date_from_requested": "2024-01-01",
    "reason": "Requested start date predates your plan window (last 365 days). Clamped to 2025-05-19. For deeper history see https://govconapi.com/enterprise"
  }
}

The window block is always present. clamped: false means your query was inside the window; clamped: true means results were bounded by it (either you sent no date and the window floor was injected, or you sent an older date and it was clamped up). When clamping is due to your explicit date, the block also includes date_from_requested so you can tell the two cases apart. Inspect your effective window directly via GET /api/v1/me (fields historical_window_days and historical_earliest_allowed).

Common questions

How can I tell the difference between search and lookup? Search endpoints are URL-stem (/opportunities/search) and return paginated lists. Lookup endpoints have an identifier in the path (/opportunities/<notice_id>, /companies/<uei>) and return a single record or that record's relations.

I have a notice_id from 2018 — can I still fetch it? Yes. GET /api/v1/opportunities/<notice_id> returns it. The window only restricts what shows up in paginated search.

I want every award for one specific UEI going back years. Use GET /api/v1/companies/<uei>/awards (Pro). Not gated by window — full company history.

Why does has_next: true still appear after my window? The window injects a floor into the WHERE clause; has_next reflects whether there are more matching rows within the window. To page further, raise offset normally.

I genuinely need cross-year historical search. Email [email protected] or visit /enterprise for a quote on full historical access.

How far back does /subawards/* go? Coverage today is FY2025 + FY2026 (every FFATA subaward from October 2024 onward). The plan window code carries a 5-year ceiling so older fiscal years can drop in transparently as we backfill from USAspending. Today the effective floor exposed in the window.earliest_searchable response field is the data floor (2024-10-01), not the plan floor, so a customer never sees a date in the response that returns zero rows. Older date filters are clamped to the effective floor, the window block surfaces what happened, and single-record lookup by subaward_sam_report_id is not gated. For the full historical depth (FY2011 onward) today, see Enterprise.

Separate rule for /opportunities/delta

/opportunities/delta is a sync endpoint ("what changed since I last pulled"), not a backfill tool. It has its own cap of 60 days, independent of the per-plan historical-search window. A since value older than 60 days is silently clamped; the sync block in the response surfaces since_requested and a clamp_reason when this happens. For initial backfill, use /opportunities/search with date filters; for ongoing sync, call /delta with a recent since (last hour / last day / last week is typical).

Authentication

Base URL: https://govconapi.com/api/v1

Send your API key in the Authorization header using the Bearer format:

Authorization: Bearer YOUR_API_KEY

API keys are available at govconapi.com.

Response Shaping (?fields=)

Search and list endpoints accept a fields query parameter to return only the columns you need. Reduces payload size dramatically when you only care about a few fields out of dozens.

Syntax: comma-separated list of field names from the response shape.

# Default — returns all 59 fields per opportunity record
curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/opportunities/search?naics=541512&limit=20"

# With ?fields= — returns only the 4 columns requested (~95% smaller response)
curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/opportunities/search?naics=541512&limit=20&fields=notice_id,title,posted_date,response_deadline"

Supported endpoints:

Behavior:

When to use it: any time you're paginating through results and only need a subset of columns. Common cases: building a search-results UI (need 4-6 columns, not 59); ETL pipelines (need stable identifiers + a few timestamps); monitoring (need notice_id, title, last_seen).

Endpoints

Response source attribution (universal)

Every /api/v1/* response carries a top-level _sources array naming where the data came from. Endpoints that compose multiple sources list each one (so a Pro /companies/{uei} call that returns both subaward and FPDS enrichment carries ["sam_entities", "sam_opportunities", "usaspending_ffata", "usaspending_fpds"]). Customers reading _sources know exactly what to verify upstream when something looks off, without consulting per-endpoint documentation.

In the response examples throughout this guide, _sources and _report_url are sometimes elided for brevity. _sources is omitted when the value is the obvious single-source case for that endpoint (e.g. ["sam_opportunities"] on opportunities responses); _report_url is omitted because its value is a unique signed link per request and any literal example value would be misleading. Assume both fields are present on every authenticated /api/v1/* response unless an example explicitly omits them. The /api/v1/federal-hierarchy/* reference-data endpoints are the exception: they currently do not carry _sources.

IdentifierMeaning
sam_opportunitiessam.gov contract notices (the opportunities table) — serves /opportunities/*, /awards/*, /delta
sam_entitiessam.gov entity registry — serves /entities/* and the base data on /companies/{uei}
sam_exclusionsSAM exclusions extract — serves /exclusions/*
usaspending_fpdsFPDS prime contract transactions from USAspending — serves /contracts/* and FPDS enrichment blocks on other endpoints
usaspending_ffataFFATA Section 2 subaward records from USAspending — serves /subawards/* and subaward enrichment blocks on other endpoints
usaspending_fabsFinancial-assistance (grant) award transactions from USAspending — serves /grants/*
derived_duns_uei_crosswalkFFATA-derived crosswalk used by the public DUNS↔UEI lookup
derived_vendor_riskComputed signal stack served by /vendor-risk/*
Reporting a wrong response (universal)

Every /api/v1/* response also carries a top-level _report_url. Open it in a browser and you land on a one-textarea form pre-filled with the call you just made (method, path, query, your email, request timestamp). Type what you expected versus what you actually got, hit Send, and it emails the team. No portal login, no support-form hop. The signed link is valid for 24 hours.

The token is bound to the customer who generated it. Don't paste a response containing _report_url into a public Slack, GitHub issue, or gist, since anyone holding the link can submit one report under your email while it's valid.

Pro enrichment fields (universal pattern)

Several Pro-tier responses include enrichment blocks that compose FPDS prime contract activity and FFATA subaward activity for a single UEI. The same shapes appear in multiple places, so the field reference is given once here and cross-linked from each endpoint section.

  • Per-recipient FPDS aggregates on /companies/{uei} and /entities/{uei}: fpds_obligated_total (float, net federal action obligation summed across all prime contract transactions in the FY2025-onward window (since 2024-10-01)), fpds_distinct_contracts (int, distinct contract_award_unique_key), fpds_transaction_count (int, every base award and modification), fpds_first_action_date and fpds_latest_action_date (ISO date, activity bounds). On /companies/{uei} only: fpds_top_naics (array of up to 5 objects, each {code, description, transaction_count, value}, sorted desc by value) and fpds_top_agencies (array of up to 5 objects, each {name, transaction_count, value}, awarding sub-agency by total $). Each top_* object also carries a deprecated n field with the same value as transaction_count; new code should read transaction_count.
  • Per-recipient FFATA subaward aggregates on /companies/{uei}, /entities/{uei}: sub_revenue_total (float, total FFATA dollars received as a sub), top_paying_primes (array of up to 10 objects, each {uei, name, total, subaward_count}). On /companies/{uei} only: prime_revenue_share (float, FPDS prime obligations / (FPDS prime + sub revenue) ratio).
  • contract_exposure block on /vendor-risk/{uei}: {fpds_obligated_total, fpds_distinct_contracts, fpds_transaction_count, fpds_first_action_date, fpds_latest_action_date, top_agencies: [{name, value, transaction_count}], window_days}. Descriptive only; not part of triage rules.
  • subaward_exposure block on /vendor-risk/{uei}: {as_prime: {total_paid_to_subs, distinct_sub_vendors, subaward_count, first_subaward_date, last_subaward_date, top_subs: [{uei, name, total, subaward_count}]}, as_sub: {total_received_from_primes, distinct_primes, subaward_count, first_subaward_date, last_subaward_date, top_primes: [{uei, name, total, subaward_count}]}, window_days}. Both sides of the FFATA flow.
  • recent_contract_actions block on /exclusions/{uei_sam}: {lookback_days, total_obligated_recent, transaction_count, distinct_contracts, distinct_agencies, most_recent_action_date, most_recent_action_amount, most_recent_awarding_agency, most_recent_piid, top_paying_agencies: [{name, value, transaction_count}]}. The compliance question: is this excluded UEI still receiving direct prime contract obligations?
  • recent_sub_payments block on /exclusions/{uei_sam}: {lookback_days, total_received, subaward_count, distinct_primes, most_recent_subaward_date, most_recent_subaward_amount, most_recent_paying_prime: {uei, name}, top_paying_primes: [{uei, name, total, subaward_count}]}. The "are clean primes still paying this excluded vendor as a sub?" question.

All blocks are silently omitted on Free / Developer keys; Pro callers (contacts_access=true) receive them inline so they don't need a second round-trip. The blocks always return the zero shape (empty arrays, zero counts) when no records exist for the UEI in the window, so client code can safely read fields without per-call None checks.

GET

/api/v1/opportunities/search

Requires auth

Search federal contract notices with filters. Returns paginated results.

At least one meaningful filter is required. Provide at least one filter parameter (e.g. naics, keywords, state, date_from, agency). Filter values must be meaningful:
  • keywords: minimum 3 characters
  • naics, psc, state, set_aside, notice_type, agency, location, solicitation_number, naics_multiple: minimum 2 characters
  • date_from, date_to, posted_after, due_before, due_after: full date format YYYY-MM-DD
  • value_min, value_max, has_attachments: any valid value
Parameters like limit, offset, and sort_by do not count as filters. Requests without a valid filter return 400. For full-database sync, use /api/v1/opportunities/delta: pass a timestamp, get only what changed since.

Basic filters (all plans)

  • naics – Single NAICS code (e.g. 541330)
  • psc – Product Service Code (e.g. D302)
  • state – State code (e.g. CA, TX) or full name (e.g. California)
  • keywords – Full-text search across title, agency name, and contract description. Also matches solicitation number. Hyphenated terms (e.g. fluorine-free) are handled automatically. Results are ranked by relevance to title and agency by default (descriptions still participate in matching but not in ranking, since they include standard regulatory clause text that would otherwise dilute results).
  • notice_type – Filter by notice type (e.g. Solicitation, Award Notice, Presolicitation)
  • solicitation_number – Exact solicitation number (e.g. W52P1J-25-R-0001)
  • limit – Results per page (default 20, max: Free=50, Developer=1000)
  • offset – Pagination offset (default 0)
  • fields – Comma-separated response fields to return. notice_id always included. Example: fields=notice_id,title,naics,posted_date. See Response Shaping.
  • active_onlytrue / false. When true, returns only currently-open notices (those whose archive date is in the future). Echoed in filters_applied when set so a client can confirm the filter applied. Computed as archive_date_detailed > today; the stored active field is always "Yes" by design and is not what this filter reads.
Concepts to know before you integrate. These are assumptions the data makes that are not obvious from the field names; they cause the most integration mistakes.
  • notice_id vs solicitation_number. One procurement (one solicitation_number) moves through a lifecycle: Sources Sought → Presolicitation → Solicitation → amendments → Award Notice. Every stage and every amendment is a separate notice_id (a new 32-char hex). One solicitation maps to many notice_ids. To count unique procurements, dedupe on solicitation_number (falling back to notice_id when null). To walk a procurement's lifecycle, filter by solicitation_number=....
  • Three different date fields. posted_date is a calendar date (no time). response_deadline is a timezone-aware timestamp (the bid deadline; precision varies by source). archive_date_detailed is a future calendar date (when the notice will be archived). Filters map to these: date_from / date_to / posted_after filter posted_date; due_after / due_before filter response_deadline; active_only reads archive_date_detailed.
  • active is always "Yes". The stored active field is a constant by design ("never archived" is a product feature). Do not filter on it. Use active_only=true to get currently-open notices.
  • description_text is tri-state. Real description text, the literal sentinel "NO_DESCRIPTION_AVAILABLE" (the notice has no description in the source), or null (description not yet retrieved). Handle all three.
  • set_aside_type "none" is tri-state too: "", "NONE", or null all mean unrestricted. A real code (SDVOSBC, 8A, WOSB, HZC, SBA, etc.) means restricted. Majority of set-asides are SBA.
  • Combined notices are common. "Combined Synopsis/Solicitation" is roughly a third of opportunities. When pulling bidding opportunities, query both notice_type=Solicitation and notice_type=Combined%20Synopsis/Solicitation to catch everything.
  • Award amounts only on Award Notices. award_amount, awardee_name, award_date, and related fields are populated only when notice_type=Award Notice. Solicitations and Sources Sought do not have these fields.
notice_type valid values (exact case-insensitive match): Solicitation, Combined Synopsis/Solicitation, Presolicitation, Sources Sought, Award Notice, Justification, Special Notice, Sale of Surplus Property, Modification, Intent to Bundle Requirements (DoD-Funded). Solicitation does NOT match Presolicitation.
Undocumented aliases. Four parameter aliases are accepted for forgiveness: q and keyword map to keywords; posted_from maps to date_from; posted_to maps to date_to. Canonical names win if both forms are sent.
naics + naics_multiple: if both are sent, naics_multiple wins and the single naics is silently dropped. Pick one. A 1-digit or 7+ digit value returns 400; a 2-5 digit prefix returns 400 with the "must be 6-digit" message; a well-formed but nonexistent 6-digit code returns 200 with total=0 (an empty result can mean "bad code," not "no opportunities").

Date, location, amount, and agency filters

  • agency – Agency name (partial match, e.g. defense)
  • set_aside – Set-aside type (e.g. SBA, 8(a), SDVOSB)
  • posted_after – Posted after date (YYYY-MM-DD format)
  • due_before – Response deadline before date (YYYY-MM-DD format)
  • due_after – Response deadline after date (YYYY-MM-DD format). Use due_after=<today> to return only open opportunities.
  • value_min – Minimum award value in dollars (only for Award Notice records)
  • value_max – Maximum award value in dollars (only for Award Notice records)
  • location – Performance location (city or state)
  • date_from – Posted date from (YYYY-MM-DD format)
  • date_to – Posted date to (YYYY-MM-DD format)
  • sort_by – Sort field. Valid: posted_date (default when no keywords), due_date (maps to response_deadline), award_amount, title, agency, relevance. Invalid value returns 400 with the valid list (no silent fallback). When keywords is set and sort_by is omitted, results default to relevance (best title and agency matches first); pass sort_by=posted_date to override.
  • sort_orderasc or desc (default desc). Ignored when sort_by=relevance. NULLs sort first on asc, last on desc. Results are tie-broken by notice_id so pagination is stable.
  • naics_multiple – Comma-separated NAICS codes for multiple search
  • has_attachments – Filter by attachment availability: true or false
  • formatjson (default) or csv. csv returns this same filtered result set as a CSV download (honoring fields), with all 59 columns by default. Requires the Developer plan, same as /export.csv.

Response

{
  "data": [
    {
      "notice_id": "abc123def456",
      "title": "IT Services Contract",
      "agency": "Department of Defense",
      "posted_date": "2025-11-01",
      "response_deadline": "2025-12-15T17:00:00+00:00",
      "naics": ["541330"],
      "set_aside_type": "Small Business",
      "solicitation_number": "W52P1J-25-R-0001",
      "description_text": "Full contract requirements...",
      "award_amount": 1500000.00,
      "awardee_name": "Tech Solutions Inc",
      "contact_name": "John Smith",
      "contact_email": "[email protected]",
      "sam_url": "https://sam.gov/opp/...",
      "performance_city_name": "Washington",
      "performance_state_code": "DC",
      "_additional": "59 fields total per record"
    }
  ],
  "pagination": {
    "limit": 20,
    "offset": 0,
    "total": 98,
    "total_is_estimate": false,
    "has_next": true
  },
  "filters_applied": {
    "naics": "541330",
    "state": "CA",
    "date_from": "2024-05-27"
  },
  "window": {
    "plan_window_days": 730,
    "earliest_searchable": "2024-05-27",
    "clamped": true,
    "reason": "No date filter was sent; results are limited to your plan window (last 730 days, since 2024-05-27). Pass date_from within the window, or see https://govconapi.com/enterprise for deeper history."
  }
}
Total count is capped at 10,000. On broad searches, pagination.total stops at 10000 and the response sets "total_is_estimate": true (read it as "10,000+"). Exact counts on very broad full-text matches are expensive, so the count short-circuits past 10,001. Page forward with has_next; do not compute the last page from total when total_is_estimate is true. The other list endpoints (awards, companies, entities, exclusions) return an exact total.

The window block shown in the response example is always present on this endpoint. It surfaces the plan history window applied to your query; see Search Window above for the full field-by-field explanation and clamp semantics.

Examples

NAICS + state:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://govconapi.com/api/v1/opportunities/search?naics=541330&state=CA&limit=10"

NAICS 541330 (engineering services) in California returns 98 results

Agency filter:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://govconapi.com/api/v1/opportunities/search?agency=defense&limit=20"

Department of Defense returns 149,706 results

Paid plans - High-value awards:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://govconapi.com/api/v1/opportunities/search?notice_type=Award%20Notice&value_min=1000000&sort_by=award_amount"

Note: value_min only works with Award Notice records (completed contracts)

Paid plans - Multiple NAICS codes:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://govconapi.com/api/v1/opportunities/search?naics_multiple=541330,541511&limit=10"

Paid plans - Only opportunities with attachments:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://govconapi.com/api/v1/opportunities/search?has_attachments=true&limit=20"

Not all opportunities have attachments – check resource_links_array in the response.

GET /api/agency-crosswalk No Auth Required

Free agency name standardization utility. Returns mappings between agency variants and canonical names, with department hierarchy and USASpending codes.

Data: Generated fresh from current contract database. Includes 2,000+ agency mappings with confidence scores.

Response Format

{ "data": [ { "raw_agency_text": "Dept. of Defense", "canonical_agency": "Department of Defense", "canonical_acronym": "DOD", "department": "Department of Defense", "department_acronym": "9700", "fh_level": "sub_tier", "usaspending_toptier_code": "097", "confidence": 0.95, "source": "federal_register", "frequency": 1250, "first_seen": "2025-08-07", "last_seen": "2025-11-21" } ], "count": 2000, "message": "Free agency name standardization utility - updated weekly" }

Example

curl "https://govconapi.com/api/agency-crosswalk"

GET /api/v1/opportunities/{notice_id} Requires Auth

Fetch one complete opportunity by its 32-character notice_id. Use it when you already hold a notice_id (from search results, a sam.gov URL, or your own store) and want the full record.

Not subject to the history window. Unlike /opportunities/search, ID lookups return notices of any age, so this is the way to retrieve an old notice you already have the id for. Plan-window clamping does not apply here.

Parameters

  • notice_id (path) — a 32-character hex string (regex ^[a-f0-9]{32}$, case-insensitive). Example: 00016e91148440e0b80537f6362f45ea.

No query parameters are accepted; field projection via ?fields= is not available on this endpoint (the full record is always returned).

notice_id is NOT the same as solicitation_number.
  • notice_id: a 32-character hex UUID like 002518857671489eb8688df91a64ecfb. This is the path parameter this endpoint accepts.
  • solicitation_number: a human-readable contract identifier like N0003925R4014 or W52P1J-25-R-0001. The same solicitation_number can appear on multiple notice rows (every amendment is its own notice).

If you have a solicitation_number and want the matching notice(s), use the search endpoint instead:

GET /api/v1/opportunities/search?solicitation_number=N0003925R4014

Calling /api/v1/opportunities/<solicitation_number> returns a structured 400 with a hint_url pointing at the search endpoint (see Errors below).

Response Format

The record is wrapped under opportunity, with two metadata fields alongside it:

{ "opportunity": { "notice_id": "00016e91148440e0b80537f6362f45ea", "title": "FILTER ASSEMBLY,ELE", "notice_type": "Solicitation", "solicitation_number": "SPRMM126Q...", "agency": "DEPT OF DEFENSE.DEFENSE LOGISTICS AGENCY.DLA MARITIME...", "naics": ["334419"], "psc": ["5915"], "posted_date": "2026-05-22", "response_deadline": "2026-06-22T20:30:00+00:00", "description_text": null, "...": "all 59 opportunity fields, same shape as /opportunities/search data[]" }, "has_raw_data": true, "last_updated": "2026-05-22T..." }
  • opportunity — the same 59-field record returned by search. Reminders from the search domain primer: naics / psc are arrays; description_text is tri-state (real text / "NO_DESCRIPTION_AVAILABLE" / null); active is always "Yes" by design; set_aside_type "none" is tri-state.
  • has_raw_datatrue if the original SAM payload is retained for this notice (available for notices ingested through the live pipeline; older backfilled rows may be false).
  • last_updated — when this notice last changed in our database. Same value /opportunities/delta sorts and filters on.

Errors

  • 400 — path is not 32-char hex (e.g. a solicitation number passed by mistake). Returns a structured body with a hint_url pointing to the search endpoint so the caller can recover programmatically.
  • 404 — well-formed id, no such notice in our database. Body: {"detail": "Opportunity not found"}.
  • 429 — rate limit exceeded.

Sample 400 body when a solicitation number is passed instead of a notice_id:

{ "detail": { "error": "invalid_notice_id", "message": "'36C24926R0057' is not a valid notice_id. notice_id is a 32-character hex string (example: 002518857671489eb8688df91a64ecfb). If '36C24926R0057' is a solicitation_number, use the search endpoint instead.", "hint_url": "/api/v1/opportunities/search?solicitation_number=36C24926R0057", "docs": "https://govconapi.com/api-guide#notice-id-vs-solicitation-number" } }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/00016e91148440e0b80537f6362f45ea"

Note: description_text: null does not mean "no description exists." It means enrichment has not run for this notice yet (rare on notices older than a day). Re-fetch later, or check the live description_url.

Worked example: an EPA Region 1 procurement (real)

Calling GET /api/v1/opportunities/0035baae11ef4760b1157425704b23c2 returns the EPA Region 1 Contracting Office's notice "H--Proficiency Testing (PT) Samples" for the New England Regional Laboratory at North Chelmsford, MA. Live response excerpt (verified 2026-06-02):

{ "opportunity": { "notice_id": "0035baae11ef4760b1157425704b23c2", "title": "H--Proficiency Testing (PT) Samples ", "notice_type": "Combined Synopsis/Solicitation", "agency": "ENVIRONMENTAL PROTECTION AGENCY.ENVIRONMENTAL PROTECTION AGENCY.REGION 1 CONTRACTING OFFICE", "naics": ["325199"], "psc": ["H268"], "posted_date": "2026-04-10", "response_deadline": "2026-04-21T16:00:00+00:00", "solicitation_number": "68HE0126Q0009", "contact_name": "Williams, Brandon", "contact_email": "[email protected]", "contact_phone": "617-918-1965", "performance_street_address": "NE Regional Laboratory", "performance_city_name": "North Chelmsford", "performance_state_code": "MA", "performance_zip": "01863", "archive_date_detailed": "2026-05-06", "active": "Yes", "...": "39 additional fields per opportunity" }, "has_raw_data": true, "last_updated": "2026-04-12T14:04:38.552010+00:00", "_sources": ["sam_opportunities"] }

One detail call delivers the procurement context (agency hierarchy, NAICS, PSC, solicitation number), the awarding officer (name, email, phone), the place of performance (lab address), the lifecycle markers (posted_date, response_deadline, archive_date_detailed), and the SAM last_updated stamp. Per the active column is "Yes" by design rule, "active": "Yes" here even though the notice has archived; the canonical "is it currently open" answer is archive_date_detailed compared against today.

GET /api/v1/opportunities/{notice_id}/attachments Requires Auth

Return the downloadable attachment URLs (solicitation PDFs, drawings, statements of work, amendments) for one notice. URLs are direct SAM.gov download links you can hand to a browser or HTTP client; we do not proxy them.

Parameters

  • notice_id (path) — same 32-character hex rule as /opportunities/{notice_id}. A solicitation number returns the same structured 400 with a hint_url.

No query parameters are accepted.

Response

{ "notice_id": "002b2fe0c9bd4af9809196504715c3e1", "title": "Mobile Subscription Services for U.S. Embassy Singapore", "attachment_count": 3, "attachments": [ "https://sam.gov/.../download" ] }
  • attachment_count — length of attachments. Often 0; many notices carry their documents as external links or have no files at all.
  • attachments — array of canonical SAM.gov download URLs. Same data as resource_links_array on the main opportunity record. An empty array ([]) is returned when a notice has none.

This is the same data the search filter has_attachments reads, so the two stay consistent: has_attachments=true on search selects notices where this array is non-empty.

Finding notices that have attachments

There is no list endpoint; look them up per notice. Filter search first, then fetch attachments for each id:

# 1) find notices that have files, in your space "https://govconapi.com/api/v1/opportunities/search?has_attachments=true&naics=541512&limit=50" # 2) for each result.notice_id: "https://govconapi.com/api/v1/opportunities/{notice_id}/attachments"

Downloading the files

Each URL is a SAM.gov "front-door" link. When a client follows it, SAM 303-redirects to a short-lived signed S3 URL and the file streams from there. Three practical notes:

  • Use GET with redirect-follow, not HEAD. AWS pre-signed URLs scope the signature to the GET method; a HEAD against the resolved S3 URL returns 403.
  • Do not cache the resolved S3 URL. The signature has a very short expiry. Re-hit the SAM URL each time you need the file.
  • Allow *.s3.amazonaws.com through proxies / firewalls. If a corporate proxy blocks S3, the redirect lands on a host that fails to load.

Errors

  • 400 — path is not 32-char hex (e.g. a solicitation number). Structured body with hint_url to the search endpoint, same shape as /opportunities/{notice_id}.
  • 404 — well-formed id, no such notice. Body: {"detail": "Opportunity not found"}. A notice that exists but has no files returns 200 with attachment_count: 0, not 404.
  • 429 — rate limit exceeded.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/002b2fe0c9bd4af9809196504715c3e1/attachments"
When a file URL returns "BAD_REQUEST: The resource has been deleted". SAM occasionally removes individual files after publication (a contracting officer pulls or replaces them in an amendment). The notice's attachment list still references the URL until the next refresh of our copy, so a previously-working URL can stop working. The error comes from SAM directly, not our endpoint; the same file is unreachable from sam.gov for the same reason. If you suspect a file is missing, re-fetch this endpoint to see if our copy has updated.
Some notices' documents are not on SAM at all. A subset of solicitations (notably DLA / NAVSUP NSN parts buys) reference Technical Data Packages hosted in DoD systems like cFolders or NECO that require JCP (Joint Certification Program) certification. Our endpoint correctly returns attachment_count: 0 in that case; the documents exist but are not in the SAM extract.

Freshness: the array is rebuilt periodically from the source; a brand-new notice may briefly show 0 before its files are picked up, even when SAM already has them.

GET /api/v1/stats Requires Auth

Get comprehensive database statistics including total records, recent activity, unique agencies and NAICS codes.

Response

{ "database_stats": { "total_opportunities": 199016, "recent_opportunities": 7874, "unique_agencies": 2224, "unique_naics_codes": 1095, "total_exclusions": 163314, "active_exclusions": 163306, "last_updated": "2026-04-04T02:01:11.019038+00:00" }, "data_freshness": { "last_collection": "2026-04-04T02:01:11.019038+00:00", "collection_frequency": "Daily at 2:00 AM UTC" } }

Note: All values shown are examples. Use the endpoint to get current statistics.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/stats"

GET /api/stats No Auth Required

Public statistics endpoint for landing page. Returns real-time counts computed from database.

Response

{ "total_notices": 199016, "currently_open": 14701, "award_records": 47103, "full_descriptions": 167770, "with_attachments": 63327, "total_exclusions": 163314, "coverage": { "descriptions": "84.3%", "attachments": "31.8%" } }

Note: All values shown are examples. Use the endpoint to get current statistics.

Field Descriptions

  • total_notices - Total contract notices in database (all types, never archived)
  • currently_open - Notices still accepting bids (deadline > now, not yet awarded)
  • award_records - Notices with award information (winner, amount, date)
  • full_descriptions - Notices with complete extracted requirement text from SAM.gov

Example

curl "https://govconapi.com/api/stats"

GET /meta No Auth Required

Get metadata about the dataset including last collection time and total record count.

Response

{ "last_delta_utc": "2025-11-22T02:06:52.095708+00:00", "records": 62550, "description": "Federal contract opportunities from SAM.gov" }

Example

curl "https://govconapi.com/meta"

Note: Use this endpoint to check data freshness. Collection runs daily at 2:00 AM UTC. The records value shown is an example - use the endpoint to get the current count.

GET /api/v1/me Requires Auth

Get information about your API key: plan, entitlements, current rate-limit state, and your effective historical search window. A good first authenticated call to confirm your key works (it requires no other parameters).

Response

{ "plan": "developer", "contacts_access": false, "email": "[email protected]", "rate_limit_remaining": 980, "rate_limit": 1000, "historical_window_days": 365, "historical_earliest_allowed": "2025-05-28", "docs_url": "https://govconapi.com/api-guide" }
  • plan / contacts_access — your tier and whether Pro Contacts/Companies/Vendor-Risk features are enabled.
  • rate_limit / rate_limit_remaining — your hourly ceiling and what's left this window (also returned as X-RateLimit-Limit / X-RateLimit-Remaining headers on every response).
  • historical_window_days / historical_earliest_allowed — the effective search window applied to /opportunities/search, /awards/search, and /export.csv.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/me"

GET /api/v1/contracts/{piid} Requires Auth

Single contract by PIID. Returns the latest transaction (the contract block, 26 lean fields by default) plus a transaction_rollup sibling that aggregates obligations, ceiling, distinct recipients, and action-date bounds across every transaction under that PIID. Pro responses also include a subaward_rollup sibling.

Parameters

  • piid (path) — Procurement Instrument Identifier, upper-cased server-side.
  • fieldslean / expanded / full / comma list, applies to the contract block. transaction_rollup is fixed shape.

Response

{ "contract": { /* 26 lean fields, latest transaction */ }, "transaction_rollup": { "transaction_count": 52, "total_obligated": 162360657.45, "max_current_total_value": 392556695.66, "max_potential_total_value": 504348015.85, "latest_current_total_value": 392556695.66, /* alias for max_current_total_value */ "latest_potential_total_value": 504348015.85, /* alias for max_potential_total_value */ "first_action_date": "2024-10-11", "latest_action_date": "2026-02-27", "distinct_recipient_ueis": 1 }, "_sources": ["usaspending_fpds"] }

Reading the numbers

The example above is the live shape for Deloitte's $392M Army task order under PIID W56JSR23F0062. Two customer-actionable signals are in the rollup directly: transaction_count: 52 over a 16-month action-date span says the prime has cycled 52 mods (active, well-managed contract). max_current_total_value and latest_current_total_value agree at $392.56M (current ceiling matches the all-time peak, scope stable). max_potential_total_value at $504.35M agrees with latest_potential_total_value at the same number, but when these two differ in a live response, that is a scope-reduction signal worth chasing in a recompete play (a previous mod set a higher ceiling than the current one). Customers tracking ceilings over time read both pairs and compare.

Errors

  • 404 — no contract with that PIID in the coverage window.
  • 429 — rate limit exceeded.

Subaward enrichment Pro

On Pro keys, the response includes an additional subaward_rollup sibling summarizing the FFATA subaward footprint flowing from this prime contract: sub count, total subcontracted, share of the prime obligation, distinct sub-vendor count, first and last subaward dates, plus up to 5 named top_subs. The PIID join into the subaward layer is dense within the FY2025+ window: roughly 88% of subaward filings match a prime contract row, and 98% of in-scope contracts that have subaward activity see a populated block. _sources extends to ["usaspending_fpds", "usaspending_ffata"] when the block fires. For UEI-keyed reverse lookups across every contract a recipient holds, use /api/v1/companies/{uei}/subawards.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/contracts/W56JSR23F0062"

GET /api/v1/contracts/{piid}/modifications Requires Auth

Full transaction history for one PIID, oldest action first. Every row is a single FPDS action: base award, exercised options, supplemental agreements, partial terminations, administrative cleanup. Use this to reconstruct what happened on a contract over its life.

Not date-windowed. When you ask for one PIID's history, you get the full history we carry, regardless of the search-time coverage window.

Parameters

  • piid (path) — Procurement Instrument Identifier.
  • limit — 1 to 500, default 100. Higher cap than search because modification trails are bounded.
  • offset — pagination offset.
  • fields — same options as search.

Response

Returns piid, data (ASC-ordered by action_date then modification_number), pagination, and _sources. 404 when no transactions exist for the PIID.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/contracts/W56JSR23F0062/modifications?limit=500"

GET /api/v1/grants/{award_key} Requires Auth

Single grant award by assistance_award_unique_key (the unambiguous award id that also appears in the USAspending permalink, e.g. ASST_NON_4683DRCAP00000001_070). Returns the latest transaction (the grant block, lean fields by default) plus a transaction_rollup sibling aggregating obligations, outlays, program count, and action-date bounds across every transaction under that award. Use the award key, not the FAIN, since a FAIN can recur across recipients and years.

Response

{ "grant": { /* latest transaction, lean fields */ }, "transaction_rollup": { "transaction_count": 1409, "total_obligated": 312276899.65, "award_total_obligated_amount": 403698397.09, "award_total_outlayed": 264256562.26, "first_action_date": "2024-10-04", "latest_action_date": "2026-05-22", "distinct_programs": 1 }, "_sources": ["usaspending_fabs"] }

Reading the numbers

The example is the live shape for a California FEMA disaster-recovery grant (CFDA 97.036). transaction_rollup.total_obligated ($312.3M) is the sum of federal_action_obligation across this award's 1,409 transactions in our coverage window; award_total_obligated_amount ($403.7M) is the award's lifetime cumulative carried verbatim from USAspending, so the roughly $91M gap is obligation that predates our FY2025+ coverage; award_total_outlayed ($264.3M) is money actually disbursed. To answer "how much has this grant obligated to date," read the award figure; to answer "how much landed in my window," read the rollup sum.

Errors

  • 404 — no award with that key.
  • 429 — rate limit exceeded.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/grants/ASST_NON_4683DRCAP00000001_070"

GET /api/v1/grants/{award_key}/modifications Requires Auth

Full transaction history for one award, oldest action first. Every row is a single assistance action: the base obligation, added funding, de-obligations, administrative corrections. Not date-windowed, when you ask for one award's history you get the full history we carry.

Parameters

  • award_key (path) — the assistance_award_unique_key.
  • limit — 1 to 500, default 100. Higher cap than search because one award can carry hundreds of modifications.
  • offset — pagination offset.
  • fields — same options as search.

Response

Returns award_key, data (ASC by action_date then modification_number), pagination, and _sources. 404 when no transactions exist for the award.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/grants/ASST_NON_4683DRCAP00000001_070/modifications?limit=500"

GET /api/v1/companies/{uei}/grants Requires Auth

Every grant a recipient received, by UEI, plus a summary and its top programs. The reverse lookup over /grants/search for one recipient.

Response

Returns uei, a summary (total obligated, distinct awards, distinct programs, distinct agencies, first/last action date, and a top_programs CFDA breakdown ranked by dollars), then the standard data / pagination / filters_applied / window / _sources. summary.total_obligated sums federal_action_obligation, so a program whose only in-window action was a de-obligation shows a negative total and the sum is the net.

Parameters

  • uei (path) — recipient UEI.
  • date_from / date_to, sort_by, sort_order, limit, offset, fields — same as search.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/RFKGNHR7ZH37/grants?limit=50"

GET /api/v1/grant-opportunities/{opportunity_id} Requires Auth

The full notice by opportunity_id: title, agency, eligibility, award range, funding instrument, dates, the agency point of contact, the description, and the CFDA program(s). On Pro keys the response also carries a program_intel block inline (the same data as /grant-programs/{cfda}), so one call tells you both what the notice is and whether the program is worth pursuing; on non-Pro keys that block is a short {"locked": true} stub with no award data.

Response

A flat opportunity object plus the program_intel sibling and _sources (["grants_gov"], or ["grants_gov","usaspending_fabs"] when the program intel joins award history). 404 on an unknown id; not date-windowed.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/grant-opportunities/357328"

GET /api/v1/grant-programs/{cfda} Pro

Program competitive intelligence: for a CFDA program, who has historically won it, whether it is a genuine open competition or wired to a few incumbents, and the realistic award size, joined from the federal grant-award record on the same program number. This answers the question a notice never does, "is this worth a proposal?", before you write one. Pair it with /grant-opportunities/search?cfda=... to see what is open under the program now.

Response

Returns cfda, a program_intel block, the open_opportunity_count currently open under the program, and _sources (["grants_gov","usaspending_fabs"]). Inside program_intel: distinct_awards, distinct_recipients, total_obligated, median_award (the realistic ask when a notice omits the ceiling), top_recipient_share and top5_recipient_share (the single / top-five recipients' share of gross obligated dollars, bounded 0-1; near 0 = open field, near 1 = wired to incumbents), and incumbents (top recipients by dollars). A program with no award history returns the block with nulls and has_award_history: false.

{ "cfda": "93.213", "program_intel": { "distinct_awards": 502, "distinct_recipients": 191, "total_obligated": 147267122.91, "median_award": 405588.0, "top_recipient_share": 0.0648, /* top recipient holds ~6.5% — an open field */ "top5_recipient_share": 0.2013, "incumbents": [ { "recipient_name": "UNIVERSITY OF CALIFORNIA, SAN DIEGO", "awards": 14, "total_obligated": 10005309.0 }, { "recipient_name": "YALE UNIV", "awards": 15, "total_obligated": 6533364.0 } ] }, "open_opportunity_count": 70, "_sources": ["grants_gov", "usaspending_fabs"] }

Errors

  • 402 — Pro plan required.
  • 422cfda is not a valid Assistance Listing number (format NN.NNN).

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/grant-programs/93.213"

GET /api/v1/awards/{award_number} Requires Auth

Fetch a single award by its contract / award number (also called the PIID). Returns the most recent record when more than one row shares that number.

Path is the contract number, not a notice_id. Award numbers are free-form government contract numbers like N00019-21-G-0006, 1240LT22P0040, or SPE7M326P0980. URL-encode the value if it contains slashes or spaces. This is the opposite of /opportunities/{notice_id}, which requires a 32-character hex string. There is no format validation on this path: any string is accepted and simply 404s if it matches no award.

Parameters

  • award_number (path) — free-form contract / PIID. URL-encode slashes or spaces.

No query parameters are accepted; ?fields= projection is not available on this endpoint.

Response

The record is wrapped under award and contains the same 16 fields as a /awards/search result.

{ "award": { "award_number": "SPE7M326P0980", "awardee_name": "BOEING DISTRIBUTION SERVICES X, INC.", "awardee_uei": "X3K6MA9ZLTW6", "award_amount": 57162.78, "award_date": "2026-05-05", "agency": "DEPT OF DEFENSE.DEFENSE LOGISTICS AGENCY...", "naics": ["332996"], "solicitation_number": "SPE7M325T5343", "title": "47--TEE,TUBE", "set_aside_type": null, "awardee_cage_code": "2N935", "awardee_city": null, "awardee_state": null, "contact_name": "William Cain", "contact_email": "[email protected]", "notice_id": "16a371c902134176812010635c459345" } }

Same data caveats as /awards/search (corrupted dates, amount sentinels, address-laden names, null UEIs). For the full ~59-field record (place of performance, full contacts, links, description, archive dates), take the notice_id from this response and call /api/v1/opportunities/{notice_id}.

Why "most recent if multiple"

One award_number can map to more than one stored row. An award gets modifications over its life, and modified awards sometimes appear as separate rows with the same contract number. This endpoint resolves the ambiguity by returning the latest by award_date (ORDER BY award_date DESC NULLS LAST LIMIT 1). To see every version, list the company's awards via /companies/{uei}/awards, or search by the parent solicitation on opportunities search.

Errors

  • 404 — no award with that number. Body: {"detail": "Award not found: {award_number}"}.
  • 429 — rate limit exceeded.
  • 500 — unexpected. Body: {"detail": "Failed to retrieve award"} (sanitized).

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/awards/SPE7M326P0980"

Scope reminder: like all award endpoints, this is SAM Award Notice data, not the full FPDS contract history. See the /awards/search scope note.

Subaward enrichment

For subaward context on a federal prime contract, use the /api/v1/contracts/{piid} endpoint. The PIID-based join through the prime contract layer covers roughly 88% of FY2025+ subaward activity, versus under 1% for the SAM Award Notice slice this endpoint serves.

GET /api/v1/entities/{uei} Requires Auth

Single SAM-registered entity by UEI. Returns the registration block (status, expiration, dates) and the entity block (legal name, address, NAICS, PSC, business-type certifications, CAGE codes). Available on Developer tier; Pro subscribers can use /api/v1/companies/{uei} for the same data merged with award history.

Parameters

  • uei (path) - 12-character SAM Unique Entity Identifier. Normalized: lowercase, mixed case, and surrounding whitespace are accepted.

Response

{ "uei": "XX2WFHJEFB45", "registration": { "status": "A", "active": true, "registration_date": "2001-08-16", "activation_date": "2026-01-21", "expiration_date": "2027-01-19", "expiring_soon": false, "source_extract_date": "2026-04-05" }, "entity": { "legal_business_name": "KAMPI COMPONENTS CO INC", "dba_name": null, "entity_structure_code": "2L", "entity_url": "www.kampi.com", "physical_address": { "street1": "...", "city": "FAIRLESS HILLS", "state": "PA", "zip": "19030", "country": "USA" }, "primary_naics": "423990", "naics_codes": ["423990Y", "332710Y", "..."], "psc_codes": ["...", "..."], "business_types": ["2X", "XS"], "business_types_labels": [ "For Profit Organization", "Subchapter S Corporation" ], "cage_codes": ["7Z016"] } }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/entities/XX2WFHJEFB45"

Errors: 401 (no auth), 404 (UEI not in current SAM data), 429 (rate limit).

Not every valid-shape UEI is in the registry. A 12-character UEI passing format validation can still 404 here. Reasons include: the entity has expired its SAM registration and dropped out of the public extract; the UEI was issued for a registration that never completed; it's a non-public or excluded entity. We carry ~873K firms (the public V2 monthly snapshot). Expect a non-trivial 404 rate when batch-looking-up UEIs from external lists. If you need only firms currently active in SAM, append ?active_only=true to /api/v1/entities/search instead.

Use cases: compliance checks before contract signing (registration.active + registration.expiration_date), CRM enrichment, vendor-onboarding form auto-fill, set-aside qualification preview (entity.business_types_labels).

Pro enrichment fields Pro

When the caller has Pro, two families of enrichment fields are added inline alongside the SAM data. Fields are omitted entirely on non-Pro keys; existing Developer-tier integrations see exactly the SAM record above.

FFATA subaward income — what this UEI received as a sub-vendor:

  • sub_revenue_total — sum of FFATA dollars received as a sub-vendor within current subaward coverage (FY2025 + FY2026 today; window scales as backfill lands).
  • top_paying_primes — up to 10 {uei, name, total, subaward_count} objects, sorted desc by total.

FPDS prime contract activity — what this UEI has been awarded directly as a prime:

  • fpds_obligated_total — sum of federal action obligation across every FPDS transaction for this UEI in the FY2025-onward window (since 2024-10-01).
  • fpds_distinct_contracts / fpds_transaction_count — count of distinct contracts and total transactions (base awards + every modification).
  • fpds_first_action_date / fpds_latest_action_date — ISO date bounds.

Field shapes are documented once at the top of the Endpoints section under Pro enrichment fields (universal pattern).

Worked example (Pro response, both enrichment families present)

Live GET /api/v1/entities/CKV2L9GZKJK3 excerpt (verified 2026-06-01):

{ "uei": "CKV2L9GZKJK3", "registration": { "..." }, "entity": { "..." }, "sub_revenue_total": 126453199.25, "top_paying_primes": [ {"uei": "SMNWM6HN79X5", "name": "GENERAL DYNAMICS INFORMATION TECHNOLOGY, INC.", "total": 26501723.69, "subaward_count": 1}, {"uei": "XDDKMXTVJSN8", "name": "COGNOSANTE MVH LLC", "total": 21702000.00, "subaward_count": 1}, "...up to 10 entries" ], "fpds_obligated_total": 4179687234.11, "fpds_distinct_contracts": 908, "fpds_transaction_count": 2808, "fpds_first_action_date": "2024-10-01", "fpds_latest_action_date": "2026-05-29", "_sources": ["sam_entities", "usaspending_ffata", "usaspending_fpds"] }

For per-record drill-down, use the underlying surfaces: /api/v1/contracts/search?uei=… for every FPDS transaction; /api/v1/companies/{uei}/prime-relationships for every subaward received. The combined prime + sub revenue picture with prime_revenue_share lives on /api/v1/companies/{uei} (also Pro). The bidirectional vendor-risk view (as_prime + as_sub + contract_exposure) lives on /api/v1/vendor-risk/{uei}.

GET /api/v1/entities/by-cage/{cage_code} Requires Auth

Same record as /api/v1/entities/{uei}, keyed by 5-character CAGE code instead of UEI. Adds a queried_cage field at the top of the response so callers can confirm the match. Useful for legacy DoD systems that key on CAGE.

Parameters

  • cage_code (path) - 5-character DLA-issued CAGE code. Normalized: lowercase and surrounding whitespace are accepted and converted to uppercase.

Response

{ "queried_cage": "53YC5", "uei": "C111ATT311C8", "registration": { "status": "A", "active": true, "...": "..." }, "entity": { "legal_business_name": "K & K CONSTRUCTION SUPPLY INC", "...": "..." } }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/entities/by-cage/53YC5"

Errors: 401, 404 (CAGE not found), 429.

Note: Most entities have one CAGE code. Some large organizations (USPS, large banks) have many - up to 80. The entity.cage_codes array in the response lists all CAGE codes registered to the resolved UEI.

Pro enrichment fields Pro. When the caller has Pro, the response carries the same enrichment families as /entities/{uei} — FFATA subaward income (sub_revenue_total, top_paying_primes) and FPDS prime contract activity (fpds_obligated_total, fpds_distinct_contracts, fpds_transaction_count, fpds_first_action_date, fpds_latest_action_date) — computed against the UEI the CAGE resolves to. See the /entities/{uei} section for worked example and field documentation.

GET /api/v1/entities/expiring Requires Auth Pro

Active SAM registrations expiring within within_days days. Sorted by urgency (most urgent first). Past-due registrations are excluded (their status is 'E'). The renewal-radar use case: compliance dashboards, renewal-reminder SaaS, BD pipelines flagging at-risk leads.

Parameters

  • within_days - 1-365, default 60. Days from today to look ahead.
  • state - 2-character US state code (optional).
  • naics - 6-digit NAICS code, no suffix (optional). Matches both primary_naics and naics_codes[].
  • limit - 1-500, default 50.
  • offset - Pagination offset (default 0).

Response

The row array is returned under BOTH results AND data (identical contents). Either key is safe to read.

{ "query": {"within_days": 30, "state": "DC", "naics": null}, "pagination": {"limit": 50, "offset": 0, "total": 391, "has_next": true}, "results": [ { "uei": "...", "legal_business_name": "ACME CONSULTING LLC", "registration_status": "A", "registration_expiration_date": "2026-05-12", "days_until_expiration": 12, "primary_naics": "541611", "business_types": ["27", "2X", "8W"], "business_types_labels": [ "Self Certified Small Disadvantaged Business", "For Profit Organization", "Women-Owned Small Business (WOSB)" ], "physical_city": "WASHINGTON", "physical_state": "DC", "physical_country": "USA" } ], "data": [ "...same rows as results above..." ] }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/entities/expiring?within_days=30&state=DC&limit=20"

Errors: 401, 402 (not on Pro), 422 (within_days out of 1-365), 429.

Note: days_until_expiration is computed at request time. Cached responses can drift. Sort is fixed at urgency-ascending.

GET /api/v1/federal-hierarchy Requires Auth

Search and filter the SAM Federal Hierarchy: the canonical tree of federal departments, agencies, offices, bureaus, and commands (~907 organizations). Use it to resolve and normalize agency identity, walk the org tree, or join external datasets on Treasury cgac codes. Free on any plan; this is reference data.

Parameters

  • type - Org type, uppercased: DEPARTMENT, AGENCY, OFFICE, BUREAU, MAJOR COMMAND, SUB COMMAND, DIVISION.
  • cgac - Treasury Common Government-wide Accounting Code, e.g. 097 (DoD), 017 (Navy). Many orgs share a department's cgac.
  • parent_id - Filter to immediate children of this organization_id.
  • hierarchy_level - Depth; 1 = root department.
  • is_active - Defaults to true. Pass is_active=false to include inactive orgs.
  • search - Case-insensitive match across canonical name, short name, and alternative names (≥ 2 chars).
  • limit - 1-1000, default 50.
  • offset - ≥ 0.

Response (list rows, 9 fields)

{ "data": [ { "organization_id": 300000188, "fh_key": "300000188", "cgac": "017", "canonical_name": "DEPT OF THE NAVY", "short_name": "USN", "type": "AGENCY", "hierarchy_level": 2, "parent_id": 100000000, "is_active": true } ], "pagination": { "limit": 50, "offset": 0, "total": 1, "has_next": false } }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/federal-hierarchy?search=navy&limit=10"

Note: Both /federal-hierarchy and /federal-hierarchy/ work; no redirect. total is exact. Results ordered by hierarchy_level then canonical_name. Reference data; expect rare changes. Key your joins on organization_id (stable integer).

GET /api/v1/federal-hierarchy/{organization_id} Requires Auth

Look up one federal organization by integer ID, returning its full 17-field record plus its parent, immediate children, and full ancestor chain in a single call. Useful for rendering breadcrumbs, expanding tree nodes, or joining a downstream dataset against the org tree without extra round-trips.

Parameters

  • organization_id (path) - Integer org ID. Non-integer returns 422; unknown ID returns 404.

Response

{ "data": { "organization_id": 300000188, "fh_key": "300000188", "cgac": "017", "canonical_name": "DEPT OF THE NAVY", "short_name": "USN", "alternative_names": ["USN", "..."], "type": "AGENCY", "description": "...", "is_active": true, "parent_id": 100000000, "hierarchy_level": 2, "hierarchy_path": [100000000, 300000188], "hierarchy_names": ["DEPT OF DEFENSE", "DEPT OF THE NAVY"], "source_modified_at": "...", "source_indexed_at": "...", "last_seen": "...", "source_version": "..." }, "parent": { /* 9-field list-shape row; null for root departments */ }, "children": [ { /* 9-field rows, immediate children, name-sorted */ } ], "ancestors": [ { /* 9-field rows, root department first */ } ] }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/federal-hierarchy/300000188"

Errors: 401, 404 (unknown ID), 422 (non-integer path), 429.

Note: data is the full 17-field record; parent / children / ancestors are the 9-field list shape. hierarchy_path is the ordered list of ancestor IDs (root first), the same source used to build the inline ancestors array.

GET /api/v1/federal-hierarchy/{organization_id}/children Requires Auth

Return the immediate child organizations of one org (one level down), ordered by name. Use it to expand one node of the tree without pulling the full detail response.

Response

{ "data": [ { "organization_id": 300000188, "fh_key": "300000188", "cgac": "017", "canonical_name": "DEPT OF THE NAVY", "short_name": "USN", "type": "AGENCY", "hierarchy_level": 2, "parent_id": 100000000, "is_active": true } ], "count": 1 }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/federal-hierarchy/100000000/children"

Note: Immediate children only, not the full subtree. A leaf org (or an unknown integer ID) returns { "data": [], "count": 0 } with a 200, not a 404; if you need to confirm the org exists, use the detail endpoint instead.

GET /api/v1/federal-hierarchy/{organization_id}/ancestors Requires Auth

Return the chain of ancestors for one org, ordered root department first down to the immediate parent. Use it to render breadcrumbs ("DEPT OF DEFENSE > DEPT OF THE NAVY > ...") for any organization.

Response

{ "data": [ { "organization_id": 100000000, "canonical_name": "DEPT OF DEFENSE", "short_name": "DOD", "type": "DEPARTMENT", "hierarchy_level": 1, "parent_id": null, "is_active": true, "cgac": "097", "fh_key": "100000000" } ], "count": 1 }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/federal-hierarchy/300000188/ancestors"

Errors: 401, 404 (unknown org), 422 (non-integer path), 429.

Note: Order is root → immediate parent. Root organizations return { "data": [], "count": 0 }. Unknown IDs return 404 (this endpoint checks existence first, unlike children which returns an empty list).

GET /api/v1/companies/{uei} Requires Auth Pro

Combined SAM registration + award profile for one UEI. Returns 200 if either the SAM entity registry or our award data has the UEI; returns 404 only when both miss. Award fields populate when the UEI has at least one Award Notice; otherwise they zero out and the registration / entity blocks still populate.

Parameters

  • uei (path) - 12-character SAM Unique Entity Identifier. Normalized: lowercase, mixed case, and surrounding whitespace are accepted.

Response

{ "uei": "XX2WFHJEFB45", "name": "KAMPI COMPONENTS CO INC", "primary_city": "FAIRLESS HILLS", "primary_state": "PA", "total_awards": 842, "total_value": 223500000.0, "avg_value": 265440.0, "first_award_date": "2024-11-04", "last_award_date": "2026-04-18", "awards_last_90d": 47, "awards_last_12mo": 312, "top_naics": [ {"naics": "332710", "awards": 198, "value": 52400000.0} ], "top_agencies": [ {"agency": "DEPT OF DEFENSE", "awards": 601, "value": 168200000.0} ], "top_agency_share_pct": 100.0, "set_aside_distribution": [ {"set_aside": "UNRESTRICTED", "awards": 830, "value": 218000000.0, "pct_of_awards": 98.6}, {"set_aside": "SBA", "awards": 6, "value": 4500000.0, "pct_of_awards": 0.7}, {"set_aside": "NONE", "awards": 6, "value": 13000000.0, "pct_of_awards": 0.7} ], "registration": { "status": "A", "active": true, "registration_date": "2001-08-16", "activation_date": "2026-01-21", "expiration_date": "2027-01-19", "expiring_soon": false, "source_extract_date": "2026-04-05" }, "entity": { "legal_business_name": "KAMPI COMPONENTS CO INC", "dba_name": null, "entity_structure_code": "2L", "entity_url": "www.kampi.com", "physical_address": { "street1": "...", "city": "FAIRLESS HILLS", "state": "PA", "zip": "19030", "country": "USA" }, "primary_naics": "423990", "naics_codes": ["423990Y", "332710Y", "..."], "psc_codes": ["...", "..."], "business_types": ["2X", "XS"], "business_types_labels": [ "For Profit Organization", "Subchapter S Corporation" ], "cage_codes": ["7Z016"] } }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/XX2WFHJEFB45"

Use cases: compliance check (registration.active + registration.expiration_date), customer-concentration risk (top_agency_share_pct — single-buyer dependents register near 100), set-aside mix (set_aside_distribution — what % of their wins are SDVOSB / WOSB / 8(a)), pipeline health (awards_last_90d vs awards_last_12mo), capability mapping (top_naics, entity.business_types_labels). UEI normalization: lowercase, mixed case, and surrounding whitespace are accepted.

For SAM-data-only lookups without the award merge, see /api/v1/entities/{uei} (Developer tier).

Subaward revenue fields

The profile carries three subaward fields alongside the prime-award totals (within current subaward coverage — FY2025 + FY2026 today). They surface the "true federal revenue" picture that prime-only data misses — for SMBs in particular, sub revenue is often the larger half:

  • sub_revenue_total — total dollars this UEI has received as a sub-vendor (sum of subaward_amount).
  • top_paying_primes — up to 10 primes that have paid this UEI as a sub, sorted by total $. Each entry: {uei, name, total, subaward_count}.
  • prime_revenue_share — float in [0, 1], the share of this UEI's combined federal revenue that came in as prime contracts (vs subs). Computed as fpds_obligated_total / (fpds_obligated_total + sub_revenue_total), using the FPDS prime obligation total (not the SAM-noticed total_value, which undercounts prime work and would read near 0 for FPDS-active firms). null when neither prime nor sub revenue exists; a pure-prime vendor reads 1.0.

Fields are populated for every UEI we have profile data on; sub_revenue_total and top_paying_primes zero out when no subawards exist (prime_revenue_share then reads 1.0 if the UEI has FPDS prime activity, and is null only when it has neither prime nor sub revenue). For the underlying records, use /api/v1/companies/{uei}/prime-relationships.

FPDS prime contract activity fields

The profile also carries FPDS prime contract aggregates from the federal procurement system of record. These cover the recipient's full direct prime contract activity (every base award and modification under their UEI in the plan window), where the SAM Award Notice total_value field above covers only the SAM-noticed slice (~10-30% of obligations). Both views are surfaced so callers can pick the denominator that matches their question.

  • fpds_obligated_total — total federal_action_obligation obligated to this UEI as a prime, summed across every FPDS transaction in the FY2025-onward window (since 2024-10-01).
  • fpds_distinct_contracts — count of distinct contracts (contract_award_unique_key) the recipient holds.
  • fpds_transaction_count — count of every FPDS row: base awards plus every modification.
  • fpds_first_action_date / fpds_latest_action_date — ISO date bounds on the recipient's FPDS activity in the window.
  • fpds_top_naics — array of up to 5 {code, description, transaction_count, value} objects, top NAICS by total $. Each object also includes a deprecated n field with the same value as transaction_count.
  • fpds_top_agencies — array of up to 5 {name, transaction_count, value} objects, top awarding sub-agencies by total $. Each object also includes a deprecated n field.

Worked example (Pro response, FPDS + subaward enrichment)

Calling GET /api/v1/companies/CKV2L9GZKJK3 (Deloitte Consulting LLP) on a Pro key returns the SAM profile + award aggregates documented above, plus enrichment fields. Live response excerpt (verified 2026-06-15):

{ "uei": "CKV2L9GZKJK3", "name": "DELOITTE CONSULTING LLP", "total_awards": 12, "total_value": 1310256763.79, "...": "SAM registration + award profile fields above", "sub_revenue_total": 126453199.25, "top_paying_primes": [ {"uei": "SMNWM6HN79X5", "name": "GENERAL DYNAMICS INFORMATION TECHNOLOGY, INC.", "total": 26501723.69, "subaward_count": 1}, {"uei": "XDDKMXTVJSN8", "name": "COGNOSANTE MVH LLC", "total": 21702000.00, "subaward_count": 1}, "...up to 10 entries" ], "prime_revenue_share": 0.971, "fpds_obligated_total": 4228940959.11, "fpds_distinct_contracts": 914, "fpds_transaction_count": 2856, "fpds_first_action_date": "2024-10-01", "fpds_latest_action_date": "2026-06-12", "fpds_top_naics": [ {"code": "541512", "description": "COMPUTER SYSTEMS DESIGN SERVICES", "transaction_count": 913, "n": 913, "value": 2051661176.18}, {"code": "541611", "description": "ADMINISTRATIVE MANAGEMENT AND GENERAL MANAGEMENT CONSULTING SERVICES", "transaction_count": 1142, "n": 1142, "value": 1138031790.98}, "...up to 5 entries" ], "fpds_top_agencies": [ {"name": "Federal Acquisition Service", "transaction_count": 228, "n": 228, "value": 905220054.81}, {"name": "Department of Veterans Affairs", "transaction_count": 70, "n": 70, "value": 529603511.82}, "...up to 5 entries" ], "_sources": ["sam_entities", "sam_opportunities", "usaspending_ffata", "usaspending_fpds"] }

Notice the magnitude split: total_value (the SAM-noticed slice, only 12 award notices) is ~$1.31B, while fpds_obligated_total (the full prime contract picture across 914 contracts and 2,856 transactions, FY2025 onward) is ~$4.23B. fpds_obligated_total is the authoritative federal-activity number for this recipient.

GET /api/v1/companies/{uei}/awards Requires Auth Pro

Paginated award history for one UEI. Same data as /api/v1/awards/search?uei=X, served under a company-centric URL for convenience.

Parameters

  • limit - Results per page (1-1000, default 50)
  • offset - Pagination offset (default 0, no upper bound)
  • sort - date (default, desc) or amount (desc). Strict: any other value returns 422.
  • sort_by / sort_order - cross-endpoint alias matching awards search: sort_by=award_date|award_amount with sort_order=asc|desc (default desc). Wins over sort when present, and unlike sort it supports ascending order. Invalid sort_by returns 400.
  • fields - Comma-separated response fields. notice_id always included. Example: fields=notice_id,award_amount,award_date,agency. See Response Shaping.

Response

{ "uei": "XX2WFHJEFB45", "pagination": {"limit": 50, "offset": 0, "total": 842, "has_next": true}, "sort": "date", "data": [ { "notice_id": "abc123...", "solicitation_number": "SPE7M525R0012", "title": "Replenishment parts for F-16 landing gear", "award_number": "SPE7M525C0042", "award_date": "2026-03-10", "award_amount": 487200.0, "agency": "DEPT OF DEFENSE", "naics": ["336413"], "psc": ["1680"], "posted_date": "2026-03-11", "response_deadline": null, "performance_state_code": "OK", "performance_city_name": "OKLAHOMA CITY" } ] }

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/XX2WFHJEFB45/awards?limit=100&sort=amount"

GET /api/v1/companies/{uei}/peers Requires Auth Pro

Similar companies ranked by NAICS and agency overlap with the target. Use for competitive mapping, teaming-partner discovery, and BD target lists.

Parameters

  • limit - Results (1-50, default 10)

Response

{ "uei": "XX2WFHJEFB45", "target": {"naics_count": 12, "agency_count": 7}, "count": 10, "peers": [ { "uei": "H11HD5VHGHN3", "name": "LOCKHEED MARTIN CORPORATION", "total_awards": 16, "total_value": 26800000.0, "naics_overlap": 6, "agency_overlap": 1, "similarity_score": 19 } ] }

Ranking

similarity_score = naics_overlap * 3 + agency_overlap. NAICS is weighted 3x higher because same-industry is a direct competition signal; shared customers is a weaker signal. Results sorted by similarity_score desc, then total_awards desc.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/XX2WFHJEFB45/peers?limit=10"

GET /api/v1/subawards/{subaward_sam_report_id} Requires Auth

Single FFATA report by its SAM report ID (a UUID, the natural primary key of the subawards table). Returns the same 29-field row shape as a search result. Not subject to the subaward history window — if you have a report ID, the record returns regardless of age.

Parameters

  • subaward_sam_report_id (path) — the UUID from any row in /subawards/search (the subaward_sam_report_id field on each result). Normalized (strip + upper); accepted in mixed case. No query parameters; ?fields= is not supported on this endpoint.

Response

A flat object with the same 29 fields documented under /subawards/search → Response. No wrapper; no data / pagination / window envelope on single-record lookup.

Errors

  • 401 — missing or invalid API key.
  • 404 — well-formed ID, no such report. Body: {"detail": "Subaward report not found"}.
  • 429 — rate limit exceeded.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/subawards/01b6a2d8-3e02-44f7-9b8e-7b5e4c8a9d23"

Note: The natural-looking subaward_number field (e.g. 1A4PZ-006-WTERX) is not the unique identifier — empirically about 10% of subaward_number values collide across records (modifications, re-issues). The 32-character UUID subaward_sam_report_id is the only stable per-record key.

GET /api/v1/companies/{uei}/subawards Requires Auth

Prime-side reverse lookup: "what subs has this UEI paid?" Indexed on prime_awardee_uei. Same row shape as /subawards/search plus a top-level summary block aggregating across the filtered result set.

Parameters

  • uei (path) — 12-character UEI of the prime. Normalized.
  • sort_bysubaward_action_date (default), subaward_amount, or subaward_sam_report_last_modified_date.
  • sort_orderasc or desc (default desc).
  • date_from / date_toYYYY-MM-DD. date_from is clamped to the effective subaward floor (2024-10-01 today).
  • limit — 1-250 (default 50). 422 on out-of-range. Tighter cap than awards/opportunities because subaward rows are wider.
  • offset — ≥ 0 (default 0).
  • fields — Comma-separated projection (subaward row columns). subaward_sam_report_id always included.

Response

{ "uei": "H11HD5VHGHN3", "summary": { "prime_name": "LOCKHEED MARTIN CORPORATION", "total_subaward_amount": 48200000.00, "distinct_sub_vendors": 127, "distinct_prime_contracts": 14, "first_subaward_date": "2021-08-12", "last_subaward_date": "2026-05-28" }, "data": [ /* same 29-field rows as /subawards/search */ ], "pagination": { "limit": 50, "offset": 0, "total": 1247, "total_is_estimate": false, "has_next": true }, "filters_applied": { "sort_by": "subaward_action_date", "sort_order": "desc", "date_from": "2024-10-01" }, "window": { "plan_window_days": 1825, "earliest_searchable": "2024-10-01", "clamped": false } }

UEIs with no subaward records return 200 with an empty data array and a zero summary, not a 404 — same pattern as /companies/{uei}/awards.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/H11HD5VHGHN3/subawards?sort_by=subaward_amount&limit=50"

GET /api/v1/companies/{uei}/prime-relationships Requires Auth

Sub-side reverse lookup: "what primes have paid this UEI as a sub-vendor?" Indexed on subawardee_uei. Same row shape as /subawards/search plus a summary block with a top-10 paying-primes leaderboard.

Parameters

  • uei (path) — 12-character UEI of the sub-vendor. Normalized.
  • sort_by / sort_order — same as /companies/{uei}/subawards.
  • date_from / date_toYYYY-MM-DD. date_from window-clamped.
  • limit — 1-250 (default 50). Same cap as the prime-side sibling.
  • offset — ≥ 0 (default 0).
  • fields — Comma-separated projection.

Response

{ "uei": "XX2WFHJEFB45", "summary": { "sub_name": "KAMPI COMPONENTS CO INC", "total_received": 6420000.00, "distinct_primes": 7, "top_primes": [ {"prime_uei": "H11HD5VHGHN3", "prime_name": "LOCKHEED MARTIN CORPORATION", "subaward_count": 12, "total": 3200000.00} ], "first_subaward_date": "2021-08-12", "last_subaward_date": "2026-05-12" }, "data": [ /* same 29-field rows */ ], "pagination": { "limit": 50, "offset": 0, "total": 31, "total_is_estimate": false, "has_next": false }, "filters_applied": { "sort_by": "subaward_action_date", "sort_order": "desc", "date_from": "2024-10-01" }, "window": { "plan_window_days": 1825, "earliest_searchable": "2024-10-01", "clamped": false } }

For most SMBs, this is the better revenue picture than /companies/{uei}/awards — sub revenue is often the larger half of federal income, and prime-only data systematically undercounts it.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/companies/XX2WFHJEFB45/prime-relationships?limit=50"

Errors (both reverse-lookup endpoints): 401 (auth), 422 (bad UEI / out-of-range param), 429 (rate limit). 404 is not returned on "no records" — empty data with zero summary is.

GET /api/v1/vendor-risk/{uei} Requires Auth Pro

Returns every red-flag signal the public federal record contains about one vendor UEI, plus a mechanical triage verdict so compliance teams can sort without parsing the full tree. Seven signals, all derived from SAM data: exclusion status, address cluster, name-variant cluster, individual exclusions at the same address, coordinated-wave membership, last-award-to-exclusion gap, and dual-CAGE detection.

Parameters

  • uei (path) - 12-character SAM UEI. Case-insensitive; non-alphanumeric input returns 422.

Response

{ "response_version": "1", "uei": "N2EDPB1SMN55", "entity_name": "Bella Mia Donna LLC", "address": {"line1": "111 SW 1 ST", "city": "Pompano Beach", "state": "FL"}, "address_source": "exclusions", "computed_at": "2026-04-24T16:22:10Z", "triage": { "category": "high", "label": "High risk", "reasons": [ "entity is currently on the federal exclusion list: Ineligible (Proceedings Pending) by DLA (create_date 2025-11-21)", "debarred alongside 24 entities by the same agency on 2025-11-21 (coordinated enforcement wave)" ] }, "signals": { "exclusion_status": { "excluded": true, "exclusion_type": "Ineligible (Proceedings Pending)", "excluding_agency_code": "DLA", "create_date": "2025-11-21", "_more_keys": "see API reference for full shape" }, "address_cluster": { "total_at_address": 24, "excluded_at_address": 24, "address_type": "normal", "others": [{"uei": "...", "entity_name": "...", "create_date": "2025-11-21"}] }, "name_variant_cluster": { "match_count": 1, "excluded_match_count": 1, "variants": [] }, "individual_exclusions_at_address": { "count": 5, "individuals": [{"name": "...", "create_date": "2025-11-21"}] }, "wave_membership": { "in_coordinated_wave": true, "wave_date": "2025-11-21", "wave_size": 24, "wave_members": [{"uei": "...", "entity_name": "..."}] }, "timing_gap": { "last_sam_award_date": "2025-11-17", "exclusion_create_date": "2025-11-21", "days_between": 4 }, "dual_cage": { "has_dual_cage": false, "cage_codes": ["8SGL5"] } }, "subaward_exposure": { "as_prime": { "total_paid_to_subs": 0, "distinct_sub_vendors": 0, "subaward_count": 0, "first_subaward_date": null, "last_subaward_date": null, "top_subs": [] }, "as_sub": { "total_received_from_primes": 482000.00, "distinct_primes": 3, "subaward_count": 7, "first_subaward_date": "2024-06-04", "last_subaward_date": "2025-11-17", "top_primes": [{"uei": "H11HD5VHGHN3", "name": "LOCKHEED MARTIN CORP", "total": 320000.00, "subaward_count": 4}] }, "window_days": 1825 }, "disclaimers": ["All signals derive from the public federal record."] }

Triage categories

  • high - the entity itself is actively excluded.
  • elevated - not excluded, but ≥2 co-tenants at the same address are excluded, or a name-variant entity is excluded, or ≥1 individual exclusion shares the address.
  • needs_review - weaker signals only (e.g. multiple CAGE codes).
  • clean - no signals returned.
  • unknown - UEI not present in SAM exclusions or opportunities data at all.

Triage rules are deliberately simple and visible in triage.reasons so your compliance team can override by reading the signals themselves. Keys in the response are stable: values may be null, empty arrays, or false, but the shape never changes. Safe to parse with response_version branching if you need to stay compatible as we add signals.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/vendor-risk/N2EDPB1SMN55"

Notes: Returns 404 only when the UEI has no record in any of our SAM tables (exclusions, opportunities, or the ~873K-firm entity registry). SAM-registered vendors with no exclusion and no recent award still return 200 with a clean triage verdict. For non-US addresses where the state field is empty (Singapore, Hong Kong, Ankara, etc.), clustering falls back to line1+city. The address_source field tells you which table the address came from: exclusions, opportunities, entities, or unknown when no address was found. See /vendor-risk-api for signal definitions, the triage rules, and worked examples from real federal enforcement actions.

Enrichment blocks Pro

Two top-level blocks sit alongside signals and triage (NOT nested inside them) on Pro responses. Both are descriptive — they don't change the triage verdict — but compliance teams read them alongside the seven signals to size the supply-chain footprint around a single UEI.

subaward_exposure block — FFATA subaward dollar flow on both sides:

  • as_prime — what this UEI has paid out to sub-vendors: {total_paid_to_subs, distinct_sub_vendors, subaward_count, first_subaward_date, last_subaward_date, top_subs: [{uei, name, total, subaward_count}, ... up to 5]}.
  • as_sub — what this UEI has received from primes: {total_received_from_primes, distinct_primes, subaward_count, first_subaward_date, last_subaward_date, top_primes: [{uei, name, total, subaward_count}, ... up to 5]}.
  • window_days — the lookback (1825 today).

Both sub-blocks always populate (zero shape when no records); subaward_exposure itself is omitted only when this UEI has no record in any of our tables (i.e. when the whole response would be a 404 candidate).

contract_exposure block — FPDS prime contract activity (parallel to subaward_exposure):

  • fpds_obligated_total, fpds_distinct_contracts, fpds_transaction_count — magnitudes.
  • fpds_first_action_date, fpds_latest_action_date — activity bounds in the window.
  • top_agencies — array of up to 5 {name, value, transaction_count} objects, awarding sub-agencies ranked by total $.
  • window_days — the lookback (1825 today).

The block returns the zero shape when this UEI has no FPDS prime contract activity in the window.

Worked example (Pro response, actively excluded UEI with mixed compliance picture)

UEI C1ZLDAS18FJ3 (ATI Government Solutions) was placed on the SBA "Ineligible (Proceedings Pending)" list on 2025-10-21 alongside its 3 named principals, debarred as individuals the same day (a firm-plus-officers coordinated action; wave_size 4). The contract_exposure block surfaces activity from 2024-10-01 onward (FY2025+): $95.34M in net obligations across 33 contracts, with the IRS as the largest awarding sub-agency at $82.55M. The subaward_exposure.as_prime shows this UEI paid out $55.44M to 10 distinct sub-vendors over the same window. For the recency-focused "is the problem ongoing?" view, see the parallel example on /exclusions/{uei_sam}, where the last-365-days window shows -$9.31M net (deobligations exceeding new obligations recently). Triage scores on the seven signals above; the exposure blocks add descriptive supply-chain context.

{ "uei": "C1ZLDAS18FJ3", "entity_name": "ATI Government Solutions", "signals": { "..." }, "triage": { "category": "high", "label": "High risk", "reasons": [ "entity is currently on the federal exclusion list: Ineligible (Proceedings Pending) by SBA (create_date 2025-10-21)", "debarred alongside 4 entities by the same agency on 2025-10-21 (coordinated enforcement wave)" ] }, "subaward_exposure": { "as_prime": { "total_paid_to_subs": 55440463.08, "distinct_sub_vendors": 10, "subaward_count": 16, "first_subaward_date": "2024-10-01", "last_subaward_date": "2025-10-22", "top_subs": [ {"uei": "W6G5J8F8ETH7", "name": "VIBRANTECH SOLUTIONS INC.", "total": 13591324.20, "subaward_count": 5}, {"uei": "NHPJUKUF8WL9", "name": "BASECAMP CONSULTING AND SOLUTIONS LLC", "total": 13049945.55, "subaward_count": 1}, "...up to 5 entries" ] }, "as_sub": { "total_received_from_primes": 0.0, "distinct_primes": 0, "subaward_count": 0, "first_subaward_date": null, "last_subaward_date": null, "top_primes": [] }, "window_days": 1825 }, "contract_exposure": { "fpds_obligated_total": 95259892.19, "fpds_distinct_contracts": 33, "fpds_transaction_count": 122, "fpds_first_action_date": "2024-10-23", "fpds_latest_action_date": "2026-05-18", "top_agencies": [ {"name": "Internal Revenue Service", "value": 82545810.72, "transaction_count": 67}, {"name": "Office of the Assistant Secretary for Administration and Management", "value": 5863769.27, "transaction_count": 13}, "...up to 5 entries" ], "window_days": 1825 }, "_sources": ["derived_vendor_risk", "sam_exclusions", "sam_entities", "sam_opportunities", "usaspending_ffata", "usaspending_fpds"] }

Field shapes for both blocks are also documented at the top of the Endpoints section under Pro enrichment fields (universal pattern). Live response verified 2026-06-01.

POST /api/v1/vendor-risk/bulk Requires Auth Pro

Bulk vendor screening. Upload up to 100 UEIs in one call and get back a risk report for each. Designed for quarterly vendor reviews where you want to screen your whole subcontractor list in one pass, not one-at-a-time.

Request body

{ "ueis": ["N2EDPB1SMN55", "C1BVCA2N9YB4", "SH2ER4W6LH13"] }
  • ueis - array of 1-100 UEIs. Duplicates deduplicated server-side. Max 100 per request (422 if exceeded).

Response

{ "requested_count": 3, "returned_count": 3, "results": [ { "uei": "N2EDPB1SMN55", "status": "ok", "report": {"_shape": "same as GET /api/v1/vendor-risk/{uei}"} }, { "uei": "C1BVCA2N9YB4", "status": "ok", "report": {"_shape": "same as GET /api/v1/vendor-risk/{uei}"} }, { "uei": "SH2ER4W6LH13", "status": "not_found", "report": null }, { "uei": "bad-uei", "status": "invalid", "report": null } ] }

Per-row status is one of: ok (report present), not_found (UEI in none of our sources), or invalid (not 12 alphanumeric characters). A bad or stale UEI is surfaced per-row, never failing the whole request, since real vendor lists always contain some. returned_count reflects the post-dedup unique count, so it can be lower than requested_count. An empty ueis list or more than 100 entries returns 422. Each UEI counts against your hourly quota (a 100-UEI bulk = 100 quota hits). Expected latency: ~80ms per UEI on warm paths.

Example

curl -X POST -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ueis":["N2EDPB1SMN55","C1BVCA2N9YB4","SH2ER4W6LH13"]}' \ https://govconapi.com/api/v1/vendor-risk/bulk

Use cases: quarterly subcontractor review ("which of my 200 vendors now need attention?"), pre-contract screening, M&A target due diligence on a portfolio company's vendor list.

GET /api/v1/contacts/lookup Requires Auth Pro

Resolver lookup for federal contracting officers: pass a name (and optionally an agency), get back up to 5 matching contacts with email, phone, agency, department, and location. Designed for BD / outreach / intelligence platforms that already know who they are looking for and need the contact info to act on it.

This is a resolver, not a directory. There is no listing endpoint, no pagination, and no filter-only browse. To explore the directory (search by NAICS, view award history, see colleague graphs, AI dossiers), use the GovCon Contacts web app at contacts.govconapi.com (included with Pro).

Parameters

One of name or email is required.

  • name - Name or partial name. Min 2 characters. Substring match, case-insensitive. Returns up to 5 results.
  • agency (optional, with name) - Agency or department substring for disambiguation (e.g. Air Force, DLA, Veterans). Matches both current_agency and current_department.
  • state (optional, with name) - 2-letter state/territory code (e.g. CA, DC, VA) for geographic disambiguation.
  • email - Exact email address for reverse lookup. Returns one record. Useful for CRM enrichment when you have the email on file and want the current name/agency/phone.

Response

{ "data": [ { "name": "Joseph Nemedy", "current_agency": "DEPT OF THE AIR FORCE", "current_department": "DEPT OF DEFENSE", "current_location_city": "Shaw Afb", "current_location_state": "SC", "email": "[email protected]", "phones": ["(803) 895-5354"], "last_seen": "2026-05-09", "slug": "a1b2c3d4e5f6" } ], "filters_applied": {"name": "Joseph", "agency": "Air Force"}, "result_count": 1, "note": "Resolver lookup. Generic search terms like 'Coordinator' or 'Specialist' may return shared role inboxes rather than individual people; use `agency` and/or `state` to narrow. For directory browse / awards history / colleague graphs, use https://contacts.govconapi.com." }

Examples

# Lookup by name curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/contacts/lookup?name=Joseph" # Disambiguate with agency + state curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/contacts/lookup?name=Smith&agency=Air%20Force&state=GA" # Reverse lookup by email (CRM enrichment) curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/contacts/lookup?email=joseph.nemedy.1%40us.af.mil"

Caching: Successful responses include Cache-Control: public, max-age=3600. Data refreshes nightly so an hour of client-side caching is safe and saves your quota on repeat queries.

Heads-up on generic search terms: "Coordinator", "Specialist", and similar return shared role inboxes (e.g. "BAA Coordinator" appears in 23 agencies as a shared queue address) rather than individual people. Use agency and/or state to narrow, or look up by email when you need exactly one record.

Errors: 401 (no auth), 402 (Developer key, Pro required), 404 (no matches), 422 (neither name nor email provided, or name too short), 429 (rate limit), 503 (contacts data store transiently unavailable; retry).

Use cases: a BD platform sees a contracting officer mentioned in a news feed or post and resolves their email/phone for outreach; a CRM enriches contact records with agency and location; an intelligence system maps names from public discussions to verified federal buyer identities.

Coverage: ~23,000 federal contracting officers and contracting specialists across 124 agencies. 100% have an email; ~61% have phone numbers. Refreshed nightly from federal contract notices.

GET /api/v1/opportunities/delta Requires Auth

Get all opportunities that were added or updated since a given timestamp. Use this to keep your local database in sync without re-downloading the entire dataset.

How It Works

  • Pass a timestamp, get changes. Returns every record that was added, updated, or backfilled since the timestamp you provide. This includes newly posted notices, updated deadlines, and historical records that were filled in during data reconciliation.
  • Same 59 fields as the search endpoint. Each record is a complete opportunity, not a diff.
  • Data updates daily around 2:00 AM UTC. A typical day has 1,000 to 2,000 changed records. Calling this once a day keeps you fully in sync.
  • Save the server_time from the response and use it as since on your next sync. This guarantees no gaps.

Parameters

  • since (required) - ISO 8601 timestamp (e.g. 2026-04-05T06:00:00Z). Clamped to the last 60 days: an older value still returns 200, and the sync block reports since (the effective value used), since_requested, and clamp_reason. Delta is a sync tool, not a backfill; for older data use search with date filters.
  • limit - Results per page (Free: max 50, Developer: max 1000, default 1000). Over your plan cap returns 403 (not a silent cap).
  • offset - Pagination offset

Only since, limit, and offset are accepted. Any other parameter (e.g. naics_multiple) returns 400; delta does not filter by content. Errors: 400 (unknown param, or unparseable since), 401 (no/invalid key), 403 (limit over plan cap), 422 (since missing), 429 (rate limit).

Response

{ "data": [ { "notice_id": "185eefbdab5a4a39aa7b53cb349bf1f0", "title": "Removal of Oil/Water Mixture and Contaminated Sand/Soil/Sludge from Oil Water Separators, Osan AB", "notice_type": "Solicitation", "agency": "DEPT OF DEFENSE.DEPT OF THE ARMY.AMC.ACC.ACC-OO.411TH CSB.0906 AQ CO CONTRACTING BAT", "posted_date": "2026-05-31", "response_deadline": "2026-06-08T04:00:00+00:00", "_additional": "all 59 fields, same as search endpoint" } ], "pagination": { "limit": 100, "offset": 0, "total": 1984, "has_next": true }, "sync": { "since": "2026-05-30T22:00:00Z", "records_changed": 1984, "server_time": "2026-06-02T09:04:49.836940+00:00" } }

The sync.server_time field is the next-cursor. Save it from this response and pass it as since on your next pull. The field is not currently named next_since but it functions as one; treat it as the watermark of "everything up to here has been delivered." If you re-pass your original since instead, you will re-pull the same records you already saw.

What a real delta pull contains. A live pull on 2026-06-02 against since=2026-05-30T22:00:00Z returned 1,984 changed records. Across the first 1,000 sampled, the notice-type mix is Combined Synopsis/Solicitation (32%), Award Notice (25%), Solicitation (21%), Sources Sought (8%), Presolicitation (7%), Special Notice (6%), plus a small tail of Justification notices (1%). The endpoint reports every state change, not just new postings, so a BD pipeline sees the same notice cycle through pre-award stages and the award decision in the same delta stream.

Sync Workflow

# 1. Initial load: pull everything with the search endpoint # (paginate with limit=1000 and offset) # 2. Save the server_time from your last response # 3. Next day: call delta with that timestamp curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/delta?since=2026-04-05T06:00:00Z" # 4. If has_next is true, paginate with offset curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/delta?since=2026-04-05T06:00:00Z&offset=1000" # 5. Save the new server_time, repeat tomorrow
Tip: On a typical day, the delta returns 1,000 to 2,000 records. That's 1 to 2 API calls at limit=1000, compared to 200+ calls to re-download the full database. If you're currently syncing by paginating through all records, switching to delta will reduce your API usage by ~99%.

GET /api/v1/exclusions/{uei_sam} Requires Auth

Vendor-risk screening lookup: pass a UEI SAM and find out whether that entity has an active or historical exclusion on record.

Response semantics

  • 200 — entity is on the exclusions list. Body returns the most recent exclusion record under an exclusion key (dates, location, agency, exclusion type, classification, ~37 fields total), a top-level additional_exclusion_records count (further records keyed to the same UEI, e.g. a debarment by a second agency; 0 for most entities, fetch the full set via /exclusions/search?uei_sam=), a top-level _sources array and, on Pro keys, the two enrichment blocks documented below.
  • 404 — the UEI has no UEI-keyed exclusion. This is the expected answer for the vast majority of legitimate vendors, but it is not a full "clean" verdict: most exclusions are individuals with no UEI, so a 404 does not rule out a name-based debarment. See the coverage caveat below and pair this with a name search before treating a vendor as clear.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/exclusions/N2EDPB1SMN55"

Coverage caveat. Roughly 73% of SAM exclusions are individuals or small entities without a UEI. A 404 from this endpoint means no UEI-keyed exclusion; it does not rule out a name-based debarment of a principal, officer, or related individual. For full vendor screening, pair this lookup with /api/v1/exclusions/search?entity_name=....

Compliance enrichment blocks Pro

When the caller has Pro and the lookup returns a 200, the response carries two top-level blocks summarizing federal money flow to this excluded UEI in the last 12 months. The compliance question goes from "are they debarred?" to "is anyone in the federal supply chain still paying them — directly or indirectly?" in a single call. Both blocks default to a 365-day lookback and always return the zero shape when there's no activity, so client code can read fields without per-call null checks.

recent_sub_payments block — FFATA subaward dollars received indirectly (clean primes paying the excluded entity as a sub):

  • lookback_days, total_received, subaward_count, distinct_primes.
  • most_recent_subaward_date, most_recent_subaward_amount, most_recent_paying_prime: {uei, name} — the smoking-gun row.
  • top_paying_primes — array of up to 5 {uei, name, total, subaward_count} objects.

recent_contract_actions block — FPDS prime contract obligations received directly (the excluded entity is still being awarded contracts by federal agencies):

  • lookback_days, total_obligated_recent, transaction_count, distinct_contracts, distinct_agencies.
  • most_recent_action_date, most_recent_action_amount, most_recent_awarding_agency, most_recent_piid — the smoking-gun row.
  • top_paying_agencies — array of up to 5 {name, value, transaction_count} objects.

Net obligation (total_obligated_recent) can be negative — agencies clawing back contract money from an excluded vendor reads as a "compliance is working" pattern; positive amounts on an active exclusion read as "compliance gap, escalate." Both block shapes are also documented under Pro enrichment fields (universal pattern) at the top of the Endpoints section.

Worked example (Pro response on an excluded UEI with the recency view)

UEI C1ZLDAS18FJ3 (ATI Government Solutions): on the exclusions list since 2025-10-21. In the last 365 days agencies issued 64 contract actions across 21 distinct contracts and 6 agencies, with a NET obligation of -$9.31M, meaning deobligations are exceeding new awards. That's the "compliance is working" pattern. recent_sub_payments is zero (no recent FFATA sub income to this UEI). For the broader 5-year activity view, see the parallel example on /vendor-risk/{uei}.

Response shape. The exclusion record is wrapped under an exclusion key; enrichment blocks are siblings at the top level, per the multi-block detail convention. Free / Developer responses omit the two enrichment blocks; Pro responses include them with the zero-shape when no activity matches.

{ "exclusion": { "entity_name": "...", "...": "37 exclusion fields total (dates, address, agency, classification, etc.)" }, "additional_exclusion_records": 0, "recent_sub_payments": { "lookback_days": 365, "total_received": 0.0, "subaward_count": 0, "distinct_primes": 0, "most_recent_subaward_date": null, "most_recent_subaward_amount": null, "most_recent_paying_prime": null, "top_paying_primes": [] }, "recent_contract_actions": { "lookback_days": 365, "total_obligated_recent": -9312076.02, "transaction_count": 64, "distinct_contracts": 21, "distinct_agencies": 6, "most_recent_action_date": "2026-06-09", "most_recent_action_amount": 85000.0, "most_recent_awarding_agency": "Department of Energy", "most_recent_piid": "89303023CMA000093", "top_paying_agencies": [ {"name": "Office of the Assistant Secretary for Administration and Management", "value": 5872530.47, "transaction_count": 6}, {"name": "Office of the Chief Financial Officer", "value": 2258980.85, "transaction_count": 8}, "...up to 5 entries" ] }, "_sources": ["sam_exclusions", "usaspending_fpds"] }

If you only need the screening yes/no plus the recent-payment views, this endpoint is sufficient. If you also want address clusters, name variants, coordinated-wave membership, and the longer 5-year exposure picture, call /api/v1/vendor-risk/{uei} instead — that response includes the subaward_exposure + contract_exposure blocks (1825-day window) plus the seven risk signals. Live response verified 2026-06-01.

GET /health No Auth Required

Health check endpoint showing system status and database connection.

Response

{ "status": "healthy", "database": "connected" }

Note: For data freshness, see last_delta_utc on /meta or last_updated on /api/v1/stats.

Example

curl "https://govconapi.com/health"

GET /export.csv Requires Auth Developer

Export contract notices as CSV file. Free plan does not have CSV export access.

Query Parameters

A subset of the /opportunities/search filters. For the full filter set (date ranges, value ranges, sort, location, attachments, active_only, response shaping), use /opportunities/search and write a CSV client-side.

  • keywords - Full-text search across title, agency, description
  • naics - Single NAICS code
  • naics_multiple - Comma-separated NAICS codes (OR'd)
  • agency - Agency name (partial match)
  • state - 2-letter state code or full state name
  • set_aside - Set-aside code (e.g. SBA, 8(a))
  • notice_type - Notice type (e.g. Solicitation, Award Notice)
  • solicitation_number - Solicitation number (partial match)
  • limit - Max records to export (default 1000, max 1000)

Any other parameter returns 400 Bad Request. Unknown params are not silently dropped.

Plan Restrictions:

  • Developer: All listed filters available. 15 exports/day.
  • Free: No CSV export access.

CSV Format

CSV exports a 15-column analyst-friendly subset (the columns below in this exact order). Empty values show as blank cells. If you need fields not in this set, pull them from /api/v1/opportunities/search instead, which returns all 59 fields per record.

notice_id,title,notice_type,agency,naics,psc, posted_date,response_deadline,solicitation_number, set_aside_type,award_amount,awardee_name, performance_state_code,contact_email,sam_url

Examples

Export defense awards:

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/export.csv?notice_type=Award%20Notice&agency=defense&limit=1000" \ -o defense_awards.csv

Export by NAICS and state:

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/export.csv?naics=541330&state=CA&limit=500" \ -o ca_it_contracts.csv

Need filters not listed above? Use /api/v1/opportunities/search for the full filter set (value ranges, date ranges, sort, etc.) and write the CSV yourself. The 59-field response gives you everything; the 15-column CSV here is the analyst-ready subset.

Free plan - Not available:

{"detail":"CSV export requires a paid plan. Upgrade at https://govconapi.com/#pricing"}

POST /api/v1/keys No Auth Required

Create a new API key. The key will be emailed to you instantly. Generating a new key deactivates the previous one for that email.

Query Parameters

  • email - Your email address (required)
  • plan - Plan type: "free" (default) or "developer"

Example

curl -X POST \ "https://govconapi.com/api/v1/keys?email=YOUR_EMAIL&plan=free"

Note: For the Developer plan, use the /api/v1/checkout endpoint to create a Stripe checkout session.

POST /api/v1/checkout No Auth Required

Create a Stripe checkout session for Developer plan subscription. Redirects to Stripe payment page.

Query Parameters

  • email - Your email address (required)

Response

{ "status": "success", "checkout_url": "https://checkout.stripe.com/c/pay/cs_...", "plan": "Developer Plan - $19/month", "session_id": "cs_..." }

Example

curl -X POST \ "https://govconapi.com/api/v1/checkout?email=YOUR_EMAIL"

Performance & Reliability

Response Times (US West Coast)

  • Search Queries: ~1.1s (measured average)
  • Single Record: ~800ms
  • Public Endpoints: ~600ms (cached)
  • CSV Export: 1-2s (small datasets)

Operational Notes

  • Concurrent Requests: 5-15 recommended for best performance
  • Data Updates: daily (new postings and modifications). Check last_updated on /api/v1/stats for current freshness.
  • Compression: responses are gzip-encoded when your client sends Accept-Encoding: gzip.

Best Practices for High-Volume Usage

Expect geographic variation in response times:

curl -w "Total: %{time_total}s (Network: ~%{time_connect}s + Server: ~%{time_starttransfer}s)\n" \ -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?naics=541330&limit=100"

Use pagination within a filtered query:

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?naics=541330&limit=100&offset=0"

For keeping a local mirror in sync, use /api/v1/opportunities/delta instead. It filters by last_seen via a since cursor (use the returned sync.server_time as your next since), so you only pull what changed.

Monitor your usage to stay within rate limits:

Every authenticated response reports your current rate-limit state in headers, so you can throttle before you hit the ceiling instead of reacting to a 429:

On a 429 response we also send Retry-After (in seconds), which tells you exactly when to resume. The pattern: back off on X-RateLimit-Remaining, recover on Retry-After.

Use specific filters to reduce server processing time:

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?naics=541330&posted_after=2025-11-01"

Note: NAICS filtering is well-optimized and faster than generic searches

Error Handling Best Practices

import requests import time def make_api_request(url, headers): response = requests.get(url, headers=headers) # Proactive: slow down before you hit the ceiling. remaining = int(response.headers.get('X-RateLimit-Remaining', 1)) if remaining < 10: time.sleep(1) # ease off as the window runs low if response.status_code == 429: # Rate limit exceeded: Retry-After tells you exactly when to resume. retry_after = int(response.headers.get('Retry-After', 3600)) print(f"Rate limited. Waiting {retry_after} seconds...") time.sleep(retry_after) return make_api_request(url, headers) # Retry elif response.status_code == 403: # Plan upgrade needed error = response.json() print(f"Upgrade required: {error['detail']}") return None return response.json()
Technical Architecture (Click to expand)

Rate Limiting

  • Free: 25 requests/day
  • Developer: 1,000 requests/hour
  • Plan Restrictions: CSV export and page size (50 vs 1,000) enforced by plan level. All search filters available on every plan.

Performance

  • Indexed filters: agency, NAICS, PSC, dates, and full-text are indexed; specific filters return faster than broad keyword-only scans.
  • NAICS filtering is among the fastest paths; prefer it when you can.
  • Data size: Loading... opportunities, refreshed daily.
  • Transport: HTTPS only; responses gzip-encoded on Accept-Encoding: gzip.

Data source & freshness

  • Source: SAM.gov (opportunities, awards, entity registry, exclusions) plus contracting-officer contacts.
  • Refresh: daily for opportunities/awards; the SAM entity registry is a monthly snapshot; exclusions refresh weekly. Check /api/v1/stats for current freshness.

Production Usage Guide

Python client with pagination & error handling (Click to expand)
# Production-ready integration pattern import requests import time from datetime import datetime, timedelta class GovConAPIClient: def __init__(self, api_key, plan='developer'): self.api_key = api_key self.base_url = 'https://govconapi.com/api/v1' self.headers = {'Authorization': f'Bearer {api_key}'} # Configure based on your plan if plan == 'developer': self.rate_limit = 1000 # hourly self.burst_limit = 60 # per minute else: self.rate_limit = 25 # daily (free) self.burst_limit = 10 # per minute def search_opportunities(self, filters=None, limit=100): """Search with automatic pagination and rate limiting. Pass at least one meaningful filter; /search rejects unfiltered requests with 400 for keys created after the filter-required cutoff.""" all_results = [] offset = 0 while True: params = {'limit': limit, 'offset': offset} if filters: params.update(filters) response = self._make_request('/opportunities/search', params) if not response or 'data' not in response: break all_results.extend(response['data']) # Use the explicit pagination.has_next signal returned by the API. if not response.get('pagination', {}).get('has_next'): break offset += limit time.sleep(0.1) # gentle on the rate limit return all_results def _make_request(self, endpoint, params=None): """Make request with proper error handling""" url = f"{self.base_url}{endpoint}" try: response = requests.get(url, headers=self.headers, params=params) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 3600)) print(f"Rate limited. Waiting {retry_after} seconds...") time.sleep(retry_after) return self._make_request(endpoint, params) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f"Request failed: {e}") return None # Example usage client = GovConAPIClient('your_api_key_here', 'developer') # Search for DoD opportunities in the last 30 days filters = { 'agency': 'Department of Defense', 'posted_after': (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d') } opportunities = client.search_opportunities(filters) print(f"Found {len(opportunities)} opportunities")

Load Testing Recommendations

Enterprise Integration Patterns (Click to expand)

Pattern 1: Daily Sync with the delta endpoint

Use /api/v1/opportunities/delta, not /search, for keeping a local copy in sync. Delta returns every record changed since your since timestamp (including backfilled records that posted_after would miss) and pages cleanly with has_next.

def daily_sync(client, last_sync_time):
    """Pull every opportunity that's been added or updated since last_sync_time."""
    offset = 0
    new_server_time = None
    while True:
        resp = client._make_request('/opportunities/delta', {
            'since': last_sync_time,
            'limit': 1000,
            'offset': offset,
        })
        if not resp:
            break
        for op in resp['data']:
            store_opportunity(op)   # upsert by notice_id
        # Capture the server's clock from the FIRST page so we use a single
        # consistent cutoff across all paginated calls in this run.
        if new_server_time is None:
            new_server_time = resp['sync']['server_time']
        if not resp['pagination']['has_next']:
            break
        offset += 1000
    return new_server_time   # save this; pass as last_sync_time tomorrow

Pattern 2: Agency Monitoring

Watching N agencies? Make one delta call (or one search call), filter agencies client-side. Looping the API once per agency burns the rate limit and adds latency for no benefit.

def monitor_agencies(client, agencies_of_interest, last_sync_time):
    """Pull all changes since last sync, route by agency client-side."""
    of_interest = {a.lower() for a in agencies_of_interest}
    resp = client._make_request('/opportunities/delta', {
        'since': last_sync_time,
        'limit': 1000,
    })
    if not resp:
        return
    for op in resp['data']:
        agency = (op.get('agency') or '').lower()
        if any(target in agency for target in of_interest):
            if is_new_opportunity(op):
                send_notification(op)
    return resp['sync']['server_time']

Pattern 3: Bulk CSV Export for Analytics

For analytics warehouses, the CSV export endpoint is the lowest-overhead path. Stream the response straight to disk; it's not JSON.

def export_csv_to_file(api_key, filename, **filters):
    """Download /export.csv to a local file. Filters: naics, agency, state,
    set_aside, notice_type, solicitation_number, keywords, naics_multiple, limit."""
    response = requests.get(
        'https://govconapi.com/export.csv',
        headers={'Authorization': f'Bearer {api_key}'},
        params=filters,
        stream=True,
        timeout=120,
    )
    response.raise_for_status()
    with open(filename, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)
    return filename

# Example: pull DLA award notices to a CSV
export_csv_to_file('your_api_key', 'dla_awards.csv',
                   notice_type='Award Notice', agency='defense', limit=1000)

The CSV is the 15-column analyst subset documented in the CSV Export endpoint section. For the full 59-field record set, use the JSON search endpoint instead.

Pattern 4: Map a prime contractor's supply chain

For BD intel, M&A diligence, and set-aside compliance: combine prime award history with the subaward layer. Two calls give you "what they won as a prime" + "who they paid as subs" + a leaderboard of the top sub-vendors.

def supply_chain_map(client, prime_uei):
    """Profile a prime contractor's federal footprint, top to bottom."""
    # 1. Prime-side awards: what contracts have they won?
    profile = client._make_request(f'/companies/{prime_uei}', {})
    awards  = client._make_request(f'/companies/{prime_uei}/awards', {'limit': 1000, 'sort': 'amount'})

    # 2. Sub-side flow: who have they paid, and how much?
    # Subaward endpoints cap at limit=250 (rows are wider than /awards/search).
    # For a top prime with thousands of FFATA reports, page through with offset.
    subs = client._make_request(f'/companies/{prime_uei}/subawards', {'limit': 250})

    return {
        'prime_name':           profile['name'],
        'total_won_as_prime':   profile['total_value'],            # all-time award notices
        'total_paid_to_subs':   subs['summary']['total_subaward_amount'],
        'distinct_sub_vendors': subs['summary']['distinct_sub_vendors'],
        'distinct_contracts':   subs['summary']['distinct_prime_contracts'],
        'subaward_date_range':  (subs['summary']['first_subaward_date'],
                                 subs['summary']['last_subaward_date']),
        'top_5_subs_by_dollars': _top_n_subs(subs['data'], 5),
        'top_5_awards':         awards['data'][:5],
    }

For the per-contract supply-chain forensics (every sub on one prime PIID), use /subawards/search?piid=<piid>. For the agency or NAICS slice across all primes, use the same endpoint with those filters instead of a UEI.

Pattern 5: True federal revenue for a small business

A small business in federal contracting often earns the larger half of its federal income as a sub, not as a prime. Looking up /companies/{uei}/awards alone undercounts. The honest revenue view is the union:

def true_federal_revenue(client, uei):
    """Combined prime + sub revenue picture. /companies/{uei} returns this in one call."""
    profile = client._make_request(f'/companies/{uei}', {})

    return {
        'name':                 profile['name'],
        'prime_revenue':        profile['total_value'],         # SAM Award Notice value (see fpds_obligated_total for the fuller FPDS prime total)
        'sub_revenue':          profile['sub_revenue_total'],   # subaward dollars received, current FFATA coverage
        'combined':             profile['total_value'] + profile['sub_revenue_total'],
        'prime_share':          profile['prime_revenue_share'], # 0..1; null when both are zero
        'top_paying_primes':    profile['top_paying_primes'],   # who their actual customers are
        'top_naics_as_prime':   profile['top_naics'],
    }

# Example output for an SMB:
#   prime_revenue = $1.2M,  sub_revenue = $8.4M,  combined = $9.6M,  prime_share = 0.13
#   → 87% of their federal revenue is sub work; the prime-only view missed it.

Two different prime measures appear here: this snippet's hand-combined combined uses total_value (the SAM Award Notice slice) for prime, while the response's prime_revenue_share field uses the broader FPDS prime obligation total (fpds_obligated_total). Both the FPDS prime total and the FFATA subaward total cover the FY2025-onward window (since 2024-10-01), so prime_revenue_share compares like-for-like. For the authoritative prime figure use fpds_obligated_total; for the underlying records, see /companies/{uei}/prime-relationships.

Pattern 6: Active-payment compliance screen on an excluded vendor

For compliance and FCA investigation: when an entity hits the SAM exclusion list, the question that matters is whether clean primes are still paying them. /exclusions/{uei_sam} answers both halves of the screen — the exclusion record itself, and the recent-payments enrichment — in one call.

def active_payment_screen(client, uei):
    """Returns the exclusion record + the smoking-gun recent-payments view,
    or None if the UEI is clean by UEI lookup (still pair with name search for full coverage)."""
    resp = client.get(f'/api/v1/exclusions/{uei}')
    if resp.status_code == 404:
        return None   # no UEI-keyed exclusion; check by entity_name for full coverage

    body = resp.json()
    return {
        'exclusion_type':           body['exclusion_type'],
        'excluding_agency':         body['excluding_agency_code'],
        'create_date':              body['create_date'],
        'active':                   body['record_status'] == 'Active',
        # The compliance signal:
        'still_being_paid':         body['recent_sub_payments']['subaward_count'] > 0,
        'total_received_last_12mo': body['recent_sub_payments']['total_received'],
        'most_recent_payment':      body['recent_sub_payments']['most_recent_subaward_date'],
        'most_recent_paying_prime': body['recent_sub_payments']['most_recent_paying_prime'],
        'top_paying_primes':        body['recent_sub_payments']['top_paying_primes'],
    }

Pair this with /exclusions/search?entity_name=... for full screening coverage: ~80% of SAM exclusions are individuals or entities with no UEI, so a UEI lookup alone is not a full screen. For the broader risk surface (address clusters, name variants, coordinated waves), the same enrichment is also present on /vendor-risk/{uei} under subaward_exposure.

Error Codes

Unknown query parameters return 400. If you pass a parameter the endpoint doesn't recognize (a typo like ?nme= instead of ?name=, or a guess like ?q= on an endpoint that uses ?keywords=), we respond with 400 Bad Request and list the valid parameter names for that endpoint in the error detail. This is deliberate: silently dropping an unrecognized filter would return unfiltered results to a caller who thought they were filtering, which has caused real bugs in customer integrations. Stick to the documented parameter names listed under each endpoint above.

Code Description Common Causes
400 Bad Request Invalid query parameters, unknown query parameter names, or malformed request
401 Unauthorized Missing or invalid API key
402 Payment Required A Pro feature (Companies, Contacts, Vendor Risk, or a Pro-only filter on entities search) used without Pro entitlement
403 Forbidden Page size exceeds your plan's max (Free: 50, Developer/Pro: 1,000)
404 Not Found Resource (notice_id) doesn't exist
422 Unprocessable Entity A parameter fails validation (e.g. limit out of range, negative offset, missing required since on delta, within_days out of range)
429 Too Many Requests Rate limit exceeded for your plan
500 Internal Server Error Server-side error (contact support)

Data Freshness & Transparency

Contract notices are collected daily at 2:00 AM UTC from SAM.gov using their official API. The collection includes:

Check the /meta endpoint for the exact timestamp of the last collection run.

Important: We Keep ALL Records

We intentionally never archive or delete records. This means:

  • To filter for currently-open opportunities, pass ?active_only=true on /opportunities/search. This is a convenience filter we compute as archive_date_detailed > CURRENT_DATE, not a SAM source field.
  • Use response_deadline for bid-deadline awareness (independent of archive status).
  • Use notice_type to understand lifecycle (Solicitation → Award Notice).
  • The stored active field is always "Yes" by design (we never archive). Don't treat it as a status flag — use ?active_only=true or archive_date_detailed directly instead.
  • Past-deadline and archived opportunities remain queryable for competitive intelligence by default — pass ?active_only=true to filter them out.

Check /api/stats for current counts.

Tracking amendments: Same solicitation_number appears multiple times (original + amendments). Use notice_id for unique records, or group by solicitation_number to track changes.

Tips for Getting the Most Out of the API

  • Use limit=1000 instead of paginating at 100. If you need 5,000 records, that's 5 requests instead of 50. Saves API calls and time.
  • Use posted_after for daily syncs. Instead of scanning all 200K+ records, fetch only what's new since your last sync. posted_after=2026-04-03 returns just today's notices.
  • Know your notice_type values:
    • Combined Synopsis/Solicitation (71K) - most common, open for bids
    • Award Notice (43K) - contracts already awarded, has winner data
    • Solicitation (40K) - open for bids
    • Sources Sought (15K) - market research, not yet a solicitation
    • Presolicitation (13K) - upcoming, not yet open
    • Special Notice (9K) - informational only
  • Find who won a contract: Search by solicitation_number and filter notice_type=Award Notice. The award record has awardee_name, award_amount, and award_date.
  • Find open opportunities only: Use due_after=2026-04-04 (today's date) to get only notices with a future deadline. Combine with notice_type=Solicitation or notice_type=Combined%20Synopsis/Solicitation.
  • Cache responses locally. Data updates once daily at 2 AM UTC. If you're calling the same query multiple times per day, cache the first response.

Tested Integration Examples

1. Plan Verification Workflow

Step 1: Check your plan level

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/me"

Response: {"plan":"developer","email":"[email protected]"}

Step 2: Build the query

Multi-filter search (every plan supports all filters; paid plans get higher page size and rate limit):

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?agency=defense¬ice_type=Award%20Notice&value_min=1000000"

Single-filter search:

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?naics=541330&state=CA"

2. Pagination Best Practices

Efficient pagination within a filtered query:

To keep a local mirror in sync, use /opportunities/delta with a since cursor (it returns only changed records). /search pagination is intended for paging through filtered result sets, not full-corpus backfill. For initial historical backfill, page a date-filtered search within your plan window.

offset=0 limit=100 total_collected=0 filter="naics=541330" # required: at least one filter on /search while true; do response=$(curl -s -H "Authorization: Bearer YOUR_API_KEY" \ "https://govconapi.com/api/v1/opportunities/search?${filter}&limit=$limit&offset=$offset") has_next=$(echo "$response" | jq -r '.pagination.has_next') data_length=$(echo "$response" | jq '.data | length') total_collected=$((total_collected + data_length)) echo "Collected $total_collected records..." if [ "$has_next" != "true" ] || [ "$data_length" -eq 0 ]; then break fi offset=$((offset + limit)) sleep 0.1 done

Note: Be respectful of the API with small delays between requests

4. Working with Attachments

Option 1: Filter for opportunities with attachments (Developer plan):

import requests API_KEY = "your_api_key" headers = {"Authorization": f"Bearer {API_KEY}"} response = requests.get( "https://govconapi.com/api/v1/opportunities/search?has_attachments=true&limit=20", headers=headers ) results = response.json()

Option 2: Get attachments for specific opportunity:

import requests # From search results notice_id = results['data'][0]['notice_id'] # Get attachments via dedicated endpoint attachments = requests.get( f"https://govconapi.com/api/v1/opportunities/{notice_id}/attachments", headers=headers ).json() # Download files (SAM.gov URLs, no auth needed) for url in attachments['attachments']: file_response = requests.get(url) # Save file to disk with open(f"attachment_{attachments['attachments'].index(url)}.pdf", "wb") as f: f.write(file_response.content)

Option 3: Use existing field in search response:

import requests # Attachments already included in search response for opp in results['data']: if opp.get('resource_links_array'): print(f"Opportunity {opp['notice_id']} has {len(opp['resource_links_array'])} attachments") for url in opp['resource_links_array']: file_response = requests.get(url) # Process attachment directly

Which approach to use? Use resource_links_array from search results when processing multiple opportunities. Use the /attachments endpoint only if you need attachments for one specific opportunity without fetching the full record.

Feature Reference

Plan Restrictions Summary

All search filters (NAICS, PSC, state, keywords, agency, dates, location, amounts, set-aside, has_attachments, etc.) are available on every plan, including the 14-day free trial. CSV export, the larger page size (1,000 vs 50), and the 1,000/hour rate limit require the Developer plan.

Current Database Stats (Live)

Counts loaded live from /api/stats; data refreshed daily. For a precise freshness timestamp, read last_updated from /api/v1/stats.

Performance Benchmarks (Tested)

Federal Contracting Officers Directory

Need direct contact with government buyers? Our sister site contacts.govconapi.com provides verified contact information for federal contracting officers with phone numbers, emails, agency details, and recent procurement activity.

Need Help?

Questions or issues? Contact [email protected]

API Status: https://govconapi.com/health

Interactive Docs: https://govconapi.com/docs (auto-generated from OpenAPI schema)