SAM.gov API & Scraper
The SAM.gov API returns US federal contract opportunities as clean JSON, with no api_key and no registration.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
The primary search endpoint returns opportunities with id, url, title, solicitation_number, the notice type (sources sought, solicitation, special notice, award), a description preview, published_at and modified_at, the response_deadline with its local time and timezone, the is_active and is_cancelled flags, the buying office as department, office, office_code, office_location and the full org chain, the awardee where one exists, and a relevance_score — plus total_matches and an offset for paging. opportunity expands one notice into the full solicitation body, set_aside, naics_codes, product_service_code, place_of_performance and the published contacts with name, email and phone. attachments lists the notice's documents with name, kind, mime type, size and URL. One ReefAPI key, one shared credit pool, the standard envelope.
The parameters that actually change your result count
Four parameters move the numbers here, and one of them behaves differently from the other three because it is applied after fetching rather than upstream. Knowing which is which stops you from reading total_matches as a filtered count. All figures measured on 2026-08-27.
| Parameter | What it does | Measured effect |
|---|---|---|
| q with phrase true (the default) | Quotes your term so it matches as one phrase | 'web scraping' returned total_matches 11, and query_sent echoed the quoted form |
| phrase false | Every word anywhere in the notice, in any order | The same query returned total_matches 76 |
| active_only true | Applied upstream. Removes anything past its deadline or archived | 11 matches became 1 |
| published_within_days | Applied AFTER fetching, so total_matches stays unfiltered | 'data extraction' held total_matches 485 while filtered_out reported 91 rows removed |
| notice_types | Nine codes. An unknown one is rejected, not ignored | ['zz'] returned INVALID_PARAM listing p, k, r, o, g, s, i, a, u |
| offset plus limit | Hard ceiling of 10,000, the index's own maxAllowedRecords | offset 9999 with limit 2 returned INVALID_PARAM |
| sort newest | Applied locally after fetching, because upstream date sorting is broken | meta echoes upstream_sort '-modifiedDate' and upstream_date_filter_supported false |
| id | The 32-hex notice id, which is NOT the solicitation number | ffabe21b876649d2a1669edb4858cd48 for solicitation W91ZLK-26-A-0123D |
| description_preview | 250 characters on search rows only | The same notice's full body from `opportunity` ran 3,642 characters |
| NAICS and PSC | Only on `opportunity`, never on a search row | naics_codes ['518210'] (an array of 6-digit strings), product_service_code 'B506' (a single 4-character code) |
Two notices can carry near-identical solicitation numbers and be different records. A single search returned W91Zlk-26-A-01234D as a Sources Sought and W91ZLK-26-A-0123D as a Special Notice, same buying office, same project. The 32-hex id is the only safe key: use it for deduplication and for every follow-up call.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/sam-gov/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"q": "web scraping",
"limit": 5
}
}{
"ok": true,
"meta": {
"api": "sam-gov",
"endpoint": "search",
"mode": "live",
"latency_ms": 385.4,
"record_count": 5,
"bytes": 20874,
"cache_hit": false,
"stop_reason": "limit_reached",
"method": "keyless_public_json_api_bare_dc_curl_cffi",
"sort_applied": "newest",
"upstream_sort": "-modifiedDate",
"upstream_date_filter_supported": false,
"active_only": false,
"notice_types": null,
"max_allowed_records": 10000
},
"data": {
"opportunities": [
{
"id": "665b0e5ea7794aa78e2bc60b45d19d0b",
"url": "https://sam.gov/opp/665b0e5ea7794aa78e2bc60b45d19d0b/view",
"title": "Syndicated POINT OF SALE (POS) DATA PLATFORM (COS)",
"solicitation_number": "W91Zlk-26-A-01234D",
"notice_type": "Sources Sought",
"notice_type_code": "r",
"description_preview": "DECA‑26 – Syndicated Data Core Services\n\nProject Number:\n W91ZLK‑26‑R‑SDCS\n\nAOS Number:\n W9128Z‑25‑S‑A002\n\nPSC:\n B506 – Special Studies/Anal",
"published_at": "[redacted-phone]T19:23:01Z",
"modified_at": "[redacted-phone]T19:23:01Z",
"response_deadline": "[redacted-phone]T15:00:00Z",
"response_deadline_local": "[redacted-phone]T11:00:00-04:00",
"response_timezone": "America/New_York",
"is_active": true,
"is_cancelled": false,
"buyer_name": "W6QK ACC-APG DIR",
"buyer": {
"department": "DEPT OF DEFENSE",
"office": "W6QK ACC-APG DIR",
"office_code": "W91ZLK",
"office_location": "ABER PROV GRD, MD, USA",
"chain": [
"[trimmed-depth]",
"[trimmed-depth]",
"[trimmed-depth]"
]
},
"awardee": null,
"relevance_score": 52,
"revisions": 0
},
{
"id": "ffabe21b876649d2a1669edb4858cd48",
"url": "https://sam.gov/opp/ffabe21b876649d2a1669edb4858cd48/view",
"title": "Syndicated Point of Sale (POS) Data Platform (COS)",
"solicitation_number": "W91ZLK-26-A-0123D",
"notice_type": "Special Notice",
"notice_type_code": "s",
"description_preview": "DECA‑26 – Syndicated Data Core Services\n\nProject Number:\nW91ZLK‑26‑R‑SDCS\n\nAOS Number:\nW9128Z‑25‑S‑A002\n\nPSC:\nB506 – Special Studies/Analysis – Da",
"published_at": "[redacted-phone]T15:58:14Z",
"modified_at": "[redacted-phone]T05:07:15Z",
"response_deadline": "[redacted-phone]T17:00:00Z",
"response_deadline_local": "[redacted-phone]T13:00:00-04:00",
"response_timezone": "America/New_York",
"is_active": false,
"is_cancelled": false,
"buyer_name": "W6QK ACC-APG DIR",
"buyer": {
"department": "DEPT OF DEFENSE",
"office": "W6QK ACC-APG DIR",
"office_code": "W91ZLK",
"office_location": "ABER PROV GRD, MD, USA",
"chain": [
"[trimmed-depth]",
"[trimmed-depth]",
"[trimmed-depth]"
]
},
"awardee": null,
"relevance_score": 54,
"revisions": 2
},
{
"id": "1f316d9815db41ebb3f6451bfafc9cd8",
"url": "https://sam.gov/opp/1f316d9815db41ebb3f6451bfafc9cd8/view",
"title": "hCaptcha Enterprise and Premium Support",
"solicitation_number": "SS-FIN-26-027",
"notice_type": "Special Notice",
"notice_type_code": "s",
"description_preview": "The Treasury Common Services Center (TCSC) on behalf of the Financial Crimes Enforcement Network (FinCEN) intends to make an award on a sole source basis to Intuition Machines, Inc.; 1065 SW 8th Street #704; Miami, FL 33130 for the purchase of exi",
"published_at": "[redacted-phone]T13:54:03Z",
"modified_at": "[redacted-phone]T03:55:44Z",
"response_deadline": "[redacted-phone]T17:00:00Z",
"response_deadline_local": "[redacted-phone]T13:00:00-04:00",
"response_timezone": "America/New_York",
"is_active": false,
"is_cancelled": false,
"buyer_name": "TREASURY, DEPARTMENT OF THE",
"buyer": {
"department": "TREASURY, DEPARTMENT OF THE",
"office": "TREASURY, DEPARTMENT OF THE",
"office_code": "2000",
"office_location": "US",
"chain": [
"[trimmed-depth]"
]
},
"awardee": null,
"relevance_score": 58,
"revisions": 0
}
],
"total_matches": 11,
"returned": 5,
"offset": 0,
"query_sent": "\"web scraping\"",
"phrase": true,
"description_truncated": true,
"description_preview_chars": 250,
"source": "sam.gov"
}
}What the SAM.gov API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search every US federal contract opportunity by phrase — the demand side of a $700bn/year buyer. Each hit carries the response deadline, the buying department and office, the notice type (is this a heads-up, a live RFP, or an award?) and a 250-character preview of the solicitation text. Pair with `active_only=true` for things you can still bid on, or with notice type 'a' to see who won what. | GovCon business-development teams call search to search every US federal contract opportunity by phrase. | q, phrase, active_only, notice_types, published_within_days, ... |
| opportunity | One notice in full: the COMPLETE solicitation text (search only ever returns the first 250 characters), the response deadline with its timezone, the set-aside type, NAICS and PSC classification, place of performance, and the contracting officer's name, e-mail and phone as published on the notice. | Bid and capture teams call opportunity to get one notice in full. | id |
| attachments | The documents attached to a notice — the statement of work, the pricing sheet, the Q&A, the amendment. Returns each file's name, MIME type, size, posting date and a direct public download URL. The real requirement usually lives in these PDFs, not in the notice body. | Public-sector analysts call attachments to get the documents attached to a notice. | id |
Call search from your stack
curl -X POST https://api.reefapi.com/sam-gov/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"q":"web scraping","limit":5}'import requests
r = requests.post(
"https://api.reefapi.com/sam-gov/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"q": "web scraping",
"limit": 5
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/sam-gov/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"q": "web scraping",
"limit": 5
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.sam-gov.search with {"q":"web scraping","limit":5}.Who uses this API and why
- GovCon business-development teams call search to catch new sources-sought notices and solicitations in their space as they publish.
- Bid/no-bid workflows use opportunity to read the full solicitation body, the set-aside type and the response deadline before committing.
- Capture teams use the contacts published on an opportunity to reach the contracting office directly.
- Competitive analysts follow awardee across award notices to see who is winning work under a given product-service code.
Questions developers ask before integrating
What is the difference between the notice id and the solicitation number?
The id is a 32-character hex string minted by the system and unique per notice, such as ffabe21b876649d2a1669edb4858cd48. It is what `opportunity` and `attachments` take, and it is what appears in the sam.gov/opp/<id>/view URL, so pasting that whole URL works too. The solicitation number is the buying office's own reference, and it is neither unique nor consistently formatted: one search returned W91Zlk-26-A-01234D and W91ZLK-26-A-0123D as two separate notices for the same project. Key on the id.
Why is my exact-phrase search returning so few results?
Because phrase matching is the default and it is doing its job. 'web scraping' returned 11 notices as a quoted phrase and 76 with phrase false, which loosens to every word anywhere in any order. The response echoes back exactly what was sent upstream in query_sent, so you can always confirm which mode you got. Start with the phrase for precision, and switch to phrase false when you are casting a wide net over a category rather than hunting one term.
Why does total_matches stay the same when I set published_within_days?
Because that filter runs here, after the rows arrive, rather than upstream. A search for 'data extraction' with published_within_days 90 kept total_matches at 485, the unfiltered upstream count, and reported filtered_out 91 for the rows removed locally. The practical consequence: the parameter narrows what you receive but does not reduce what was fetched, so treat total_matches as the size of the unfiltered match set and use returned plus filtered_out to understand your page. active_only is the opposite case; it runs upstream and cut 11 matches to 1.
Which notice-type codes should I ask for?
Nine exist and they answer different questions. 'r' Sources Sought means the buyer is still asking who can do this, which is the cheapest door for a new vendor. 'p' Presolicitation announces an upcoming requirement. 'o' Solicitation and 'k' Combined Synopsis/Solicitation are the live RFPs you can bid on. 's' Special Notice covers industry days and RFIs. 'a' Award Notice tells you who won and therefore who the incumbent is. 'u' Justification and Approval explains a sole-source award, 'i' covers bundling intent, 'g' surplus property sales. An unrecognized code is rejected with the full list in the error rather than being silently dropped.
How are deadlines and timezones represented?
Three fields that agree with each other. response_deadline is UTC with a Z suffix, response_deadline_local is the same instant carried with its offset, and response_timezone is an IANA name such as America/New_York. A deadline measured as 2026-08-18T15:00:00Z came back locally as 2026-08-18T11:00:00-04:00. Award notices return all three as null, on 3 of 3 measured, because an award has no response deadline. archive_date is a bare date with no time at all.
What tells me whether a notice is still live?
Three independent booleans, and they are not redundant. is_active is the one to filter on for biddability. is_archived says the record has aged out of the active index, and it pairs with archive_date. is_cancelled says the buyer withdrew the requirement, which is different from letting it expire. One measured notice returned is_active false with is_archived true and archive_date 2026-08-18, alongside revision '2' (a string, not a number) which counts amendments. The search row for the same notice reported revisions 2 as an integer, so watch the type when you compare them.
How do I reach the contracting officer?
The `opportunity` action returns a contacts[] array as published on the notice, typed as primary and secondary. A measured notice returned a primary contact with a name, a .mil e-mail address and a phone number as bare digits with no formatting or extension separator, plus a secondary contact whose phone was null. title was null on both. Two contacts on one notice is common and the primary is the one to write to; the secondary is usually the specialist who will actually answer.
How do I get the actual statement of work?
Call `attachments` with the same notice id. It returned two files for a measured notice, each with a name, kind, size_bytes, posted_at and a direct public download URL under the sam.gov file-resource path, with access 'public'. The real requirement usually lives in those PDFs rather than in the body. One shape to expect: the `mime` field carries a file extension such as '.pdf' rather than a MIME type, so branch on the extension or on the name, not on a media type string.
What is the SAM.gov API?
SAM.gov API is a ReefAPI endpoint group for us federal contract opportunities, response deadlines and award notices. It returns live JSON through POST requests under /sam-gov/v1.
Is the SAM.gov API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. SAM.gov calls use the same shared credit balance as every other ReefAPI engine.
Do I need a SAM.gov login or account?
No login to SAM.gov is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the SAM.gov data?
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the SAM.gov API use?
SAM.gov actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call SAM.gov from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call sam-gov actions with the same key, credit pool and JSON envelope used by normal REST requests.