ActorStack.dev
Lead generationv0.1.9updated 19 August 2026

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

input.json
{
  "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.
On this page11 sections

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.

output — one row
{
  "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.

FieldDefaultWhat it does
maxResultsinteger100Maximum noticesYou are charged per notice delivered, never for duplicates and never for error rows. Hard cap 50,000 per run.
activeOnlybooleantrueOnly active noticesAbout 49,700 notices are active. Turn this off to reach the archive of 5.6 million going back to 2000.
publishedWithinDaysinteger7Published 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.
scrapeAttachmentsbooleanfalseAlso fetch solicitation documentsAdds the attachment list with name, size, MIME type and a stable URL. One extra request per notice.
maxExclusionsinteger0Maximum 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.
maxCacheAgeDaysinteger90Maximum 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.
maxConcurrencyinteger4Maximum 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.

FieldFilledMeaning
notice_idstring100%SAM.gov notice identifier. The dedupe key, so you are never charged twice for it.
solicitation_numberstring99.5%With `solicitation_number_clean` alongside.
titlestring100%Notice title as published.
notice_typestring100%With `notice_type_code`: `o` solicitation, `k` combined, `r` sources sought, `a` award.
is_activeboolean100%With `is_canceled` and `modification_count`.
published_atdatetime100%With `modified_at`, `archive_at` and `original_published_at`.
response_due_localstring75.2%The deadline as the notice states it. This is the one to display.
response_due_utcdatetime61%A true UTC instant, but only when `response_due_precision` is `instant` — 61.0% of deadlines. Null otherwise.
response_due_precisionstring75.2%`instant` 61.0%, `date_only` 38.8%, `local_naive` 0.2%. Read it before converting a deadline.
naics_codestring96.1%With `naics_title` and `naics_all`.
psc_codestring97.8%With `psc_title` and `psc_all`. Better filled than NAICS.
set_aside_codestring46.1%With `set_aside`. Present on fewer than half of notices.
agency_namestring99.4%With `agency_id`, `agency_code` and the full `agency_hierarchy`.
contact_namestring99.6%Contracting officer, with `contacts_all` for every contact on the notice.
contact_emailstring99.2%Published business email of the contact.
contact_phonestring32.7%The weakest of the contact fields by a wide margin.
pop_statestringnot measuredPlace of performance, with country, city and ZIP. Only about a third of notices state one.
award_amountnumber23%About 23% of all notices, because most notices are not awards. Within award notices it is effectively always present.
awardee_namestring23%With `awardee_uei`, `awardee_cage_code` and the awardee's address.
description_htmlstring84.3%The full solicitation text, with `description_text` alongside.
attachmentsobject[]not measuredEach entry carries `kind` (`file` or `link`), `name`, `size_bytes`, `mime_type` and a stable unsigned `url`.
file_countintegernot measuredDownloadable documents. Separate from `link_count` on purpose: a link to an agency portal is not the solicitation.
from_cacheboolean100%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`.billed
  • exclusionsFederal debarment records, when `maxExclusions` is above 0.billed
  • ERRORS (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.

EventPriceNotes
actor-startActor start$0.00001Effectively free. A run that finds nothing costs you nothing.
noticeContract notice$0.0025One notice with 62 fields, including the full solicitation text. Never charged for a duplicate or an error row.
attachmentSolicitation document$0.0005One attachment listed with its name, size, MIME type and a stable download URL. Only charged when attachments are enabled.
exclusionExclusion record$0.0005One 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?
No. Several SAM.gov scrapers ask you to register for an api.sam.gov key, wait for approval, and then own the problem when its quota runs out. This Actor talks to the same public endpoint the SAM.gov search page itself uses, so there is nothing to register and nothing to renew.
Can I download the solicitation documents?
Yes. Enable attachments and every notice gains an `attachments[]` array with the file name, size, MIME type and a stable unsigned URL that keeps working after the run. It is a 303 redirect to a short-lived signed link, so use `curl -L` and finish the download promptly. Verified: the bytes match the reported `size_bytes` exactly.
How many notices actually have documents?
36.7% ± 4.6 of active notices carry at least one downloadable file, measured on a stratified sample of 829 notices covering 96.7% of the active index. It varies more by agency than by notice type: roughly 100% at Veterans Affairs, ~90% at GSA, ~28% at the Department of Defense — which is 63% of the index and posts much of its documentation on its own portals.
Why are there three deadline fields?
Because SAM.gov's response date is not always a real instant. 61.0% carry a true offset, 38.8% say "due on the 25th" with no time, and SAM.gov stamps midnight UTC on those. 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.
Can I check whether a vendor is debarred?
Not with this Actor, deliberately. The exclusion list is available as a dataset, but 63% of it is individuals and only about 20% of those carry a UEI — so matching by name alone manufactures false positives, and a false positive here asserts that a real person is debarred when they are not.
Is scraping SAM.gov allowed?
The notices are published so that businesses can find them: FAR Part 5 requires agencies to publicise them, and U.S. Government works are not subject to copyright under 17 U.S.C. § 105. This Actor also fetches robots.txt on every run and enforces it in the request layer, and keeps to 4 concurrent requests with a one-second delay.
How far back does the archive go?
To 2000 — 5,591,183 notices, of which 49,702 are active right now. Turn off `activeOnly` to reach the archive, and note that sweeping is by publication date at about 2,500 notices per calendar day.

Guides for this Actor