Skip to content

eBuy

GSA eBuy requests — the RFQs, RFPs and RFIs agencies post to GSA Schedule holders — are exposed at /api/ebuy/requests/. For field definitions, see the eBuy Data Dictionary.

eBuy is scoped to your own contracts

eBuy is not public data. GSA shows a Schedule holder only the requests posted under the SINs on that holder's own contracts, and Tango keeps the same boundary: you see a request only while your account is linked to a contract it was posted under.

Access needs two things: a paid plan, and a contract link that MakeGov sets up for your account. Reach out to get started. /api/ebuy/access/ tells you which of the two you have.

Authentication: eBuy endpoints require authentication (API key or OAuth2). Unauthenticated requests receive HTTP 401.

Endpoints

  • GET /api/ebuy/access/ — whether eBuy is enabled for your account, and through which contracts
  • GET /api/ebuy/requests/ — list, with filtering, search, ordering and pagination
  • GET /api/ebuy/requests/{rfq_id}/ — one request, with its attachments
  • GET /api/ebuy/requests/{rfq_id}/attachments/{doc_seq_num}/download/ — download one attachment

The detail path segment is the eBuy request id, e.g. RFQ1835158.

Checking your access

Without a contract link, /api/ebuy/requests/ returns an empty list — the same answer as a search with no matches. GET /api/ebuy/access/ tells the two apart:

{
  "enabled": false,
  "reason": "no_contract_grant",
  "contracts": []
}
Field Meaning
enabled true when your plan includes eBuy and your account has at least one active contract link.
reason Why enabled is false: tier_required (your plan does not include eBuy) or no_contract_grant (no contract link yet). tier_required wins when both apply. null when enabled.
contracts The GSA Schedule contract numbers linked to your account, sorted. Only ever your own.

What you can see

  • A request outside your contracts does not exist, as far as the API is concerned. It is absent from the list, and its detail URL returns 404 — the same answer as an id that was never issued. Nothing in a response tells you that a request exists under someone else's contract.
  • Access follows the link, not the moment you fetched. Every call re-checks your contract links. If a link is removed, the requests behind it disappear from the list and their detail and download URLs start returning 404.
  • No response names which contract a request was posted under. ?contract_number= narrows to one of your own contracts, but the contract is never a field on the request.

A contract can be linked to you directly or to your account organization. Your access is the union of the two, so everyone in an organization sees the requests under the organization's linked contracts.

status is frozen

status is stored exactly as eBuy supplied it — Open or Cancelled — and is never recomputed.

eBuy's feed carries only the requests that are currently active. A request that closes simply stops appearing; it never arrives with a final closed status. So an Open request may have closed months ago.

  • Read last_seen — when Tango last observed the request in eBuy — to judge whether it is still live. A request seen this week is current; one last seen in the spring is not, whatever its status says.
  • close_date is the buyer's stated deadline, and ?close_date_after= is the practical way to ask for requests that have not yet closed.
  • There is deliberately no is_open flag and no ?open= filter: a flag derived from the clock would call a months-dead request open.

Filtering

Most filters accept | (or the word OR) for OR, and match case-insensitively. Date filters require YYYY-MM-DD; invalid dates or inverted ranges return 400 (see Date filters). The _before date filters include the whole of that day.

Param What it does
search Ranked full-text search — see Search. Multi-value: use \| for OR.
rfq_id Exact match on the eBuy request id, e.g. RFQ1835158. Multi-value: use \| for OR.
reference_number The buying office's own solicitation or reference number. Matches every spelling variant, dashed and undashed. Multi-value: use \| for OR.
request_type RFQ, RFP or RFI. Multi-value: use \| for OR.
status Open or Cancelled — as supplied, and frozen. Multi-value: use \| for OR.
sin The Special Item Number the request was posted under, e.g. 54151S. Multi-value: use \| for OR.
schedule The GSA schedule the request was posted under. Multi-value: use \| for OR.
buyer_agency The buying department's name as eBuy gives it. Multi-value: use \| for OR.
agency Agency, resolved to the buyer's organization and rolled up its hierarchy, so a department includes the sub-agencies and offices beneath it. Matches by name, abbreviation or code (CGAC, FPDS, organization key or AAC), e.g. ?agency=GSA. Multi-value: use \| for OR. A value that resolves to no organization falls back to an exact match on buyer_agency and never returns 400. buyer_agency alone is the department name as eBuy gives it, with no roll-up.
contract_number Narrow to the requests under one or more of your own linked contracts. A contract your account is not linked to returns nothing, not an error. Multi-value: use \| for OR.
issue_date_after, issue_date_before Issue-date range (YYYY-MM-DD).
close_date_after, close_date_before Close-date range (YYYY-MM-DD).

