Opportunities¶
Opportunities are SAM.gov opportunities/solicitations exposed at /api/opportunities/ (with a latest-notice pointer and history behind the scenes). For field definitions, see the Opportunities Data Dictionary.
Endpoints¶
GET /api/opportunities/(list + filtering + search + ordering)GET /api/opportunities/{opportunity_id}/(detail)
Notice UUID Resolution¶
If you pass a notice UUID (instead of an opportunity UUID) to the detail endpoint, the API automatically redirects (HTTP 302) to the parent opportunity. This is useful when you have a subsequent notice UUID (e.g., from an amendment) and want to reach the parent opportunity directly.
# Using a notice UUID redirects to the parent opportunity
GET /api/opportunities/{notice_id}/ → 302 /api/opportunities/{opportunity_id}/
If the notice UUID has no parent opportunity, the endpoint returns 404.
Filtering¶
Swagger is the canonical list of query parameters. These are the core filters most people use day-to-day. All date filters require YYYY-MM-DD format; invalid dates or inverted ranges return 400. See Date filters.
| Param | What it does |
|---|---|
search | Full-text search over opportunities (vector-backed). |
agency | Filter by agency or department (vector-backed). |
naics | Filter by NAICS. |
psc | Filter by PSC. |
set_aside | Filter by set-aside. |
notice_type | Filter by notice type (valid values are validated). |
solicitation_number | Filter by solicitation number. Matches every spelling of the number (N6600120R0002 or N66001-20-R-0002, any case). Multi-value: use | for OR. |
first_notice_date_after, first_notice_date_before | First notice date range (inclusive, YYYY-MM-DD). |
last_notice_date_after, last_notice_date_before | Last notice date range (inclusive, YYYY-MM-DD). |
response_deadline_after, response_deadline_before | Response deadline range (inclusive, YYYY-MM-DD). |
place_of_performance | Full-text-ish filter over place-of-performance text. |
active | Filter active/inactive (default true). |
awarded | true for opportunities that carry an award or have award notices linked back to them; false for the rest. See Awards. |
awardee_uei | Filter by the awardee UEI on the opportunity's own award notice. Case-insensitive. Multi-value: use | for OR. |
Active-only by default¶
A list request returns active opportunities only unless it sends active, solicitation_number, notice_type, award_number, awarded or awardee_uei — any of which widens the request (including search) to the full dataset. Send active=true alongside them to keep the active-only scope.
Multi-value filter syntax¶
Use | (or the literal word OR) to match any of several values:
naics=541511|541512
agency=DOD|GSA
The same syntax works on every filter that accepts multiple values.
Ordering¶
Opportunities support ordering= with a strict allowlist:
first_notice_date(alias:posted_date)last_notice_dateresponse_deadline
Examples:
- Newest posted first:
GET /api/opportunities/?ordering=-posted_date - Soonest deadline first:
GET /api/opportunities/?ordering=response_deadline
Note: if you use search without an explicit ordering=..., results default to relevance ordering (rank).
Pagination¶
Opportunities use page-number pagination (page, limit) and return next/previous URLs.
Response Shaping¶
Opportunities support the ?shape= query parameter. When no ?shape= is provided, the endpoint returns a default shape.
Default shape (list): active,award_number,awarded,first_notice_date,last_notice_date,meta(*),naics_code,office(*),opportunity_id,place_of_performance(*),psc_code,response_deadline,sam_url,set_aside,solicitation_number,title
Default shape (detail): active,attachments(attachment_id,file_size,mime_type,name,posted_date,resource_id,type,url),award_amount,award_count,award_date,award_number,awarded,awardee,awardee_uei,awards(*),description,first_notice_date,last_notice_date,meta(*),naics_code,notice_history(*),office(*),opportunity_id,place_of_performance(*),primary_contact(*),psc_code,response_deadline,sam_url,set_aside,solicitation_number,solicitation_opportunity_id,title
Compared with the list default, the detail default adds attachments(...), the award fields (see Awards), description, notice_history(*), primary_contact(*) and solicitation_opportunity_id. The default attachments(...) expansion lists attachment_id, file_size, mime_type, name, posted_date, resource_id, type, url — note that the doc_role, doc_role_alt and extracted_text sub-fields are Pro+ tier and are not in the default. Free-tier callers who request one receive the response without it plus a meta.upgrade_hints entry. None of the three is returned by attachments(*) — name them explicitly.
Not every attachment is a document. Each row's type is file (a document, the only kind with extracted text and a document role) or link (a URL pointing somewhere else), and meta.attachments_count counts both. From API version 5.9.0, meta(*) also carries files_count and links_count. meta.files_count greater than 0 means the opportunity has documents, 0 means it has none, and null means not yet counted. See Attachments: files and links for the definitions, the null case and a worked example.
Document roles¶
doc_role says what job a document does inside its solicitation package, so you can decide which attachments are worth opening (or worth sending to a model) without downloading them:
| Value | What it means |
|---|---|
requirement | What the government wants — statements of work, specifications, drawings of the work to be performed |
instructions | How to bid — submission instructions, forms to fill in, evaluation criteria |
pricing | Where the money goes — schedules, line-item worksheets, wage determinations |
terms | The deal's conditions — clauses, representations and certifications, contract terms |
reference | Context rather than obligation — existing-conditions drawings, amendments' cover pages, background material |
unknown | The classifier could not read enough of the document to decide |
doc_role_alt carries a runner-up label when the classifier saw a close second, and is absent otherwise. An attachment the classifier has not reached yet omits both keys rather than returning null.
GET /api/opportunities/{id}/?shape=title,attachments(name,doc_role)
Awards¶
SAM.gov posts a contract award as a separate award notice, which appears in Tango as its own opportunity. The award details (award_date, award_amount, awardee, awardee_uei) are filled only where SAM has posted an award notice and are null otherwise.
| Field | What it holds |
|---|---|
awarded | Boolean. true when the opportunity carries an award number or has award notices linked back to it; never null. |
award_number, award_date, award_amount, awardee, awardee_uei | The award as posted on this opportunity's own award notice. award_date, award_amount, awardee and awardee_uei always come from the same notice. award_amount is a string, as SAM publishes it. |
solicitation_opportunity_id | On an award, the opportunity_id of the solicitation it was made under; null when the award is unlinked. |
award_count | On a solicitation, the total number of award opportunities linked back to it (not capped); null when none are linked. |
awards(...) | On a solicitation, up to ten of the most recent linked awards, each {opportunity_id, notice_id, award_number, award_date, award_amount, awardee, awardee_uei}. Use award_count for the total. |
An award is linked to its solicitation only when SAM's award notice references that solicitation. An award notice posted without that reference stays unlinked: it still carries its own award fields, but has no solicitation_opportunity_id and does not count toward any solicitation's award_count.
awarded does not change what active means. An award can be posted while its solicitation is still open — multiple-award vehicles are the common case — so an opportunity can be both active and awarded.
# Awards made to one vendor, across the full dataset
/api/opportunities/?awardee_uei=ABC123XYZ456&shape=opportunity_id,title,award_date,award_amount,solicitation_opportunity_id
# A solicitation and the awards linked to it
/api/opportunities/{opportunity_id}/?shape=opportunity_id,title,awarded,award_count,awards(*)
The office(*) expansion is the canonical 7-key office payload — organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name — same shape used on awards, vehicles, forecasts, grants, IT Dashboard, and protests.
set_aside is available as a leaf (returns the code string) or as an expansion set_aside(code,description) for the full label.
See Response Shaping for syntax and examples.
Opportunities shaping notes¶
- New leaves:
latest_notice_id,archive_date, plus relation id leavesagency_id,department_id,office_id. - New expansions:
agency(*),department(*),latest_notice(notice_id,link). - File and link counts:
meta(files_count,links_count)alongsidemeta(attachments_count), from API version 5.9.0. See Attachments: files and links. - Award leaves and expansion:
awarded,award_date,award_amount,awardee,awardee_uei,award_count,solicitation_opportunity_id, andawards(opportunity_id,notice_id,award_number,award_date,award_amount,awardee,awardee_uei). See Awards. - Bare expand shorthand: common expansions can be requested without parentheses when the runtime must expand them for safety (e.g.,
officebehaves likeoffice(*), andattachmentsbehaves likeattachments(*)).
Examples:
# Include latest notice pointer + org expansions
/api/opportunities/?shape=opportunity_id,title,latest_notice_id,latest_notice(*),agency(*),department(*),office&limit=1
# Attachments without specifying subfields (shorthand)
/api/opportunities/{opportunity_id}/?shape=opportunity_id,title,attachments
SDK examples¶
See also: Full SDK method reference — tango-python methods · tango-node methods.
Search opportunities (with shaping)¶
import os
from tango import ShapeConfig, TangoClient
client = TangoClient(api_key=os.environ["TANGO_API_KEY"])
resp = client.list_opportunities(
search="cybersecurity",
agency="DOD",
limit=10,
shape=ShapeConfig.OPPORTUNITIES_MINIMAL,
)
for o in resp.results:
print(o.opportunity_id, o.title)
import { ShapeConfig, TangoClient } from "@makegov/tango-node";
const client = new TangoClient({ apiKey: process.env.TANGO_API_KEY });
const resp = await client.listOpportunities({
search: "cybersecurity",
agency: "DOD",
limit: 10,
shape: ShapeConfig.OPPORTUNITIES_MINIMAL,
});
for (const o of resp.results) {
console.log(o.opportunity_id, o.title);
}