SAM.gov contract opportunities as JSON or CSV: the public daily extract, the keyed API, a Python example and a free sample
How to pull US federal solicitations and award notices from SAM.gov yourself, what the keyed API quota really is, what the public daily CSV extract contains and where it surprises you, and when a normalized feed is worth paying for.
The short version
- SAM.gov is where US federal agencies post contract opportunities (solicitations, sources sought, award notices). There are two official machine-readable routes: the Get Opportunities API (needs an API key and has a per-day request quota) and a public daily bulk extract (one CSV file, no key, no login).
- For a non-federal account without a SAM.gov role, the documented personal-key quota is 10 requests per day. That is fine for a test and thin for a daily pipeline. The bulk file has no quota: it is one 217 MB download that SAM.gov regenerates every day.
- The extract is easy to read with 30 lines of Python, but it has traps: mixed text encodings, one awardee string instead of fields, contact columns filled with named officials, and active notices only. This page lists them, with a script tested today and a free 20-record sample.
1. The two official routes
Route A: the Get Opportunities API
Documentation: open.gsa.gov/api/get-opportunities-public-api. The endpoint is https://api.sam.gov/prod/opportunities/v2/search. The docs state that a user must provide an API key, that a posted-date range is mandatory, and that limit has a maximum of 1000 records per page (default 1). The key comes from the Account Details page of your SAM.gov account, so a login is required. In our test, a request without a key returned a bare HTTP 404 instead of a message about the missing key.
The quota depends on your role. The Opportunities page only says that requests per day are limited based on the federal, non-federal or general role. The numbers are in the API key rate-limit table on the Entity Management API page:
| Account type | Key type | Default daily limit |
|---|---|---|
| Non-federal user with no role in SAM.gov | Personal API key | 10 requests/day |
| Non-federal user with a role in SAM.gov | Personal API key | 1,000 requests/day |
| Federal user | Personal API key | 1,000 requests/day |
| Non-federal system user | System account API key | 1,000 requests/day |
Two caveats. We did not spend a key to confirm that this table is applied to the Opportunities endpoint in the same way, so check your own account. And with limit=1000, ten requests can still return up to 10,000 notices, so the quota mostly hurts when you experiment, retry, or need several filtered queries per day.
Route B: the public bulk extract
SAM.gov Data Services publishes the "Contract Opportunities" extract with privacy "Public" (sam.gov/data-services), and it is listed on data.gov. The file we use:
https://s3.amazonaws.com/falextracts/Contract%20Opportunities/datagov/ContractOpportunitiesFullCSV.csv
Checked on 5 November 2026:
HTTP 200without any key or cookie.Content-Length: 217,091,523bytes,Last-Modified: Thu, 05 Nov 2026 04:30:52 GMT, S3 metadatax-amz-meta-privacy: Public. The file is rebuilt once a day, so the newest notices are those posted up to that morning (UTC).Accept-Ranges: bytes: HTTP range requests work, so you can fetch only the head of the file to peek at the columns.- One full pass over the file found 70,965 rows and 47 columns, no duplicate
NoticeId, and every row hasActive = Yes. It is the set of active notices, not an archive: posted dates run from 2008-03-05 to 2026-11-05, but old closed notices are gone. - Rows are sorted by
PostedDate, newest calendar day first, with no violation in the whole file (within a day the times are not ordered). That is what allows the early stop in the script below. - Notice types: Combined Synopsis/Solicitation 21,185; Solicitation 19,161; Award Notice 11,931; Presolicitation 6,963; Sources Sought 5,180; Special Notice 5,101; Justification 690; Modification/Amendment/Cancel 648; Justification and Approval (J&A) 81; Sale of Surplus Property 14; Consolidate/(Substantially) Bundle 11.
Quirks of the extract
- Mixed encoding. Most lines are UTF-8, a few are Windows-1252. Decoding the whole file as UTF-8 fails; decode line by line and fall back to cp1252 (the script below does).
- The awardee is one string:
"A HILL FORESTRY & TREE SERVICES, LLC Columbia LA 71418 USA". Name, city, state and ZIP are not separate columns, and the address part is sometimes a full street address. Some awardees are plain business names without any address. All 11,931 award notices in the file carry both an awardee and an amount (Award$). - Contact columns hold named people. 70,183 of the 70,965 rows have a primary contact:
PrimaryContactFullname,PrimaryContactEmailandPrimaryContactPhone(plus secondary equivalents) hold the contracting officer's name, e-mail and phone. Those are personal data; drop them unless you need them. - Dates are not uniform.
PostedDateisYYYY-MM-DD HH:MM:SS.ResponseDeadLineis empty in 8,843 rows, an ISO timestamp with offset (2026-11-19T23:30:00-07:00) in 51,367 rows and a bare date (2026-11-12) in 10,755 rows. - Set-aside is usually a code (
SBA,8A,SDVOSBC,NONE, ...) next to a long description column; a few rows hold JSON-like text such as["SBA"]. - The deadline can precede the posting date. One sole-source notice in our sample was posted on 5 November with a response deadline of 4 November. Filter on the deadline if you want open bids only.
- No estimated value for solicitations, no attachments (statements of work and RFQ files are on the notice page), no CPV: the classification is NAICS (
NaicsCode) and PSC (ClassificationCode). - Amendments and updates are separate rows that share a solicitation number (
Sol#).
2. DIY: new solicitations to CSV in Python
This script streams the file, stops at the first row older than three days (the file is sorted newest day first, so a few MB are enough) and writes a CSV. Run on 5 November 2026 it returned 14 notices in NAICS 5415 posted since 2 November. It needs pip install requests. For a full-history scan remove the break; the whole download is 217 MB.
import csv
import io
from datetime import date, timedelta
import requests
URL = ("https://s3.amazonaws.com/falextracts/Contract%20Opportunities/datagov/"
"ContractOpportunitiesFullCSV.csv")
CUTOFF = (date.today() - timedelta(days=3)).isoformat() # file is sorted newest day first
NAICS_PREFIX = "5415" # computer systems design and related
KEEP = ["NoticeId", "Title", "Sol#", "Sub-Tier", "PostedDate", "Type", "ResponseDeadLine",
"NaicsCode", "ClassificationCode", "SetASideCode", "PopState", "Link"]
def lines(resp):
"""Decode line by line: the file is mostly UTF-8 with a few Windows-1252 lines."""
for raw in resp.iter_lines(chunk_size=1 << 16):
try:
yield raw.decode("utf-8")
except UnicodeDecodeError:
yield raw.decode("cp1252")
rows = []
with requests.get(URL, stream=True, timeout=120, headers={"User-Agent": "my-script/1.0"}) as resp:
resp.raise_for_status()
# csv.DictReader accepts any iterator of lines; quoted multi-line descriptions are handled.
for row in csv.DictReader(lines(resp)):
if row["PostedDate"][:10] < CUTOFF:
break # stop early: nothing newer follows
if row["Type"] in ("Solicitation", "Combined Synopsis/Solicitation") \
and row["NaicsCode"].startswith(NAICS_PREFIX):
rows.append({k: row[k] for k in KEEP})
with open("sam_opportunities.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=KEEP)
w.writeheader()
w.writerows(rows)
print(len(rows), "notices since", CUTOFF)
3. When the Actor saves time
For a single export the script above is enough, and free. The Apify Actor SAM.gov Contract Opportunities (by Nexus Scrapers) reads the same public extract, not the API, so it needs no key either. It pays off when you would otherwise maintain that code:
- One normalized schema shared with our TED and UK Actors: buyer object, ISO country, money as
{amount, currency}, dates asYYYY-MM-DD, deadline with time and offset, unified notice types. Missing values arenull. SAM-specific fields (samNaicsCode,samPscCode,samSetAsideCode, solicitation number, place of performance state and ZIP) are extra keys. - Filters as plain inputs: keyword, NAICS and PSC prefixes, set-aside, agency words, state, posting window, response-deadline window, "open for response only", award amount range.
- Encoding, record assembly and early stop are handled: the default run reads about 0.1 MB, not the whole file.
- Awardee state and ZIP parsed from the awardee string, which is otherwise kept as published. Contacts are off by default; an
includeContactsinput adds them as published. - Scheduling and integrations through Apify: a daily run into Slack, e-mail, Google Sheets or a webhook; export as JSON, CSV or Excel; API and MCP access.
Pricing is pay per result; the Actor page shows the current price.
What it does not do: it cannot give you closed or historical notices (the extract holds active ones only; schedule it and keep the datasets if you need history), attachments, or estimated values. It does not use the keyed API, so there is no detail beyond what the extract contains. For a one-off CSV, use the script.
4. Real sample: 20 normalized records
These 20 records were produced on 5 November 2026 by running the Actor's own code locally on the live extract (no Apify platform involved): 12 solicitations and 8 award notices posted on 4 and 5 November 2026, taken from the newest rows with at most one notice per agency, except Defense Logistics Agency (at most two solicitations and three awards). They are the first matches in that order, not hand-picked for quality. Contacts are not included. One award whose awardee was a named individual was skipped on purpose, and a scrub pass over every text field removed one e-mail address and one string shaped like a phone number from free-text descriptions (the second was a wage-determination ID, so the scrub is deliberately over-eager).
Download: sample.json (full records, UTF-8) and sample.csv (flattened, lists joined with | , opens in Excel). Free to use under the notes in section 5.
| Notice | Type | Agency | Title | NAICS | Set-aside | Deadline / award |
|---|---|---|---|---|---|---|
| 33cea54e | Solicitation | VETERANS AFFAIRS, DEPARTMENT… | Correct Electrical Deficiencies | 236220 | SDVOSBC | 2026-12-08 |
| fcbdee6b | Comb. Synopsis/Sol. | NATIONAL INSTITUTES OF HEALTH | Maintenance/Service Agreement - GE PET Discov… | 811210 | NONE | 2026-11-13 |
| 1578828a | Solicitation | DEPT OF THE AIR FORCE | Replace DSN-HDD Generator for 78th Signal Bat… | 335312 | NONE | 2026-11-10 |
| f0144497 | Solicitation | DEPT OF THE NAVY | Sole Source OEM Hirsch Software Service Agree… | 513210 | - | 2026-11-04 |
| 3a347e6c | Comb. Synopsis/Sol. | DEFENSE LOGISTICS AGENCY | 81--SUPPORT,SHIPPING AND S | 332439 | - | 2026-11-12 |
| f8c20df3 | Comb. Synopsis/Sol. | DEFENSE LOGISTICS AGENCY | 47--HOSE ASSEMBLY,NONMETAL | 332999 | - | 2026-11-12 |
| 308a70d9 | Solicitation | US CENSUS BUREAU | Pressure Seal V-Fold Mailer | 322220 | SBA | 2026-11-11 |
| b8fa750a | Solicitation | FOREST SERVICE | Stove Prairie Campground Gate Replacement | 237990 | SBA | 2026-11-16 |
| 74284fef | Comb. Synopsis/Sol. | FEDERAL PRISON SYSTEM / BUREA… | FCI-GRE FY27 QTR 2 SUBSISTENCE - NATIONAL MENU | 311999 | SBA | 2026-11-18 |
| 30657ffb | Comb. Synopsis/Sol. | AGRICULTURAL MARKETING SERVICE | Mozzarella Cheese | 311513 | - | 2026-11-12 |
| 52ec7727 | Comb. Synopsis/Sol. | AGRICULTURAL RESEARCH SERVICE | Replacement Cattle Dip Vat Drip Pad (Edinburg… | 236220 | SBA | 2026-12-16 |
| f38e8430 | Comb. Synopsis/Sol. | NATIONAL OCEANIC AND ATMOSPHE… | N--UIL Generator Replacement | 238290 | SBA | 2026-11-17 |
| 6bdfdd58 | Award Notice | FOREST SERVICE | National Forests and Grasslands of Texas Davy… | 115310 | SBA | 35,447 USD |
| 66f3ede9 | Award Notice | DEFENSE LOGISTICS AGENCY | 62--PANEL,INDICATING,LIGHT TRANSMITTING | 336320 | - | 350,000 USD |
| 4e0b027f | Award Notice | DEFENSE LOGISTICS AGENCY | 59--RELAY,HYBRID | 333612 | - | 350,000 USD |
| b643ded2 | Award Notice | AGRICULTURAL MARKETING SERVICE | 12-1NFG-27-B-0002 - AMS International Bulk So… | 11119 | - | 5,125,304 USD |
| c2fb9a69 | Award Notice | NATIONAL OCEANIC AND ATMOSPHE… | F--This requirement is for oil containment bo… | 562910 | - | 150,000 USD |
| 84c41720 | Award Notice | DEPARTMENTAL OFFICES | Z--WV-CANAAN VLY NWR-CHAIN LINK FENCE | 236220 | - | 18,000 USD |
| 0cd8054e | Award Notice | FEDERAL PRISON SYSTEM / BUREA… | FCC TERRE HAUTE BREAD NOV26-SEP27 | 311812 | SBA | 227,945 USD |
| 206024ac | Award Notice | VETERANS AFFAIRS, DEPARTMENT… | 6515--NX EQ Physiologic Motility Analyzers (E… | 339113 | - | 5,457,452 USD |
One award record, trimmed to the main fields:
{
"sourceId": "b643ded22776416391c1870fe11480ab",
"noticeType": "contract-award",
"noticeTypeCode": "Award Notice",
"title": "12-1NFG-27-B-0002 - AMS International Bulk Sorghum Solicitation",
"buyer": {
"name": "AGRICULTURAL MARKETING SERVICE",
"country": "US",
"city": "WASHINGTON",
"nutsCode": null,
"buyerType": "central-government"
},
"publicationDate": "2026-11-04",
"awardInfo": {
"winners": [
{
"name": "CARGILL, INCORPORATED Wayzata MN 55391 USA",
"country": "US"
}
],
"awardedValue": {
"amount": 5125303.8,
"currency": "USD"
},
"awardDate": "2026-11-04"
},
"samSolicitationNumber": "12-1NFG-27-B-0002",
"samNaicsCode": "11119",
"samPscCode": "8915",
"samSetAsideCode": null,
"samAwardNumber": "12-1NTB-27-C-0006",
"samAwardeeState": "MN",
"samAwardeeZip": "55391"
}
5. Data licence and terms
The content is created by US federal agencies. The data.gov entry does not state a licence. SAM.gov's Terms of Use say that SAM.gov makes data available via APIs and extracts at open.gsa.gov/api and sam.gov/data-services, and that automated data gathering and web scraping tools are prohibited (with account access denied if detected). Downloading the published extract is the route the terms point to; scraping the website is not.
The Dun & Bradstreet note. The same terms say that SAM.gov contains data supplied by D&B, that D&B owns it and restricts its use, and that "D&B Open Data" (legal business name, street address, city, state, ZIP, country and similar elements) may be used with limits, among them that you may not share it in bulk, meaning enough records that someone could use them as a substitute for getting the data from D&B. The terms list the affected records: entity registrations and exclusions dated before 4 April 2022, and base award notices with an award date before 4 April 2022. In today's extract 716 award notices are dated before that day; the 2026 awards in our sample fall outside the list. If you plan to republish older award notices (awardee names and addresses) in bulk, read the terms and the D&B section first. This is a summary, not legal advice.
Personal data: the raw file carries contracting officers' names, e-mails and phones. Our sample leaves them out. If you keep them, use them only for correspondence about that notice.