?search= is a ranked full-text query over each request's title, description, reference number and request id, and the extracted text of its attachments — statements of work, pricing schedules and the like.

A match on the request itself always ranks above a match that is only in an attachment. Attachment text is long, repetitive contracting boilerplate, so a hit there is a weaker signal than a hit in what the buyer wrote; an attachment-only match is still returned, just after every direct match.

Under search= with no explicit ordering, results come back by relevance. An explicit ordering overrides it.

Ordering

ordering= allowlist:

  • issue_date — the default is -issue_date (newest first)
  • close_date
  • last_seen
  • modified

Results are tie-broken deterministically, so paging through a large result set never repeats or skips a request.

Sorting descending on close_date puts requests without a close date first. Pair it with close_date_after, which excludes them.

Pagination

Standard page-number pagination:

  • page (default 1)
  • limit (default 25, max 100)

Responses carry count / next / previous / results.

Rate limits

eBuy endpoints — list, detail and attachment downloads — count against your account's standard daily and per-minute rate limits, like every other endpoint.

Shaping

See Response shaping for the syntax.

  • List default: rfq_id, request_type, title, schedule, sin, status, buyer_name, buyer_agency, buyer_agency_code, reference_number, issue_date, close_date, attachment_count, link_count, last_seen
  • Detail default: every field, plus organization(*) and attachments(*)

Two expansions are available:

  • organization(*) — the buying office resolved to the canonical 7-key office payload (organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name), the same object used on awards and opportunities. Best-effort: some buyers eBuy lists are not federal agencies (the judiciary, Congress, state and local governments), and those resolve to null.
  • attachments(*) — doc_seq_num, doc_name, doc_type, doc_path, is_link, doc_session_date.

description is long-form and is in the detail default only; name it in ?shape= to get it on a list.

Attachments

GET /api/ebuy/requests/RFQ1835158/attachments/3/download/

A request's attachments include both documents and links, told apart by is_link:

  • A document (is_link: false) downloads through the endpoint above, addressed by its doc_seq_num. The response is a redirect to a download URL that expires after five minutes — fetch it right away, and request a new one rather than storing it.
  • A link (is_link: true) is an outbound URL with no file behind it; doc_path is the URL. The download endpoint returns 400 for a link, with the URL in the response body.

A download returns 404 when the document has not been captured yet, and — like every eBuy route — when the request is outside your contracts.

attachment_count counts every document ever observed on a request, including ones an amendment later removed from eBuy's own page, so it can exceed what eBuy currently shows.

Alerts

ebuy_request is an alertable query type — alerts.ebuy_request.match, using the same filters documented above. See the webhooks guide.

  • An alert only ever matches requests under your own contracts. Matching uses the contract links of the account that owns the webhook endpoint, re-read on every evaluation, so a removed link stops alerts on the next run.
  • An alert fires when a matching request appears or changes. A request closing does not fire, because a closed request simply stops appearing — see status is frozen.

Examples

# Is eBuy enabled for this key?
GET /api/ebuy/access/

# Open requests under one SIN
GET /api/ebuy/requests/?status=Open&sin=54151S

# Still-open requests closing in the next month
GET /api/ebuy/requests/?close_date_after=2026-10-01&close_date_before=2026-10-31&ordering=close_date

# Search titles, descriptions and attachment text
GET /api/ebuy/requests/?search=cybersecurity%20assessment

# Look a request up by the buyer's own number
GET /api/ebuy/requests/?reference_number=W912DY-26-Q-0012

# Only one of your own contracts
GET /api/ebuy/requests/?contract_number=47QTCA26D0001

# Detail with the buying office and attachments
GET /api/ebuy/requests/RFQ1835158/?shape=rfq_id,title,close_date,organization(*),attachments(doc_seq_num,doc_name,is_link)

Notes:

  • Invalid shape fields return HTTP 400 with structured validation errors.