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 contractsGET /api/ebuy/requests/— list, with filtering, search, ordering and paginationGET /api/ebuy/requests/{rfq_id}/— one request, with its attachmentsGET /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 itsstatussays. close_dateis 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_openflag 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¶
?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_datelast_seenmodified
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(*)andattachments(*)
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 tonull.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 itsdoc_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_pathis 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
statusis 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
shapefields return HTTP 400 with structured validation errors.