Skip to content

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_date
  • response_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 leaves agency_id, department_id, office_id.
  • New expansions: agency(*), department(*), latest_notice(notice_id,link).
  • File and link counts: meta(files_count,links_count) alongside meta(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, and awards(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., office behaves like office(*), and attachments behaves like attachments(*)).

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);
}