SAM.gov Federal Contract Opportunities & Attachments
No API key. No login. Documents included.
Every U.S. federal contract opportunity as clean JSON — 62 fields per notice, including the full solicitation text and the solicitation documents themselves. It talks to the same public endpoint the SAM.gov search page uses, so there is no API key to register for and nothing to renew.
oswaldocarabano/sam-gov-data-scraper
{
"maxResults": 500,
"activeOnly": true,
"publishedWithinDays": 7,
"noticeTypes": ["o", "k"],
"naicsCodes": ["541511"],
"scrapeAttachments": true
}- Version
- v0.1.9
- Memory
- 4096 MB
- Browser
- none
- Proxy
- None required
Short answer
The SAM.gov Federal Contract Opportunities & Attachments Actor extracts U.S. federal contract notices as JSON with 62 fields: identity, lifecycle, deadline with its precision, NAICS and PSC classification, agency hierarchy, contacts, place of performance, award details, the full solicitation text, and the attachment list with stable download URLs. It needs no API key and no login. Fill rates were measured on 13,434 notices, and 36.7% ± 4.6 of active notices carry at least one downloadable file. Pricing is $0.0025 per notice and $0.0005 per attachment, with no charge for duplicates or errors.
Key points
- No API key and no login. Several SAM.gov scrapers ask you to register for an api.sam.gov key and then own the problem when its quota runs out; this one uses the same public endpoint the SAM.gov search page itself uses.
- 62 fields, snake_case, with `null` where a value is genuinely absent and never a missing key — so `row.award_amount` is safe on every row.
- Fill rates measured on 13,434 notices across five days spread over ten months: contact name 99.6%, solicitation number 99.5%, PSC 97.8%, NAICS 96.1%, description 84.3%, response deadline 75.2%.
- 36.7% ± 4.6 of active notices carry at least one downloadable file, measured on a stratified sample of 829 notices across 70 agency × notice-type cells covering 96.7% of the active index. The rate swings from ~100% at Veterans Affairs to ~28% at the Department of Defense, which is 63% of the index.
- `response_due_precision` says whether a deadline is a real instant (61.0%) or a date with no time (38.8%). SAM.gov stamps midnight UTC on the date-only ones; this Actor does not pass that through as an instant.
- Notices are deduplicated by `notice_id` before delivery, so you are never charged for a duplicate.
- robots.txt is fetched on every run and enforced in the request layer, and runs are deliberately polite: 4 concurrent requests with a one-second delay.
What it does
Every U.S. federal contract opportunity as clean JSON — 62 fields per notice, including the full solicitation text and the solicitation documents themselves. It talks to the same public endpoint the SAM.gov search page uses, so there is no API key to register for and nothing to renew.
{
"notice_id": "a1b2c3d4e5f6",
"solicitation_number": "W912DY-26-R-0042",
"title": "Custom Software Development Services",
"notice_type_code": "o",
"notice_type": "Solicitation",
"is_active": true,
"published_at": "2026-08-14T13:02:00.000Z",
"archive_at": "2026-09-30T00:00:00.000Z",
"response_due_local": "2026-09-12 14:00 CDT",
"response_due_utc": "2026-09-12T19:00:00.000Z",
"response_due_precision": "instant",
"response_timezone": "America/Chicago",
"naics_code": "541511",
"naics_title": "Custom Computer Programming Services",
"psc_code": "D302",
"set_aside_code": "SBA",
"set_aside": "Total Small Business Set-Aside",
"agency_name": "Department of the Army",
"agency_id": "100000000",
"contact_name": "Jane Contracting Officer",
"contact_email": "jane.officer@example.mil",
"contact_phone": null,
"pop_state": "TX",
"award_amount": null,
"awardee_name": null,
"description_text": "The Government intends to award a firm-fixed-price contract…",
"attachments": [
{
"kind": "file",
"name": "Statement_of_Work.pdf",
"size_bytes": 1583201,
"mime_type": "application/pdf",
"url": "https://sam.gov/api/prod/opps/v3/opportunities/resources/files/..."
}
],
"file_count": 1,
"link_count": 0,
"from_cache": false,
"data_age_hours": 0
}Why this one
The documents, with the honest number attached
Turn on attachments and every notice gains an `attachments[]` array, and each entry says which kind it is: a `file` — the real PDF, statement of work, price matrix or amendment — or a `link` to the agency's own portal, when that is all the agency posted. `file_count` and `link_count` are separate fields on purpose. A link to neco.navy.mil is not the solicitation, and counting it as one would inflate the number that matters most.
Attachment rates that vary more by agency than by type
Sources Sought notices carry a file 60% of the time and Award Notices 14%, but the bigger factor is who published it: roughly 100% at Veterans Affairs, ~90% at GSA, and ~28% at the Department of Defense — which is 63% of the whole index and drags the overall rate down because it posts on its own portals. That is worth knowing before you plan a run, so it is published as a table rather than as an average.
Three deadline fields, because one would be a lie
SAM.gov's `responseDate` is not always a real instant. Measured on 1,566 notices with a deadline, 61.0% carry a true offset, 38.8% say "due on the 25th" with no time, and 0.2% give a wall-clock time with no zone. SAM.gov stamps midnight UTC on the date-only ones; passing that through would present a precise deadline that does not exist. So `response_due_utc` is null unless `response_due_precision` is `instant`, and `response_due_local` is what you display.
The debarment lookup it refuses to offer
Exclusion records are available as a separate dataset, but there is deliberately no "is this vendor debarred?" lookup. 63% of the list is individuals and only about 20% of those carry a UEI, so matching by name alone manufactures false positives — and a false positive here tells you a real person is debarred when they are not.
Use cases
- Monitor new federal opportunities in your NAICS or PSC codes, with the solicitation text in the row.
- Download the solicitation documents themselves — statements of work, price matrices, amendments — through stable URLs.
- Filter for set-aside opportunities: SBA, 8(a), WOSB, SDVOSB or HUBZone.
- Track award history by agency, awardee UEI and amount.
- Build a pipeline keyed on real deadlines, using `response_due_precision` so date-only notices are not treated as timed.
- Analyse which agencies actually publish documents on SAM.gov versus on their own portals.
Input
Every field has a default, and the defaults are deliberately small so a first run is cheap enough to inspect before you commit to a sweep. This table mirrors the Actor's own input schema field for field.
| Field | Default | What it does |
|---|---|---|
maxResultsinteger | 100 | Maximum noticesYou are charged per notice delivered, never for duplicates and never for error rows. Hard cap 50,000 per run. |
activeOnlyboolean | true | Only active noticesAbout 49,700 notices are active. Turn this off to reach the archive of 5.6 million going back to 2000. |
publishedWithinDaysinteger | 7 | Published within the last N daysAbout 2,500 notices are published per calendar day. Ignored if you set explicit dates. |
publishedFromstring | "" | Published from (YYYY-MM-DD)Inclusive start of the publication range. |
publishedTostring | "" | Published to (YYYY-MM-DD)Exclusive end of the publication range. |
keywordsstring | "" | KeywordsFree-text search across title and description. Wrap a phrase in double quotes to match it exactly. |
noticeTypesstring[] | [] | Notice typesSolicitations and combined synopsis/solicitations are the ones that carry documents; award notices rarely do — 14% against 49%. |
naicsCodesstring[] | [] | NAICS codesE.g. 541511 for custom computer programming. Present on 96% of notices. |
pscCodesstring[] | [] | PSC codesE.g. R425. Present on 98% of notices, so it is the better classification filter. |
setAsideCodesstring[] | [] | Set-aside codesSBA, 8A, WOSB, SDVOSBC, HZC. A set-aside code is present on 46% of notices. |
agencyIdsstring[] | [] | Agency IDsSAM.gov organization IDs — 100000000 is the Department of Defense, 100006688 the General Services Administration. Filtering by agency changes what you get more than filtering by type does. |
placeOfPerformanceStatesstring[] | [] | Place of performance statesTwo-letter state codes. Only about a third of notices state a place of performance. |
officeStatesstring[] | [] | Contracting office statesTwo-letter state codes for the office that issued the notice. Better filled in than place of performance. |
scrapeAttachmentsboolean | false | Also fetch solicitation documentsAdds the attachment list with name, size, MIME type and a stable URL. One extra request per notice. |
maxExclusionsinteger | 0 | Maximum exclusion recordspersonal dataFetches the federal debarment list into a separate dataset. 63% of the list is individuals, which is why no name-matching lookup is offered. |
maxCacheAgeDaysinteger | 90 | Maximum cache age (days)Notices are immutable once published — an amendment creates a new notice that links back — so a cached row stays correct. 0 forces a fresh fetch. |
maxConcurrencyinteger | 4 | Maximum concurrencyDeliberately polite, with a one-second delay. SAM.gov is public infrastructure paid for by taxpayers. |
Output and fill rates
A field being in the schema is not the same as it having a value. The percentages below were counted on real runs; the sample sizes are in Measurements. Anything not listed here is not promised.
| Field | Filled | Meaning |
|---|---|---|
notice_idstring | 100% | SAM.gov notice identifier. The dedupe key, so you are never charged twice for it. |
solicitation_numberstring | 99.5% | With `solicitation_number_clean` alongside. |
titlestring | 100% | Notice title as published. |
notice_typestring | 100% | With `notice_type_code`: `o` solicitation, `k` combined, `r` sources sought, `a` award. |
is_activeboolean | 100% | With `is_canceled` and `modification_count`. |
published_atdatetime | 100% | With `modified_at`, `archive_at` and `original_published_at`. |
response_due_localstring | 75.2% | The deadline as the notice states it. This is the one to display. |
response_due_utcdatetime | 61% | A true UTC instant, but only when `response_due_precision` is `instant` — 61.0% of deadlines. Null otherwise. |
response_due_precisionstring | 75.2% | `instant` 61.0%, `date_only` 38.8%, `local_naive` 0.2%. Read it before converting a deadline. |
naics_codestring | 96.1% | With `naics_title` and `naics_all`. |
psc_codestring | 97.8% | With `psc_title` and `psc_all`. Better filled than NAICS. |
set_aside_codestring | 46.1% | With `set_aside`. Present on fewer than half of notices. |
agency_namestring | 99.4% | With `agency_id`, `agency_code` and the full `agency_hierarchy`. |
contact_namestring | 99.6% | Contracting officer, with `contacts_all` for every contact on the notice. |
contact_emailstring | 99.2% | Published business email of the contact. |
contact_phonestring | 32.7% | The weakest of the contact fields by a wide margin. |
pop_statestring | not measured | Place of performance, with country, city and ZIP. Only about a third of notices state one. |
award_amountnumber | 23% | About 23% of all notices, because most notices are not awards. Within award notices it is effectively always present. |
awardee_namestring | 23% | With `awardee_uei`, `awardee_cage_code` and the awardee's address. |
description_htmlstring | 84.3% | The full solicitation text, with `description_text` alongside. |
attachmentsobject[] | not measured | Each entry carries `kind` (`file` or `link`), `name`, `size_bytes`, `mime_type` and a stable unsigned `url`. |
file_countinteger | not measured | Downloadable documents. Separate from `link_count` on purpose: a link to an agency portal is not the solicitation. |
from_cacheboolean | 100% | With `fetched_at` and `data_age_hours`. Notices are immutable, so cached rows stay correct. |
Every key is always present. A field that exists but is empty comes back as explicit null, so a parser never has to guess.
Datasets
Different record types go to different datasets, so the main table never carries columns that are blank on most rows.
defaultOne row per contract notice, 62 fields, deduplicated by `notice_id`.billedexclusionsFederal debarment records, when `maxExclusions` is above 0.billedERRORS (key-value store)Anything that failed, with the reason.never billed
Pricing
Pay per delivered result. Charges are applied as each row is produced rather than in a lump at the end, so an aborted run bills only for what it actually gave you.
| Event | Price | Notes |
|---|---|---|
actor-startActor start | $0.00001 | Effectively free. A run that finds nothing costs you nothing. |
noticeContract notice | $0.0025 | One notice with 62 fields, including the full solicitation text. Never charged for a duplicate or an error row. |
attachmentSolicitation document | $0.0005 | One attachment listed with its name, size, MIME type and a stable download URL. Only charged when attachments are enabled. |
exclusionExclusion record | $0.0005 | One federal debarment record with agency, dates, classification and address. |
Measurements
Each figure is shown with the method that produced it. A benchmark without a method is a marketing claim wearing a number's clothes.
Fill-rate sample
13,434 notices
Five days spread over ten months. Nothing is listed as a field unless it was measured.
Notices carrying a document
36.7% ± 4.6 of active notices
Stratified sample of 829 notices across 70 agency × notice-type cells, covering 96.7% of the active index. Average file size 1.5 MB.
By notice type
Sources Sought 60%, Solicitation 49%, Award 14%
Same stratified sample. Special Notice 56%, Justification 52%, Combined Synopsis 39%, Presolicitation 32%.
By agency
VA ~100%, GSA ~90%, DoD ~28%
DoD is 63% of the whole index and posts much of its documentation on its own portals, which is what drags the overall rate down — so agency matters more than notice type.
Deadline precision
61.0% instant, 38.8% date-only, 0.2% naive
1,566 notices with a deadline. SAM.gov stamps midnight UTC on date-only ones, which this Actor does not pass through as an instant.
Index size
5,591,183 notices, 49,702 active
Back to 2000, with about 2,500 published per calendar day. Sweeping is by publication date, one day per request, newest first.
Download integrity
Bytes match `size_bytes` exactly
Verified against the reported size. The URL is a 303 redirect to a short-lived signed link, so clients must follow redirects.
What it will not do
Stated plainly so you can judge fit before spending anything.
- Attachments that SAM.gov marks restricted, export-controlled, deleted or non-public are not returned at all — not even their URL, because publishing the URL to a controlled document is publishing the document. Checked against six flags with an allowlist, not a blocklist.
- There is no debarment lookup, deliberately: name-only matching against the exclusion list manufactures false positives about real people.
- `response_due_utc` is null on the 38.8% of deadlines that are date-only, and on the 0.2% that carry no zone.
- Award fields sit at ~23% of all notices because most notices are not awards. Within award notices they are effectively always present.
- Attachment download URLs are 303 redirects to short-lived signed links, so a client has to follow redirects and finish the download promptly.
- 50,000 notices per run is the hard cap.
Privacy
- The data is public by law: U.S. Government works are not subject to copyright under 17 U.S.C. § 105, and FAR Part 5 requires agencies to publish these notices so that businesses can find them.
- Contact details in a notice are the contracting officer's published business contacts, returned as the agency published them.
- Restricted, export-controlled, deleted and non-public attachments are excluded entirely, URL included, via an allowlist over six separate flags.
- No debarment lookup is offered, because 63% of the exclusion list is individuals and name-only matching would assert that a real person is debarred when they are not.
- Data policy and removal requests: samgov.actorstack.dev, or privacy@actorstack.dev
See also the data removal process.
Frequently asked questions
Do I need a SAM.gov API key?
Can I download the solicitation documents?
How many notices actually have documents?
Why are there three deadline fields?
Can I check whether a vendor is debarred?
Is scraping SAM.gov allowed?
How far back does the archive go?
Guides for this Actor
- Scrape SAM.govA walkthrough of extracting U.S. federal contract notices: which filters actually narrow the index, how to get the solicitation documents, and why there are three deadline fields.
- The API key questionSeveral SAM.gov scrapers require you to register for an api.sam.gov key. That moves the quota problem, the approval wait and the renewal onto you — for data that is public either way.
- Solicitation documentsMeasured on a stratified sample covering 96.7% of the active index: Veterans Affairs posts documents on nearly every solicitation, the Department of Defense on about a quarter — and DoD is 63% of the index.
- Deadline precisionSAM.gov stamps midnight UTC on deadlines that are date-only. Passing that through as an instant would present a precision that does not exist, so it is null instead.
- SAM.gov data fields62 fields, snake_case, null where a value is genuinely absent and never a missing key — with coverage measured across five days spread over ten months.
- Set-aside codesSBA, 8(a), WOSB, SDVOSB, HUBZone — the codes that reserve a contract for a category of small business, present on 46.1% of notices.
- NAICS vs PSCTwo classification systems on the same notice, describing different things. One is better filled, and the difference decides which one your pipeline should key on.
- Exclusions and debarment63% of the federal exclusion list is individuals and only about a fifth of those carry a UEI. A name-matching lookup would manufacture false positives about real people.
- Opportunity monitoringA working monitoring pipeline: what to filter, how often to run, which deadline field to schedule against, and where the attachment rate changes your plan.
- Is it legal?Federal notices are published because the law requires agencies to publicise them, and U.S. Government works carry no copyright. That settles more than usual — and not everything.