Changelog¶
This is the most up-to-date change log for Tango and the Tango API.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Added¶
Changed¶
Deprecated¶
Breaking¶
Fixed¶
Internal / Infra¶
[5.9.1-7] - 2026-10-12¶
Fixed¶
- A list's
countno longer lags the data behind it — a count cached before new records loaded could be served for up to a day, so a search could return rows undercount: 0. Counts are now recomputed once a load lands.
[5.9.1-5] - 2026-10-12¶
Fixed¶
- More complete entity obligation totals —
federal_obligations.total.awards_obligatedcounts all years, including awards before FY2008 or without a funding agency, and thetotal_awards_obligated_gte/_ltefilters follow;total.subawards_countdrops subawards the source has removed.
[5.9.1-1] - 2026-10-10¶
Added¶
- eBuy attachments can return their extracted text — ask for it by name with
?shape=attachments(extracted_text)on/api/ebuy/requests/. It is never part of a default response orattachments(*), and an attachment with no text omits the key.
Changed¶
- IDV
gsa_elibraryno longer returnsextracted_textorsource_dataunless named —gsa_elibrary(*),gsa_elibrary()and the default IDV list and detail shapes drop both keys; ask for them withgsa_elibrary(extracted_text).
Fixed¶
- Intermittent 502 Bad Gateway responses — about 1 in 2,300 requests got a 502 from our load balancer before reaching the API; a connection-reuse mismatch between the load balancer and the API servers is fixed.
[5.9.0-6] - 2026-10-08¶
Fixed¶
- Account usage includes today — the Overview and Usage pages count today's requests as they happen instead of leaving them out until the next night's rollup.
[5.9.0-5] - 2026-10-07¶
Fixed¶
- With
search, an agency filter scopes to the organizationmeta.resolved_filtersnames — a token matching both an inactive and a current organization could filter by one while reporting the other.
[5.9.0-3] - 2026-10-07¶
Fixed¶
- More agency names resolve to the agency you meant.
Air Force,Coast Guard,ArmyandStateresolved to an office or an unrelated body;VA - U.S. Department of Veterans AffairsandOffice — Departmentvalues returned a400. agency=ATFreturns ATF's Federal Register documents.- Attachment counts on new opportunities —
meta.attachments_count,meta.files_countandmeta.links_countare set when an opportunity first syncs, instead of stayingnulluntil one of its attachments next changes.
[5.9.0-2] - 2026-10-06¶
Fixed¶
- A few opportunities were missing attachments — an attachment linked to a notice but not to the notice's opportunity was left off that opportunity's
attachmentslist and out of itsmetacounts. About two dozen attachments on 11 opportunities are restored.
[5.9.0-1] - 2026-10-06¶
Added¶
- Files and links are counted separately —
meta.files_count/meta.links_counton opportunities andfile_count/link_counton notices count the files attached to a notice and the links listed on it. Any other attachment type counts only in the total, so the two need not sum to it.
meta.resolved_filtersreports how each agency value matched. Every resolved entry carriesmatched_by:key,code,name,aliasorfuzzy. Afuzzymatch is the one to double-check against the organization name echoed beside it.
Changed¶
- Agency filters no longer guess at a name they cannot place. A value in
agency,awarding_agencyorfunding_agencythat is not a code, an abbreviation or a recognizable form of an organization's name used to resolve to the closest-looking organization and return its records. It now resolves to nothing: a400on most endpoints, and a text match on the endpoints that fall back to one. Checkmeta.resolved_filtersif a saved query built on a loose name stops matching, and use a code or the organization's key for a stable reference.
Fixed¶
- An agency's official name returns its records. 25 agencies, including the Agricultural Marketing Service and the Food and Nutrition Service, returned nothing when filtered by full name while their abbreviation worked.
- An agency name copied from a response resolves to that agency.
US CUSTOMS AND BORDER PROTECTION, asagency_nameprints it, resolved to an unrelated office. - An agency's abbreviation resolves to the agency.
NCD,PCLOB,ATFand several others resolved to an unrelated office.
[5.8.1-3] - 2026-10-06¶
Fixed¶
- Agency filters find more agencies by name and code. A department's everyday name (
Department of Veterans Affairs, and likewise Defense, State and Homeland Security) now works on everyagencyfilter, and/api/federal_register/?agency=now finds the FAA and the Army.
[5.8.1-1] - 2026-10-05¶
Changed¶
- Searching for an ID that matches nothing now returns no results —
?search=on/api/opportunities/and/api/notices/with a single award number or ID that appears nowhere used to return ten unrelated opportunities.
- Search results that contain your search terms now come before similar-but-not-matching results —
?search=on/api/opportunities/and/api/notices/lists matches first, including those found only in attachments.
Fixed¶
/api/entities/stateandzip_codefilters return all matching entities — they previously missed most entities because addresses are stored in more than one key format;stateinput is now case-insensitive
[5.8.0-4] - 2026-10-05¶
Added¶
- Budget accounts add
request_onlyrows — current-law accounts that appear in the President's Budget but have no Treasury data, such as NIH's request; obligations are null. Filter withaccount_category=request_only. - Budget accounts add
proposedrows — recurring President's Budget requests for programs Congress never created, such as the EV Fund; obligations are null. For a current-law request total, filteraccount_category__in=budgetary,request_only.
Fixed¶
- Budget account sorts put empty values last — every
/api/budget/accounts/?ordering=lists accounts with no value last, and pages no longer repeat or skip rows; the/recipients/and entity/budget-flows/drill-downs list rows with no contract spend last. - Invalid
?ordering=on/api/budget/accounts/now returns 400 — an unsupported field used to be silently ignored; the error lists the fields you can sort by. - Malformed
funding_organization_idon budget/recipients/now returns 400 — a value that isn't a UUID used to return a 500.
[5.8.0-2] - 2026-10-02¶
Fixed¶
X-Results-CountTypeno longer reportsapproximatefor exact counts on repeated requests. A list response served from cache always saidapproximate, so an exact count (entities, vehicles, recipient-filtered awards) read as an estimate the second time it was requested. A cached response now carries the count type computed when it was first served.
[5.8.0-1] - 2026-10-02¶
Added¶
- Vehicles report how many companies hold them —
/api/vehicles/list and detail addholder_count(distinct companies holding one of the vehicle's contracts) andorder_winner_count(distinct companies that have won a task order under it) to the default shape. - Budget accounts say which period their figures run through:
data_through_period— the latest fiscal year is partial until it reaches 12. It is in the default shape of/api/budget/accounts/and filterable (data_through_period,__gte,__lte,__isnull).
Deprecated¶
- Vehicle
awardee_countis deprecated — it is the same value asorder_winner_countand will be removed in the next major API version.
Fixed¶
- Alerts created during scheduled maintenance start paused and resume on their own — while a maintenance window is in effect, a new alert comes back
paused(is_active: false) instead of going live and delivering matches from the maintenance work; it resumes when the window closes. - Award alerts report only awards the government changed after you created the alert — contract, IDV, OTA and OT IDV alerts match an award only when its FPDS last-modified date is on or after the alert's creation, so reprocessing on our side never alerts you about older awards.
- A new alert's first delivery never reaches back into a maintenance window — the first check still covers up to the previous 24 hours, but starts no earlier than the end of the latest maintenance window affecting that alert type.
[5.7.0-1] - 2026-10-02¶
Added¶
- Budget accounts report source-data anomalies —
/api/budget/accounts/addssource_anomaliesto the default shape, naming each problem found in the source data (such as capped File C contracts). - Budget accounts carry
account_category—budgetaryorcredit_financing, filterable withaccount_category; credit financing accounts are served but left out of organization budget totals.
Changed¶
- Budget account lists sort accounts without enacted budget authority last — the default ordering puts null
enacted_baafter every other account in an FY and breaks ties by id, so pages are stable.
[5.6.0-1] - 2026-10-01¶
Added¶
- Budget responses carry data-as-of headers.
/api/budget/accounts/responses includeX-Tango-Data-As-Of(the File A fiscal year and period, e.g.2026-P10) andX-Tango-Data-Published-At; both are omitted until a validated publication is live.
[5.5.0-8] - 2026-10-01¶
Fixed¶
- Sorting Federal Register documents newest-first by
comments_close_onoreffective_onputs undated documents last, soordering=-comments_close_onreturns the latest comment deadlines.
[5.5.0-4] - 2026-09-30¶
Changed¶
- Sign-in codes now stay valid for 10 minutes, up from 3, so a code that a mail filter holds up still works when it arrives; the email says how long it lasts.
Fixed¶
- The "Account Already Exists" email now links to the sign-in page instead of a password reset, since Tango accounts have no passwords.
[5.5.0-3] - 2026-09-30¶
Added¶
- The OpenAPI schema marks tier gates. A gated operation or filter parameter carries
x-tango-access-level, the lowest access level that can use it, and an operation with tier-gatedshapefields carriesx-tango-shape-access-level.
[5.5.0-1] - 2026-09-29¶
Added¶
- Award details on opportunities — where SAM has posted an award notice,
/api/opportunities/can returnawarded,award_date,award_amount,awardeeandawardee_uei, filterable with?awarded=and?awardee_uei=. Older records fill in gradually as they're reprocessed.
- Awards linked to solicitations — when SAM's award notice references its solicitation, the award carries
solicitation_opportunity_idand the solicitation getsaward_countplus anawardsexpand of up to ten recent awards. Awards posted without that reference stay unlinked.
- Award details on notices —
/api/notices/acceptsaward_date,award_amount,awardeeandawardee_ueiinshape, and opportunity alert summaries includeawardeewhen present.
[5.4.0-2] - 2026-09-29¶
Fixed¶
- Resolving "Department of the Army", "Department of the Navy" or "Department of the Air Force" returns the military department — the organization resolver used to return an unrelated USDA office for the Army and only DoD or a Navy sub-office for the other two.
[5.4.0-1] - 2026-09-29¶
Changed¶
- Creating an alert whose agency matches nothing now returns a 400 — an agency filter where no
|-separated term matches an organization used to be saved and never fire. Partial matches are still accepted, as are forecast, grant, exclusion and eBuy alerts, which fall back to the agency text.
- Alert agency values that can never match now return a 400 — a value over 150 characters, or one with no agency in it (
|||, an empty string), used to be saved and never fire. Existing alerts are re-checked only when their filters change.
- Alert filters with a key the alert type doesn't support now return a 400 that names the key — an unrecognized key next to a valid one used to be dropped silently, so the alert matched more than you asked for.
- A resumed alert starts matching from the moment you resume it — records created or changed while it was paused are no longer delivered on resume. Query the matching API endpoint directly if you need that period.
- Tango names itself consistently in search results — every page declares the site name
Tango by MakeGov; the home page title now leads with it, Terms drops a redundant "for Tango", and the API reference at/api/is titledAPI reference | Tango by MakeGov.
Fixed¶
- An agency abbreviation shared with a retired duplicate now covers both —
agency=FNSresolved to a retired FNS record, so Federal Register returned nothing and contract filters missed the live agency; it now resolves to the live agency and includes the retired one's records.
- Alerts keep working through very large data updates — a batch of millions of changed records is now matched in chunks over the following evaluation cycles instead of failing until the alert was paused. Matches arrive late rather than never, and one large batch can arrive as several deliveries.
- Filtering awards by recipient name and the entity
past_performanceexpansion pick up new award data again, within about half a day. They had stopped updating on 2026-09-19. Entity candidates fromPOST /api/resolve/now track registration changes within about a day.
[5.3.1-1] - 2026-09-28¶
Added¶
- SBIR topics carry their DoD DSIP cycle —
/api/sbir/topics/returnscycle_name(e.g.DOD_SBIR_2026_P1_CBZ) and filters on it exactly with?cycle_name=; DoD topics link to their DSIP solicitation by cycle, not only by solicitation number.
Changed¶
- Sign-in, verification and usage-alert emails show the sender as Tango by MakeGov and address you by name — usage alerts now come from
[email protected](previously[email protected]), so update allowlists keyed on the old address; other emails keep theirs.
[5.3.0-4] - 2026-09-28¶
Changed¶
- Ordering
/api/vehicles/bylatest_award_dateis much faster — the value is precomputed per vehicle instead of worked out from every vehicle's IDVs on each request.
Fixed¶
- A vehicle's
latest_award_dateis no longer null when one of its IDVs has no award date — it is the newest award date among the IDVs that have one. - Paging through
/api/vehicles/no longer repeats or skips vehicles — vehicles that tie on theorderingfield come back in a fixed order, so each one appears on exactly one page.
[5.3.0-2] - 2026-09-28¶
Fixed¶
agency=on SBIR topics, exclusions and budget accounts now reports what it resolved to — list responses carrymeta.resolved_filters(the organization each token matched) andmeta.warnings, like every other endpoint with anagency=filter.agency=Corps of Engineersnow resolves to the Army Corps of Engineers instead of all of DoD — an agency name no longer loses to a department that only mentions it in its description, on every endpoint that takesagency/awarding_agency/funding_agency.
[5.3.0-1] - 2026-09-27¶
Added¶
- Filter eBuy requests and IT Dashboard investments by agency —
/api/ebuy/requests/?agency=and/api/itdashboard/?agency=match a department or agency and everything beneath it, likeagencyon awards, grants and forecasts;|combines several, and an unrecognized value falls back to the agency text. - Filter budget accounts by agency —
/api/budget/accounts/?agency=accepts a name, abbreviation, or code and returns every account across that department's bureaus;?shape=organization(*)returns the resolved organization. - Filter SBIR topics by agency, including its sub-agencies —
/api/sbir/topics/?agency=now rolls up the agency hierarchy (HHSincludes NIH topics); topics and solicitations also expose anorganization(...)expand. A value is matched as text only when it names no agency (or against topics not linked to one), so a short fragment likeDOorNSthat happens to resolve to some organization no longer substring-matches: pass a full abbreviation such asDODorNSF. - Filter exclusions by agency, including every component under it —
/api/exclusions/?agency=DODnow returns exclusions issued by DoD and its components (Navy, Army, DLA, …), and exclusion alerts accept the sameagencyfilter. Each exclusion can also return its issuing agency with?shape=...,organization(*). - Filter Federal Register documents by agency —
/api/federal_register/?agency=EPAaccepts the same agency names, abbreviations, codes and organization keys as other endpoints; a department such asDOTincludes documents from its sub-agencies.
Changed¶
agencyalso matches records not yet linked to an organization — on grants, forecasts, protests, eBuy, IT Dashboard, SBIR topics, exclusions and budget accounts,agency=now also returns records whose agency couldn't be linked to an organization when their agency text matches the value, so a value likeJudiciaryorCorps of Engineersfinds those records instead of returning nothing.- Attachment text comes from the search index —
attachments(extracted_text)is read from the index behind attachment search; attachments with under 20 characters of text, or not linked to an opportunity, no longer return it.
Fixed¶
- Protests filed under the Government Printing Office's old name are linked to the Government Publishing Office, so
/api/protests/?agency=GPOincludes them.
[5.2.1-2] - 2026-09-26¶
Fixed¶
- Contract searches for uncommon terms no longer time out — searches sorted oldest-first, or narrowed by award date or fiscal year, now return in well under two seconds instead of failing with a 504.
[5.2.1-1] - 2026-09-25¶
Fixed¶
- Keyword search now finds closed opportunities —
/api/opportunities/?search=…&active=false, andsearchsent alongsidesolicitation_number,notice_typeoraward_number, used to return an empty page; it now searches closed opportunities as documented. - Notice search honors
active—/api/notices/?search=returns every notice of a matching opportunity, filtered byactivewhen you send it, including an open notice on an otherwise closed opportunity; withoutactiveit now includes closed notices. - Narrowly filtered searches no longer miss matches —
?search=combined with a selective filter such assolicitation_numberor a small agency, and opportunity search alerts, now find every match rather than only those in the top 1,000 overall. - Search reports an outage instead of an empty result — if the search service can't answer,
?search=on/api/opportunities/and/api/notices/returns503with aRetry-Afterheader rather than zero results.
[5.2.0-1] - 2026-09-24¶
Added¶
- Federal Register documents are now an API resource —
/api/federal_register/serves rules, proposed rules, notices and presidential documents since 1994, filterable by CFR citation, agency, RIN, type, dates and open comment period, with full-text search. - Federal Register alerts — create an alert with
query_type=federal_registerto be notified (alerts.federal_register.match) when a matching document is published or updated. - Check your eBuy access with
/api/ebuy/access/— returns whether/api/ebuy/requests/is enabled for your account, why not if it isn't, and which of your contracts it covers. It counts against your normal rate limits, not the eBuy daily budget.
Changed¶
- eBuy request search also matches attachment text now —
/api/ebuy/requests/?search=finds a request through text extracted from its attachments (SOWs, pricing schedules, etc.); a match on the title or description still ranks first.
Fixed¶
- eBuy alerts can be created now —
/api/webhooks/alerts/and/api/webhooks/subscriptions/rejectedquery_type: "ebuy_request"as "not a valid choice"; both accept it and deliveralerts.ebuy_request.match.
[5.1.0-6] - 2026-09-24¶
Added¶
- More multiple-award BPAs show up as vehicles — BPAs that FPDS reports without a solicitation number, such as NOAA's NMITS MAS BPAs, are now grouped into vehicles; they carry
is_synthetic_solicitation: trueand a nullsolicitation_identifier.
[5.1.0-2] - 2026-09-23¶
Fixed¶
- Contract lists filtered by a smaller or retired agency load reliably —
/api/contracts/?awarding_agency=…or?funding_agency=…sorted by award date could time out for agencies with relatively few contracts, or whose most recent contract is years old; those requests, including later pages, now return quickly.
[5.1.0-1] - 2026-09-23¶
Added¶
- eBuy requests are now a first-class endpoint —
/api/ebuy/requests/— search, filter and page the GSA eBuy requests your linked Schedule contract entitles you to, and download their attachments. Requires a paid plan and separate MakeGov authorization; reach out if interested.
[5.0.1-2] - 2026-09-22¶
Fixed¶
- New accounts created with an emailed sign-in code get their plan right away — accounts made this way since 4 September were left without a plan assigned and never received a welcome email; the plan is now granted as soon as the code is confirmed, and the affected accounts have been corrected.
- Plan upgrades apply immediately — an API request sent while an upgrade was being applied could leave your account throttled at your previous plan's limits for up to five minutes; the new limits now take effect as soon as the change completes.
- Alert limits apply to your whole account — your plan's alert allowance now counts alerts across all of your webhook endpoints, paused alerts included, instead of per endpoint; existing alerts are kept, but new ones are refused once the account is at its limit.
[5.0.0-1] - 2026-09-21¶
Changed¶
- Awards are built on a rebuilt pipeline — all four award types, their transactions and their search index now come from one chain that tracks amended and retracted actions, records where each value came from, and returns lists, filters and searches faster.
Breaking¶
- 🚨
employment_eligibility_verificationandservice_contract_inventoryare booleans on contract and IDV responses, not the strings"Y"and"N". Code that compares either field against"Y"has to switch to a boolean test.
[4.27.0-1] - 2026-09-20¶
Added¶
- Attachment document roles —
attachments(doc_role)on/api/opportunities/and/api/notices/labels each documentrequirement,instructions,pricing,terms,referenceorunknown, withdoc_role_altfor a close second. Pro+, named explicitly (not inattachments(*)); see the API reference.
Breaking¶
/api/vehicles/?shape=opportunity(attachments(extracted_text))now returns 400. Attachment text is a Pro+ field everywhere it is served, but the vehicles endpoint applies no tier check, so this path returned document bodies to any caller. Request it through/api/opportunities/or/api/notices/, where the tier gate applies.attachments(*)no longer returnsextracted_text— name it (attachments(extracted_text)) to keep receiving document bodies on/api/opportunities/,/api/notices/and/api/sled/opportunities/. Every other attachment field is unchanged.
Fixed¶
- Attachment bodies are no longer reachable on a Free plan —
attachments(*)on/api/notices/returnedextracted_textthat the named spelling correctly withheld. /api/notices/list responses withattachments(...)are faster — each notice on a page was fetching its own attachments instead of riding the page's single query.
[4.26.0-1] - 2026-09-18¶
Added¶
- Contract appeal decisions are now an API resource —
/api/contract_appeals/serves Civilian Board of Contract Appeals (CBCA) and Armed Services Board of Contract Appeals (ASBCA) decisions, filterable by board, docket number, appellant, judge, decision type and date, with full-text search. - The full text of each appeal decision is available on Enterprise plans — request
decision_textinshape; other plans get the rest of the record plus an upgrade hint, and can still search the text. - Contract appeal alerts — create an alert with
query_type=contract_appealto be notified (alerts.contract_appeal.match) when a matching decision is published or updated.
[4.25.3-6] - 2026-09-17¶
Breaking¶
- Court of Federal Claims protests now come from the court's own ECF/PACER system. CoFC protests loaded from the earlier CourtListener feed (mostly cases filed before 2026) are removed with their documents, and CoFC protest
ids change.
[4.25.3-3] - 2026-09-13¶
Changed¶
first_seen_atandlast_seen_aton state and local opportunities now record when the portal was observed, not when we processed it. Some values move earlier, by up to about 11 days; if you poll withfirst_seen_after, overlap your window by a few days.
Fixed¶
- Daily and premium quotas reset reliably at midnight UTC. Some accounts could previously be rate limited before reaching their full daily allowance.
- Looking up a notice or opportunity by an undashed ID no longer uses extra rate-limit quota. The redirect to the dashed ID is returned even when you are at your limit.
[4.25.3-2] - 2026-09-13¶
Added¶
- Securities and Exchange Commission forecasts.
GET /api/forecasts/now covers the SEC (?agency=SEC), whose forecast lists incumbent contracts nearing their end; the award date is that contract's end date, and the incumbent, dollar band, PSC, and business lead are inraw_data.
[4.25.3-1] - 2026-09-13¶
Added¶
status_reason=delistedanddelisted_aton state and local opportunities. A solicitation the portal stops listing before its deadline now readsstatus=closedwithstatus_reason=delisted, anddelisted_atsays when we first saw it gone; if it is listed again, it reopens.
Fixed¶
- Duplicate state and local opportunities are gone. Duplicate records left behind when a portal changed its identifier are removed; old ids resolve to the current record with a
301.
[4.25.2-1] - 2026-09-10¶
Added¶
meta.jurisdiction_declaredon state and local opportunities. It tells you whether the source itself stated the opportunity'sjurisdiction— a portal field naming the issuer type, or a portal that serves exactly one government — or whether we determined it from the name of the issuing body.falsemeans we determined it;nullmeans nothing established a level at all.
Changed¶
jurisdictionis far more accurate, and thestatebucket is smaller. Opportunities whose jurisdiction had never actually been determined were being reported as state-level. They now report the level of the body that is really buying, so a great many university, school-district, city and county solicitations move fromstatetoeducationorlocal, and a small number move tounknown. If you filter?jurisdiction=stateyou will see fewer results and they will be state agencies;?jurisdiction=localand?jurisdiction=educationreturn several times what they did.
Breaking¶
Fixed¶
- Georgia school-district and university solicitations report the right jurisdiction. They were reaching the API as state-level.
- Hawaii opportunities report the county, Department of Education or University of Hawaii that issued them rather than all reporting as state-level.
[4.25.1-1] - 2026-09-10¶
Added¶
-
Document text on state and local solicitations, on the Small plan and above —
?shape=attachments(extracted_text)returns the extracted body of a solicitation's documents. Three things to know. Ask for the field by name:attachments(*)does not include it, so addextracted_textto the expand when you want it. The key is absent, not null, whenever the text is not being served to you — on a Free plan it is withheld and listed inmeta.upgrade_hints, and where we have no text for a document it simply carries no key, so read it with a default rather than assuming it is there. And a document flaggedis_contestednever returns text, on any plan: the file we retrieved for it does not match what the solicitation listed, so we cannot vouch that its text belongs to this record.Every fact about a document stays free on every plan — name, size, page count, character count and extraction status — so you can tell whether a body is worth fetching before you fetch it. Searching document text is also free and unchanged:
?search=matches inside attachments on every plan and returns matching solicitations without any of their text.
[4.25.0-1] - 2026-09-10¶
Added¶
- State, local and education solicitations are now an API resource —
/api/sled/opportunities/and/api/sled/forecasts/, with filters for state, level of government, status, agency, category codes, deadlines and documents.
- 🚨 Beta, and complete only for what is currently open. Every state and territory jurisdiction we read is covered for open solicitations; historical ones are still being backfilled. Field names, filters and response shapes may change while this is in beta.
Changed¶
- Solicitation-number filters match every spelling — contracts, IDVs, protests, opportunities and notices now find a number written the FPDS way (
N6600120R0002) or the SAM.gov and GAO way (N66001-20-R-0002), in any case, and accept|-separated values.
Fixed¶
- CALC labor-category rates honour your plan. The
idvandrecipientexpansions on/idvs/{key}/lcats/and/entities/{uei}/lcats/require a paid plan and were being returned to Free callers.
- 🚨 A daily rate limit could outlast the day it counted. A daily counter could stop resetting at midnight UTC, leaving an account rate-limited indefinitely; it now re-arms its own reset on every request.
[4.23.1-2] - 2026-09-04¶
Fixed¶
- 🚨 Signing in from
tango.devworks again. The redesign moved sign-in to/, which meant the mirror served the page itself rather than handing you totango.makegov.com— so the bot check failed to load and the sign-in code was never sent. Both addresses now behave the same.
[4.23.1] - 2026-09-04¶
Added¶
- A comprehensive redesign of everything you reach while signed in. The account page is now five sections — Overview, API, Usage, Billing and Settings — each with its own sub-navigation and its own address, replacing the single page everything used to stack onto.
- Overview opens on what you have actually used: requests today against your limit, thirty days of history, your plan, and your keys.
- Usage shows requests per day, a breakdown by resource, and how close you are to each of your limits, over the period you choose.
- Billing gives your subscription, your invoices and the card you are charged on their own pages. If MakeGov invoices you directly, every page says how your account actually bills rather than hiding the tab.
- Settings holds your profile — your name and organization — and your notification preferences.
- Light and dark mode is a control in the account page's top bar. Leave it on System to follow your operating system; the choice is remembered in your browser.
- The OAuth application pages match the new account page too — registering, editing and deleting an application, and reviewing the tokens you have authorized.
- Sign in, sign up and the pages around them are redesigned, along with the Terms of Service.
- You can withdraw marketing consent — Settings → Notifications shows your current answer, when it was recorded, and lets you change it. It is Tango's own record; if you are already on a Mailchimp list, coming off that list is a separate step.
Changed¶
- Signing in now lands you on your account Overview. The welcome page is gone —
/welcome/redirects there, and a brand-new account starts on Overview too.
- 🚨 Sign in moved to
/, and creating an account moved to/start/. The old/accounts/login/and/accounts/signup/addresses redirect and keep any query string, so existing links and bookmarks still work.
[4.23.0-1] - 2026-09-03¶
Added¶
-
Entities can now return employee count and hiring locations — request
workforcein yourshapeon any entity endpoint:/api/entities/{uei}/?shape=uei,legal_business_name,workforce(*)"workforce": { "number_of_employees": "100 to 499", "locations": ["CA", "TX", "NY"], "location_count": 58, "last_verified": "2026-08-01" }number_of_employeesis a published size band, not an exact figure.locationslists the states where the company hires, which is often wider than the single address on its registration — about 17,000 companies span more than one state, and some span forty.
-
last_verifiedtells you how old the employee count is — it comes back insideworkforcefor a reason: it is the date the company itself last attested to these figures, and it ranges from days to over a decade. Read the count with the date, never on its own.workforceisnullfor a company we could not confidently match, which is most of them — roughly 138,000 entities carry one today.
[4.22.0-28] - 2026-09-01¶
Added¶
- Budget flows now return the 10 largest contracts per row — on
/api/entities/{uei}/budget-flows/and/api/budget/accounts/{id}/recipients/, so a big vendor's response stays a usable size.
- A row whose contracts were cut sets
contracts_truncatedand lists its fullpiids— fetch any one you need with/api/contracts/?uei={uei}&piid={piid}.
Fixed¶
- 🚨 Budget flows listed contracts that weren't the vendor's — on both budget drill-downs,
contractsmatched on PIID alone, pulling in other companies' awards that shared one. Each row now carries only that recipient's contracts.
- Budget-flow responses are dramatically smaller — for a large vendor a single page dropped from thousands of contracts to the handful actually belonging to it.
[4.22.0-15] - 2026-08-17¶
Added¶
- Premium endpoints now report their own budget — in
X-RateLimit-Premium-*headers, including on a 429 (see Rate limits).
- Your account page now shows your premium query budgets — usage, reset, and how to ask for more (see Rate limits).
Breaking¶
- 🚨
GET /badges/version.svghas been removed. The endpoint returned an SVG version badge; if you embedded it, it now returns 404. The same versions are still available as JSON from/api/version(the API contract version) and/healthz(the deployed build).
[4.22.0-12] - 2026-08-14¶
Changed¶
- The Micro plan is no longer offered. Small is now the entry paid plan. If you're on Micro, your price, limits, and features are unchanged. Where the API named Micro as the tier to upgrade to, it now names Small:
required_tierin a 403 and inmeta.upgrade_hints, the tier named when a custom cron alert schedule is refused, and the plan suggested by usage recommendations. One knock-on for Micro subscribers: a 403'scurrent_tiernow readsSmallrather thanMicro, because Micro and Small share one access level and that field names the level, not your plan.
- Upgrading your plan is now charged when you upgrade. The prorated difference for the rest of the billing period is billed at the moment you switch, rather than appearing on your next invoice. If that payment is declined, your plan stays as it was and nothing changes. Downgrades are unchanged — the credit still applies to your next invoice.
- Your daily request allowance resets once per day when you move to a higher plan. Previously every upgrade cleared the day's usage; a second upgrade on the same day now keeps it. The higher plan's limit still applies immediately either way.
[4.22.0-6] - 2026-08-11¶
Fixed¶
- Filtering by a department's 4-digit agency code now covers the whole department. Codes like
7500(HHS) and9700(DOD) matched only the department's own top-level office, so an agency filter that should have returned the department's full contract activity came back empty or errored. Sub-agency codes such as7530(CMS) were unaffected. Department abbreviations (HHS) and 3-digit codes (075) always worked and still do.
[4.22.0-5] - 2026-08-10¶
Fixed¶
- Search results now show the matching passage from an attachment far more often. When
?search=matched text inside a solicitation's documents, most results still came back without asnippet— the excerpt was drawn from a much smaller set of documents than the search itself covered, so the passage that caused the match usually could not be quoted. Snippets now come from the same document set the search runs against, so a result that matched on attachment text should carry the excerpt showing why. Request and response formats are unchanged:snippetis the same optional field, just populated far more often.
[4.22.0-3] - 2026-08-08¶
Changed¶
- A Claude or ChatGPT connector left unused for 90 days now asks you to reconnect. Access is unchanged while you keep using it — connectors refresh in the background every day — but one that sits idle for a full quarter has its authorization retired and prompts you to approve it again the next time you use it.
[4.22.0] - 2026-08-07¶
Added¶
-
The joint venture behind an SBA Mentor-Protégé agreement is now identified where we can find it. SBA publishes only the two firms in an agreement — it names no joint venture at all — so the JV is recovered by matching SAM registrations, and it appears as a
mentor_protege_jvrelationship from the JV to each of its two members. Viewed from the JV, each partner'srelationismember; viewed from a partner, the JV'srelationisjoint_venture. About 690 of roughly 1,900 current agreements resolve to a JV; the rest are not identifiable from published data, and some pairs never formed one.These links are inferred, and they say so. Every one carries
source: "inferred"— never thesbaused for the agreement itself — plusverification_methodnaming the evidence (document_verificationwhere the JV's own website names both partners,exact_matchwhere SAM records a partner as the JV's immediate owner,name_matchwhere the JV's legal name is built from both partners' names) and aconfidencetier. Treat them as a strong lead, not as a fact SBA published. Thementor_protegerelationship is unchanged and remains exactly what SBA asserts.
- Find an IT investment by an identifier that no longer exists. Agencies retire an IT investment by folding it into a successor, which leaves any UII you have on file — in a citation, a saved query, a bookmarked URL — pointing at nothing.
GET /api/itdashboard/?previous_uii=015-000000100now returns the investment(s) that absorbed it, on every tier, and full-text search covers retired identifiers too, so?search=015-000000100finds the successor.
Changed¶
- A retired UII on
GET /api/itdashboard/{uii}/now resolves instead of returning 404. If exactly one current investment absorbed it, the request redirects (301) to that investment, preserving your query string — so a client that follows redirects simply gets the right record. If the source split it across several successors, you get a300listing each candidate with its title and URL rather than an arbitrary pick. An identifier that was never a real investment still returns404.
[4.21.0] - 2026-08-03¶
Added¶
- Agency filters now tell you what your search term matched. List responses that use an agency filter (
awarding_agency,funding_agency,agency) include ameta.resolved_filtersblock naming, for each value you passed, the organization it resolved to — its name, identifier, hierarchy level, and CGAC code — ornullif it matched nothing. Agency matching is fuzzy, so a term likeHUDcould previously land on an unrelated organization and quietly scope your query to it; you would get a confident, plausible, wrong answer with no way to tell. Now you can see the match and catch it. Anything ignored or matched loosely is also listed inmeta.warnings. The block appears on successful responses only.
Changed¶
- An agency filter that matches nothing now returns a 400 instead of an empty list. If every value you pass to
awarding_agency,funding_agency, oragencyfails to match an organization, the request is rejected with a message naming the value, rather than returning zero results — which was indistinguishable from "no such records exist".GET /api/contracts/,/api/idvs/,/api/otas/, and/api/otidvs/already behaved this way; this brings/api/subawards/,/api/opportunities/,/api/notices/, and/api/vehicles/into line. If some of your values match and others don't, nothing changes — the matching ones still apply and the rest are reported inmeta.warnings, so multi-value searches like?agency=7530|7523keep working exactly as before./api/forecasts/,/api/grants/, and/api/protests/are unaffected: an unmatched value there still searches their agency text field, so it is a valid search rather than a dropped one. - Multi-value agency filters work correctly on subawards and contracts.
?awarding_agency=A|Bwas not being split into separate values onGET /api/subawards/, or onGET /api/contracts/in its default mode — the wholeA|Bstring was matched as one term. On contracts that returned an empty list; on subawards it matched a single unintended organization and returned results scoped to it. Both now return the union of both organizations, as documented.
Fixed¶
- Performance improvements for contract searches. Some vendor-scoped searches that could previously time out now return normally. Results, ordering, and pagination are unchanged.
[4.20.0] - 2026-08-02¶
Added¶
- Department of Education forecasts.
GET /api/forecasts/now covers the Department of Education — several hundred upcoming requirements, refreshed daily, filterable with?agency=ED(or combined, e.g.?agency=ED|HHS). Education publishes its forecast as a spreadsheet rather than a feed, so the available fields differ a little from other agencies: you get the contract name as the title, NAICS code, anticipated award date, fiscal year, set-aside, contract type, and the contracting point of contact, plus the funding office, incumbent, and dollar ranges in the record's raw data. There is no free-text requirement description, sodescriptionisnullon these records and keyword search matches on the title. Anticipated award dates are the first day of Education's target federal fiscal quarter — a target of FY2026 Q1 comes back as2025-10-01. - Entity relationships now report the relationship type and its source. Each entry in the
relationshipsexpand carriestype(prime_sub,parent_subsidiary,ultimate_parent,predecessor) andsource(sam,subawards), both selectable viashape. Match ontyperather thanrelation— it is the stable key. - SBA Mentor-Protégé agreements are now available as entity relationships. Firms in an active SBA Mentor-Protégé agreement carry a
mentor_protegerelationship linking mentor to protégé, with the approval date inrelationship_date. Viewed from the mentor the partner'srelationisprotege; viewed from the protégé it ismentor. Covers roughly 1,900 current agreements, refreshed as SBA republishes. Note that SBA publishes only currently-active agreements, so an agreement that expires or is terminated stops appearing rather than being marked inactive — and SBA's data does not identify the joint venture formed under an agreement, only the two firms.
Changed¶
relationno longer returnsaffiliatefor subcontracting or predecessor ties. The value collapsed two unrelated relationship types and both directions of a one-way tie, so it could not distinguish "hired this company as a subcontractor" from "was hired by them." It now names the partner's role:subcontractor/primefor subcontracting,predecessor/successorfor corporate succession. This affects the large majority of relationship entries — if you match on the stringaffiliate, switch to thetypefield, which is stable and won't churn again.- An entity linked by its ultimate parent now reads
descendantinstead ofchild. We only record an ultimate-parent tie when the ultimate owner differs from the immediate owner, so the other end of that tie is never a direct subsidiary — it sits at least one company further down the ownership chain.childremains correct for immediate parent/subsidiary ties. relationshipsentry counts can differ by requested shape. Entries are de-duplicated after yourshapeselection is applied, so asking for a narrower shape (e.g.shape=relationships.uei) can return fewer entries thanshape=relationshipsfor a company with several distinct ties to the same partner.
Fixed¶
- Duplicate entries removed from the
relationshipsexpand. Two companies that had each subcontracted to the other produced two identical entries; they now appear as the two distinct ties they are. - Attachment-text search results are current again. Between 2026-07-21 and this release,
GET /api/opportunities/?search=…matched attachment text against a stale index: text extracted from attachments in that window was not searchable, and some attachments that had since dropped out (extraction failed, unlinked from their opportunity) still produced matches. Roughly 36,000 attachments were affected. Newly changed attachments are indexed on the normal refresh cadence again; no client change is needed.
[4.19.0-4] - 2026-07-28¶
Added¶
X-Tango-API-Versionresponse header. Every API response now returns a stable API version (starting4.19.0) so you can pin against the contract you built for, independent of our deploy version.
Changed¶
/api/versionand the Swagger/OpenAPIversionnow report the API version (4.19.0) rather than the build number.
[4.19.0] - 2026-07-22¶
Added¶
- Period-of-performance dates on budget drill-down contracts. The contracts hydrated inline by
GET /api/budget/accounts/{id}/recipients/andGET /api/entities/{uei}/budget-flows/now include a nestedperiod_of_performanceobject (start_date,current_end_date,ultimate_completion_date) alongside the existingkey,piid,naics_code,psc_code,obligated, andaward_date. You can now tell which of an account's contracts are ending soon — the natural "about to recompete / about to end" question — directly from the drill-down, without a follow-up lookup on/api/contracts/. Dates arenullwhere FPDS doesn't carry them.
[4.18.7] - 2026-07-21¶
Fixed¶
- Bug fixes associated with an enterprise bulk data endpoint.
[4.18.0] - 2026-07-17¶
Added¶
- New endpoints:
/api/sbir/topics/and/api/sbir/solicitations/. Search and filter SBIR/STTR topics across every participating agency (DoD, NIH, NSF, NASA, DOE, DOT and more), plus the DoD DSIP solicitation cycles they belong to. Filter topics byactivity(open/closed),agency,year,program, solicitation or topic number, and open/close/release date ranges; order by any date or byactivity. Each topic links to the parent solicitation it belongs to — a DSIP cycle, a SAM.gov notice, or a Grants.gov announcement — so you can follow a topic through to the opportunity or grant that funds it. BAA PDFs are available as adocuments(*)expand on a solicitation. Response shaping (shape) is supported on both endpoints.
[4.17.0] - 2026-07-14¶
Added¶
- New endpoints:
/api/dibbs/rfqs/,/api/dibbs/rfps/, and/api/dibbs/awards/. Search the DLA Internet Bid Board System (DIBBS) — small-purchase requests for quote, large solicitations (RFP/IFB), and awards from the Defense Logistics Agency. Available on every plan, including Free.?open=truereturns the RFQs and solicitations still accepting responses today, derived from the closing date on each record. Each result also carriesis_open.- DIBBS is an NSN and part-number world, so
nsnandpart_numberfilter every resource, alongsidesolicitation,organization, and full-textsearchacross nomenclature and identifiers. RFQs addset_aside,status_code,quantityranges, andreturn_by_date/issue_dateranges; solicitations addbuyer_codeandissued_date/closes_dateranges; awards addaward_number,delivery_order_number,awardee_cage,entity(awardee UEI),award_date/posted_dateranges, and atotal_contract_pricerange. - Awards carry the resolved
awardeeentity when the awardee's CAGE code matches a registered entity;awardee_cageis always present either way. - Read this before you total anything. An award record is a line item, not a whole award: a delivery order covering many items appears as one record per item, and
total_contract_priceis the order's total repeated on every one of them. Summing it across records will badly overstate spend — one real order spans 483 line items that all read $9,000,000.00, so a naive total reports $4.35 billion for it — and inflates DIBBS spend overall from $6.0 billion to $33.0 billion. To total spend, count each order once. - Detail lookup is by
uuid. Response shaping (?shape=) is supported on both list and detail.
- New alert types:
alerts.dibbs_rfq.matchandalerts.dibbs_rfp.match. Subscribe to a saved DIBBS filter — say, an NSN or part number you supply — and get notified when a matching quote request or solicitation is newly posted or updated. Note that an RFQ or solicitation simply reaching its closing date is not itself a change to the record, so it does not trigger an alert. Alerts cover quote requests and solicitations; awards are not alertable. - New endpoint:
/api/exclusions/. Search SAM.gov exclusions — debarments, suspensions, and other exclusions from federal awards. Available on every plan, including Free.?active=truereturns the exclusions in force today, derived from the dates on each record. Each result also carriesis_currently_excluded.- Filter by
classification_type,exclusion_type,exclusion_program,excluding_agency_code,excluding_agency_name,uei,cage_code,npi,entity_uei,delisted, andactivate_date/termination_date/update_dateranges; full-textsearchacross names, excluding agency, and comments. - Detail lookup is by
exclusion_key. Response shaping (?shape=) is supported on both list and detail.
- New alert type:
alerts.exclusion.match. Subscribe to a saved exclusions filter and get notified when a matching exclusion is newly issued, revised, or lifted. Note that an exclusion simply reaching its termination date is not itself a change to the record, so it does not trigger an alert.
[4.16.0] - 2026-07-13¶
Added¶
- New endpoint:
/api/exclusions/. Search SAM.gov exclusions — debarments, suspensions, and other exclusions from federal awards. Available on every plan, including Free.?active=truereturns the exclusions in force today, derived from the dates on each record. Each result also carriesis_currently_excluded.- Filter by
classification_type,exclusion_type,exclusion_program,excluding_agency_code,excluding_agency_name,uei,cage_code,npi,entity_uei,delisted, andactivate_date/termination_date/update_dateranges; full-textsearchacross names, excluding agency, and comments. - Detail lookup is by
exclusion_key. Response shaping (?shape=) is supported on both list and detail.
- New alert type:
alerts.exclusion.match. Subscribe to a saved exclusions filter and get notified when a matching exclusion is newly issued, revised, or lifted. Note that an exclusion simply reaching its termination date is not itself a change to the record, so it does not trigger an alert.
Fixed¶
- Agency filters now accept an organization UUID.
agencyon opportunities,awarding_agencyandfunding_agencyon contracts and subawards, and the vehicles agency filter previously accepted only a name, abbreviation, or agency code — passing an organization UUID matched nothing and returned an empty result set rather than an error. Organization UUIDs are exactly what the organizations endpoints return, so the natural "look up an organization, then filter by it" workflow silently produced zero results. All of these filters now accept a name, abbreviation, 3-digit CGAC code, 4-digit FPDS code, or an organization UUID, and every form rolls up to descendants — filtering by a department returns records from all of its sub-agencies and offices. - Documented that
/api/opportunities/returns only active opportunities by default. The endpoint restricts results to active opportunities unless the request includesactive,solicitation_number,notice_type, oraward_number, any of which searches the full dataset. Sendingactive=falsereturns closed opportunities. Theactiveparameter was missing from the API reference entirely, so a search scoped to an organization whose opportunities have all closed appeared to return nothing at all.
[4.15.0] - 2026-07-12¶
Added¶
- SBA OHA decisions are now available through the protests endpoint. Decisions from the Small Business Administration's Office of Hearings and Appeals join GAO and the Court of Federal Claims as a third venue. Filter to them with
?source_system=sba_oha, or exclude them the same way. As with every other venue, the endpoint serves decisions from 2015 onward — roughly 1,350 OHA cases, most of them size and NAICS appeals, with new decisions arriving daily. - Five new protest fields, available on request via
shape.challenged_party(the firm whose size or status was challenged),naics_code(the code at issue in the appeal),size_standard(as written, for example$33.5 millionor1,000 employees),outcome_reason(why a case was dismissed — untimely, moot, withdrawn, lack of jurisdiction, lack of standing, insufficient specificity), andjudge(the authoring administrative judge). These are populated for SBA OHA decisions and are null for GAO and Court of Federal Claims records, so they are not part of the default response — request them explicitly with?shape=. They are available on every plan. - A
naics_codefilter on the protests endpoint, for finding the size and NAICS appeals that turned on a particular code:?naics_code=541512.
Changed¶
- The protests
outcomefield can now returnGranted,Remanded,Reversed, orVacated. These come from SBA OHA decisions. GAO and Court of Federal Claims records continue to use onlyDenied,Dismissed,Withdrawn, andSustained, so existing filters on those four values are unaffected — but code that treats the set of four as exhaustive should be widened.
Fixed¶
- Fixed how rate-limited requests are counted against your daily quota and how retry times are reported.
[4.14.8] - 2026-07-09¶
Changed¶
- Stripe Checkout now asks for your name and billing address, so invoices and receipts carry the right details.
Fixed¶
- Agency filters now accept an organization UUID.
agencyon opportunities,awarding_agencyandfunding_agencyon contracts and subawards, and the vehicles agency filter previously accepted only a name, abbreviation, or agency code — passing an organization UUID matched nothing and returned an empty result set rather than an error. Organization UUIDs are exactly what the organizations endpoints return, so the natural "look up an organization, then filter by it" workflow silently produced zero results. All of these filters now accept a name, abbreviation, 3-digit CGAC code, 4-digit FPDS code, or an organization UUID, and every form rolls up to descendants — filtering by a department returns records from all of its sub-agencies and offices. - Documented that
/api/opportunities/returns only active opportunities by default. The endpoint restricts results to active opportunities unless the request includesactive,solicitation_number,notice_type, oraward_number, any of which searches the full dataset. Sendingactive=falsereturns closed opportunities. Theactiveparameter was missing from the API reference entirely, so a search scoped to an organization whose opportunities have all closed appeared to return nothing at all. - If you cancelled a plan and later wanted to come back, the pricing page could show your old plan as still current and refuse to let you subscribe again. You can now resubscribe normally.
- Accounts billed by invoice rather than through Stripe no longer show as "Free" on the pricing page.
[4.14.6] - 2026-07-08¶
Changed¶
- Agency filters (
?awarding_agency=,?funding_agency=,?agency=) resolve substantially faster, especially the first request for an agency code after a deploy. - When a recipient name search times out, the request now fails fast with a
504instead of retrying against a slower table. Previously such a request could take up to roughly 50 seconds before returning results.
Fixed¶
- Agency and recipient filters now return a
504when the database times out, matching every other endpoint. Previously these filters reported a timeout as an empty result set, which was indistinguishable from a search that genuinely had no matching records. Requests with an unrecognized agency or recipient value still return an empty result set, as before. - Webhook alerts filtered by agency could stop delivering shortly after being created, and would not recover on their own. Newly created agency alerts now begin delivering as expected.
[4.14.4] - 2026-07-07¶
Fixed¶
- Budget account narratives without a subaccount granule now appear in a budget account's
narratives— around 3,500 of these were previously dropped on every data refresh and never surfaced
[4.14.2] - 2026-07-06¶
Fixed¶
- Planned-maintenance windows now reliably appear on status.makegov.com — some windows announced in 4.14.0 were silently failing to publish
- Alerts stay reliable during very large data refreshes — an alert evaluating against an unusually large batch of changes could time out and stop delivering; evaluation now processes those batches incrementally
- If an alert genuinely can't be evaluated for a sustained period, we now pause it and email you, instead of letting it fail silently — resuming the alert clears the error state
[4.14.0] - 2026-07-05¶
Added¶
- Planned API maintenance now appears on status.makegov.com as a scheduled-maintenance window (with the expected restore time) instead of unexplained downtime
Changed¶
- Raised webhook alert limits on larger plans. Alert evaluation runs in parallel batches behind the scenes, so match checks stay on schedule at high alert counts.
Fixed¶
- Webhooks: pausing and resuming an alert via
PATCH /api/webhooks/alerts/{id}/withis_active: false/truenow works. Previously the request returned 200 but the alert kept evaluating (or stayed paused); the alert'sstatusfield now correctly reflects"paused"/"active"after the update. Deactivating an alert also takes effect immediately for matches already being processed when it was paused.
[4.13.0] - 2026-07-04¶
Changed¶
- Award list endpoints now cap
?limit=per plan — 100 results per page on standard plans (higher on Enterprise). Requests above the cap return at most that many rows and set anX-Results-Limit-Clampedheader; use thenextcursor to page through the rest.
[4.12.7] - 2026-06-26¶
Changed¶
- Each matched webhook event is now delivered as its own HTTP request with one top-level
delivery_id. Previously events for the same endpoint could be combined into a single request, so a receiver expecting more than one event in theeventsarray now gets one per request.
Fixed¶
- Webhook delivery payloads now include the top-level
delivery_iddescribed in the payload format and Slack how-to — a UUID that stays the same across retries, so you can use it as an idempotency key to dedupe deliveries. - The "Send test" delivery and sample payload endpoint now return events in the same shape as real alert deliveries (
event_type,query_type,filters,matches), so you can validate your receiver against a representative payload. Test deliveries stay marked withtest: true.
[4.12.4] - 2026-06-23¶
Fixed¶
- Made LinkedIn (and other social) sign-in more resilient. When the identity provider was temporarily rate-limiting us, some login attempts failed with an error; sign-in now stays working through those brief provider hiccups.
[4.12.1] - 2026-06-18¶
Fixed¶
- Fixed alerts that trigger a Claude Code routine: these deliveries were failing to start the routine, and now send the correct request format so the routine runs reliably with the matched results as context. These deliveries are also sent once and not retried, so a transient hiccup can no longer start the same routine twice.
[4.12.0] - 2026-06-18¶
Added¶
- Manage your webhook endpoints right from your account page — create, edit, and enable or disable them without touching the API. Endpoints that fail to deliver for a sustained period are now disabled automatically, and we email you a heads-up with a link to re-enable.
Changed¶
- Webhooks are now available on all plans.
[4.11.0] - 2026-06-18¶
Added¶
- Webhook endpoints can now send custom request headers (such as an
Authorizationheader), so alerts can deliver to authenticated destinations — including triggering a Claude Code routine when an alert matches. Header values are stored encrypted and are never returned in API responses.
Changed¶
- Contract data ingestion now runs as one ordered pipeline with a freshness alert, so a stalled refresh is caught within a day instead of silently serving stale data.
[4.10.0] - 2026-06-17¶
Changed¶
- Webhook alerts are now evaluated against our data lake — faster, and no longer paused or throttled for matching too broadly.
Breaking¶
- The alert filter preflight endpoint (
POST /api/webhooks/subscriptions/check-filter/) has been removed, along with the automatic pausing of broad alerts. - The
weeklyalert frequency has been removed; use acustomcron expression for a weekly cadence.
[4.9.0] - 2026-06-16¶
Added¶
- Webhook alerts that match too broadly are now paused automatically and you'll get an email explaining how to narrow the filter (use a sub-agency instead of a whole department, add NAICS/PSC/keyword constraints, or split it into several alerts) and a link to re-enable it. A "firehose" filter that matches tens of thousands of records can't be evaluated efficiently, so it's safer to flag it than to let it flood your endpoint. Creating or updating an alert with a too-broad filter is now rejected up front, and a new
POST /api/webhooks/subscriptions/check-filter/endpoint lets you preview how many records a filter would match before you save it.
Fixed¶
- Webhook alert evaluation is faster and no longer times out on broad filters; entity, grant, and forecast alerts evaluate against an index instead of a full table scan.
[4.8.0] - 2026-06-10¶
Added¶
/api/forecasts/now includes USDA procurement forecasts (source_system=USDA, ~2,400 records).
Changed¶
- Labor-category rates (
/api/idvs/{key}/lcats/,/api/entities/{uei}/lcats/) now refresh daily. One-time effect:modifiedtimestamps reset table-wide at cutover. opportunity_historyon/api/grants/detail responses is now populated; it was previously alwaysnull.- Forecast
modifiedtimestamps now update only when a forecast actually changes, somodified_afterfilters and webhook alerts reflect true changes. /api/protests/: ~9,900 recovered historical GAO protests are restored, and existing cases carry fresher outcome and decision data.
Internal / Infra¶
- GSA eLibrary contract metadata refreshes daily through the new ingest pipeline. The
sinsarray may list codes in a different order (it is semantically a set), andlast_updatednow tracks GSA's newest published value, including downward corrections. - On
/api/contracts/, object-valued code/description fields such astradeoff_process,fair_opportunity_limited_sources, andsubcontracting_plancan now be named bare in ashape(e.g.?shape=piid,tradeoff_process) and behave exactly liketradeoff_process(*), matching howcompetitionalready works. Previously a bare reference returned a 400 with reasonunknown_fieldeven though the field was listed inavailable_fields. Shape errors for fields that genuinely require sub-selection now report the reasonrequires_subselectioninstead ofunknown_field.
[4.7.9] - 2026-06-09¶
Changed¶
- More trustworthy company-match confidence. Name resolution no longer reports high confidence for near-miss matches (similar-looking but different companies); confidence tiers now require the returned company's name to actually account for what you searched.
- Substantially more accurate company name resolution. Searching by short names ("Booz Allen"), common abbreviations (GDIT, HII, SAIC), or ticker symbols (LMT, MSFT) now reliably finds the right company, and vendors with several registrations under one parent company return a single, correct match instead of duplicates.
[4.7.6] - 2026-06-08¶
Changed¶
- Upgraded
dj-stripe2.9.1 → 2.10.3 (#2520). Under-the-hood plumbing change to the library that mirrors Stripe data into Tango's database: each Stripe object is now stored as a single JSON blob with the same Python attribute names exposed via property accessors. No user-visible change to checkout, billing, or self-serve plan management. Unlocks the queryable churn-feedback work introduced in #2519 — we can now ask the database for "subscriptions cancelled because of price" instead of reading each one from the Stripe dashboard. Deploy includes a one-timedjstripemigration +manage.py sync_stripe_datare-sync from the Stripe API (sub-minute given current data volume).
Internal / Infra¶
- IT Dashboard ingest moved to the lakehouse pipeline. Behind-the-scenes plumbing change — federal IT investments now load through the Parquet-based sync pipeline. No change to
/api/itdashboard/responses.
[4.7.4] - 2026-06-05¶
Added¶
- Your name on sign-up. The sign-up form now asks for your first and last name, so Tango can greet you by name and personalize your welcome email.
Changed¶
- Refreshed sign-in & sign-up pages. The login, sign-up, and email-verification screens got a visual and copy polish for a clearer, more consistent passwordless experience.
Fixed¶
- Clearer "create your account" page. When you enter an email that doesn't have an account yet, the account-creation page no longer shows confusing "already have an account? sign in instead" wording.
[4.7.3] - 2026-06-04¶
Added¶
- DoD contract-attribution signals on budget accounts.
/api/budget/accounts/now returns three new fields by default:attribution_status,attribution_confidence, andcontract_obligated_estimated. The underlying USAspending File C data captures only a small share of the Department of Defense's contract obligations, so DoD federal accounts previously looked identical to accounts with no contract activity at all (contract_obligatedcame backnull), dropping them out of any contract-share filter or ranking. Useattribution_statusto tell "no contracts" apart from "no source data," and fall back tocontract_obligated_estimated(an FPDS-based estimate) where the primary figure is missing. All three arenullfor account-years that haven't been backfilled yet.
[4.7.2] - 2026-06-03¶
Added¶
- Bot protection on sign-up and email sign-in code requests. The sign-up form and the "Email me a sign-in code" form on the login page now include a quick Cloudflare Turnstile challenge to keep automated traffic out. You may see a brief verification check before submitting.
- Microsoft sign-in + passwordless email sign-in. You can now sign in (and sign up) with a Microsoft work or school account, or with just your email and a one-time sign-in code — no password and no social provider required. The new "Email me a sign-in code" entry point appears on the login page alongside the social buttons.
Breaking¶
- Email sign-up is now open. Previously Tango required a social provider (Google, GitHub, LinkedIn) to create an account. Sign-up with email + one-time code is now supported, so the
/accounts/signup/page is reachable. If your integration assumed every Tango user is backed by a social identity, expect to see passwordless email accounts as well.
[4.7.0] - 2026-06-02¶
Changed¶
- Entity descriptions are now derived at render time.
GET /api/entities/{uei}/?shape=…(*)keeps returning{code, description}forentity_structure,entity_type,profit_structure,organization_structure,purpose_of_registration, andcountry_of_incorporation— same payload shape, same values for known codes. Behind the scenes, the description is now derived from a built-in code-to-label map (or, for country, django-genc's country table) rather than read from a stored*_desccolumn. For codes outside the known set,descriptioncomes back asnull.state_of_incorporation(*)has no map and now consistently returns{code, description: null}for every state.
Breaking¶
- Entity flag fields now serialize as bool. Four fields on
/api/entities/{uei}/change wire type:registered,registration_status,uei_status,public_display_flag. JSONtrue/false/nullreplace the previous string tokens ("Active"/"Inactive"/"Y"/"NPDY"/"Not Active"). Webhook alerts (alerts.entity.match) shipregistration_statusin the match summary and are subject to the same change. Mapping:registration_status"Active"→true,"Inactive"→false;uei_status"Active"→true,"Not Active"→false;public_display_flag"Y"→true,"NPDY"→false(a non-obvious source token — interpret as "Not Publicly DisplaYed");registered"Y"→true, any other value →null(no"N"token was observed in production data for this field). Any unmapped source value on any of the four →null. None of the four publicly-surfaced fields ever emitted a literal"N"token, so no"N"→falsemapping exists on the public surface. Action for integrators: webhook filters and client code that compared these fields to string tokens (e.g.registration_status === "Active") need to switch to bool comparison (registration_status === true). The 7*_desctop-level fields (purpose_of_registration_desc,entity_structure_desc,entity_type_desc,profit_structure_desc,organization_structure_desc,state_of_incorporation_desc,country_of_incorporation_desc) are removed from the default entity payload — use the?shape=…(*)expansion for any of these to keep getting{code, description}derived at render time. source_typeremoved from/api/entities/output. The field was auto-set to"sam"for every row and duplicated information already exposed viasource. Any client readingentity.source_typeshould switch toentity.source.- Stricter value sets on two entity enums.
evs_sourceis now constrained to{"D&B", "E&Y"}(or null), andexclusion_status_flagto{"Y", "N", "D"}(or null). Out-of-set legacy values were coerced tonullduring the upgrade. Field shape is unchanged — values still serialize as strings.exclusion_status_flag = "D"means "delinquent" and is kept intentionally alongside"Y"/"N"; a matching data-dictionary update at docs.makegov.com is tracked as a follow-up.
Removed¶
- Internal
field_provenanceJSONB column on entities (and the related CDC changelog tables) is gone. The column was never exposed via the API; field-level arbitration is handled upstream now. (#2376) - The internal
load_entities/load_dsbsSAM ingest loaders are gone. Entity data now flows entirely through the parquet-drivensync_entitiesfamily — no API surface change. (#2296)
[4.6.12] - 2026-06-01¶
Fixed¶
- Upgrading your plan now gives you the full daily request allowance right away. Previously, requests already made on your old tier counted against your new tier's daily budget immediately after an upgrade, so a paid plan could start the day with less headroom than expected. Upgrading to a higher tier now starts you on a clean daily budget. Additionally, requests rejected for exceeding your daily limit (HTTP 429) no longer count toward your daily usage.
[4.6.11] - 2026-05-29¶
Fixed¶
- Webhook alerts now deliver reliably again. Filter-based webhook alerts — opportunity, contract, entity, grant, and forecast matches — had stopped being evaluated, so matching records were not delivered to your endpoints. Alert evaluation has been corrected and delivery has resumed.
[4.6.10] - 2026-05-28¶
Changed¶
- Webhook deliveries now identify themselves. Outbound webhook requests from Tango — including test deliveries — now send a
User-Agentheader of the formMakeGov-Tango-Webhooks/<version> (+https://docs.makegov.com/)instead of a generic HTTP-client default. Receivers can use this to recognize, allowlist, and log MakeGov webhook traffic.
[4.6.8] - 2026-05-26¶
Added¶
- Federal budget data is now in the API. A new family of
/api/budget/endpoints exposes the full federal-account budget lifecycle — request → enacted → apportioned → obligated → outlayed — for ~2,000 federal accounts per fiscal year across FY17–FY26. Each account includes pre-computed execution ratios, year-over-year and 5-year trends, the contract/assistance/unlinked breakdown, and links to the underlying OMB Appendix.- List & search:
GET /api/budget/accounts/?search=acquisition+services+fund&fiscal_year=2024matches account title, agency, or bureau. Rich filters support pipeline detection (?apportioned_to_enacted_pct__gte=0.9&obligated_to_apportioned_pct__lte=0.3), forward-look (?ba_growth_next_year_pct__gte=0.5&contract_share_of_obligated_capped__gte=0.5), and request-vs-actual (?actual_vs_requested_contract__lte=0.5). - Drill-down: each account exposes
/recipients/(top contract recipients with hydrated contract details inline) and/quarters/(TAS-level quarterly obligation/outlay flow, FY21+) — both paginated with the standardcount / next / previous / resultsenvelope.?shape=*,appendix(*)adds the President's Budget Appendix object-class breakdown and?shape=*,narratives(*)adds the narrative + appropriations text. Both shape leaves returnnullwhen no data is available for the account-year, so consumers branch on truthiness once.
- List & search:
- Budget as context, not just a destination. Budget data is embedded where the question is usually being asked:
- Organizations get two opt-in budget shape leaves.
GET /api/organizations/{key}/?shape=name,budget_appropriation(*)returns the "what Congress appropriated" rollup for departments.GET /api/organizations/{key}/?shape=name,budget_spending(*)returns the "what this office (and its subtree) is spending on contracts" rollup, walking down the hierarchy so middle-tier orgs aggregate their descendants automatically. Ask for one, the other, or both — each has a fixed shape. Both support?shape=...,budget_appropriation[fiscal_year=2024](https://github.com/makegov/tango/blob/main/*)for historic views. - Entities get
GET /api/entities/{uei}/budget-flows/— the federal accounts that paid this vendor, paginated, with the surrounding account context (agency, enacted BA, contract share), the funding office hierarchy, and the contracts hydrated inline (key, NAICS, PSC, obligation, award date).
- Organizations get two opt-in budget shape leaves.
Fixed¶
- Procurement forecast refreshes no longer stall when one agency publishes a non-JSON document. A binary publication from one agency was causing the entire combined-format forecast refresh to fail, leaving other agencies' forecasts (HHS, GSA, DOI, DOL, DOT, VA, NRC, DOE, DHS, COMMERCE, ENERGY, TREASURY) out of date. Unsupported document formats are now skipped per-agency with a warning; remaining agencies refresh normally.
[4.6.7] - 2026-05-24¶
Added¶
- Entity metrics now include award counts per period.
GET /api/entities/{uei}/metrics/{months}/{period_grouping}/results now carryawards_count(distinct prime awards) andsubawards_count(distinct subawards) alongside the existingawards_obligatedandsubawards_obligateddollar sums. Counts participate in?group_by=agency/?group_by=department, so a single call returns per-agency award counts for a UEI — no more N+1 calls to the contracts / subawards endpoints. Counts are aggregated per month at the source; if you request a coarserperiod_grouping(quarter,year), the counts in each bucket sum the underlying monthly counts — read them as "award-months of activity" in that window rather than a global distinct count.
Fixed¶
- Attachment-text search is reliably fast on common terms.
GET /api/opportunities/?search=…with attachment-text search enabled previously could take several minutes on cold cache for broad terms (e.g.data governance) — long enough to hit the API timeout. Cold-path wall time on those searches now drops by roughly 30× (e.g. ~5 min → ~10 s fordata governance), comfortably under the timeout. The set of matching opportunities and per-page results are unchanged.
GET /api/opportunities/?search=is reliably fast on common terms. Searches matching thousands of opportunities (e.g.data governance,department of state) — including paired withordering=-last_notice_date/response_deadline/first_notice_date, deep pagination (?page=10), and field-selection viashape=— previously could hit the API timeout when the database cache was cold. Cold-path wall time on these searches now drops by roughly 6-7× and stays well under the timeout. The set of matching opportunities and the per-page results are unchanged. One caveat for clients that exhaustively crawl results via?page=Nwhile attachment-text search is enabled: the approximatecountfield can come back lower than before, which lowers the maximum?page=you can navigate to. If you want the precise total — or need to crawl all matches without missing pages — pass?exact=true(this was already the documented way to opt out of approximate counts; nothing about its behavior has changed).
[4.6.6] - 2026-05-23¶
Added¶
- Follow one or many resources with a single webhook alert. Webhook alerts for opportunities, contracts (including IDVs / OTAs / OTIDVs), grants, and forecasts now accept the same identifier you get from each resource's detail endpoint — and you can pass several at once. Use
opportunity_id,key(for contracts and IDV / OTA / OTIDV),grant_id, orid(for forecasts) in your alert'sfilters, either as a single value or as a pipe-OR list (e.g."key": "key1|key2|key3"). One alert that follows a set of resources is more efficient than one alert per resource, and counts as a single subscription against your tier's alert limit. Up to 500 IDs per filter; the same identifiers are also accepted on the corresponding list endpoints (e.g.GET /api/contracts/?key=k1|k2|k3).
[4.6.5] - 2026-05-22¶
Breaking¶
- Entity flag fields now boolean.
registered,registration_status,uei_status,public_display_flag,bonding_flag,disaster_registry_flag,debt_subject_to_offset,credit_card_usage,edi_information_flagnow serialize as JSONtrue/false/nullinstead of string tokens ("Active"/"Inactive"/"Y"/"N"/"NPDY"/"NA"). Webhook subscribers filtering onregistration_status === "Active"need to switch to=== true. Seven*_desccompanion fields (purpose_of_registration_desc,entity_structure_desc,entity_type_desc,profit_structure_desc,organization_structure_desc,state_of_incorporation_desc,country_of_incorporation_desc) are dropped from the top-level entity payload — use the?shape=…(*)expansions to get{code, description}derived at render time.
Fixed¶
GET /api/opportunities/?search=no longer times out on common terms. Searches likedata governanceordepartment of statepreviously could return a gateway timeout; they now return results reliably.
[4.6.2] - 2026-05-18¶
Added¶
GET /api/opportunities/?search=matches attachment text.?search=now searches text from opportunity attachments in addition to notice titles and descriptions. Matched results include asnippetcentered on the hit.
[4.6.1] - 2026-05-13¶
Fixed¶
GET /api/vehicles/?search=matches text from member-IDV descriptions. Vehicles whosesolicitation_identifieris a single run-on token (e.g.RFPCMS2016SPARC) now match substring queries like?search=SPARCvia the underlying IDV descriptions. The endpoint's default ordering (solicitation_identifier, agency_id, uuid) is unchanged. (issue #2224)
[4.6.0] - 2026-05-12¶
Added¶
- Shape aliases:
naics_code(...)andpsc_code(...)now work as expand forms.?shape=naics_code(code,description)is accepted as a synonym fornaics(...)on/api/contracts/and/api/idvs/(the only award endpoints with a NAICS expand).?shape=psc_code(code,description)is accepted as a synonym forpsc(...)across all award endpoints (/api/contracts/,/api/idvs/,/api/otas/,/api/otidvs/). The expanded object comes back under the canonical key (naics/psc). Bare scalarnaics_code/psc_codeis unchanged — it still returns the raw code value. (issue #2257) POST /api/webhooks/endpoints/test-delivery/now acceptsendpoint. The canonical field name matchesPOST /api/webhooks/subscriptions/, so SDKs and docs can use one identifier across both calls. The legacyendpoint_idfield is still accepted as a deprecated alias; if both are provided,endpointwins.endpoint_idwill be removed in the next major release. (issue #2252)POST /api/webhooks/alerts/accepts an optionalendpoint(UUID) field. Multi-endpoint accounts can disambiguate without dropping to/api/webhooks/subscriptions/; single-endpoint accounts can still omit it. (issue #2256)
Breaking¶
- Subject-based webhook subscriptions removed (#2267). Subscriptions now match by saved filters (
/api/webhooks/alerts/) only —subject_type,subject_ids, and the legacyresource_ids/entity_type/entity_ids/change_typesfields are no longer accepted byPOST /api/webhooks/subscriptions/(orPATCH). Existing integrations that still send these fields will see them silently dropped. See the alerts guide on docs.makegov.com for migration.
Fixed¶
- OpenAPI now matches runtime for
ordering./api/notices/no longer advertisesordering(the endpoint never accepted it — every value 400s), and/api/subawards/advertises itsorderingenum aslast_modified_date/-last_modified_dateonly. SDKs generated from the schema no longer surface anorderingkwarg on notices, and the subawards kwarg is typed to the actual allowlist. (issue #2254) - OpenAPI
orderingnow has a realenumon the remaining endpoints. Completes the schema/runtime parity work started in #2258:/api/contracts/,/api/idvs/,/api/opportunities/,/api/forecasts/,/api/vehicles/,/api/vehicles/{uuid}/orders/, and/api/gsa_elibrary_contracts/now declare an explicitenummatching each viewset's runtime allowlist (ordering_fields∪ordering_fields_map.keys()+ their-variants). SDK generators emit a typed kwarg instead of a free-formstr, so invalid values fail at type-check time rather than as a runtime 400. (issue #2262) POST /api/webhooks/alerts/no longer returns a sibling endpoint's subscription row when the same user has the same filter(query_type, filters)on two different endpoints. The post-create lookup is now scoped by the resolved endpoint instead of user — a latent dedup bug that became reachable once multi-endpoint disambiguation was added. (issue #2256)GET /api/webhooks/endpoints/sample-payload/no longer 500s. The endpoint previously referenced a subject-type attribute that was removed when subject-based webhooks were deprecated. Returns the alert-only sample set as expected.
[4.5.2] - 2026-05-11¶
Changed¶
- Public docs moved to docs.makegov.com. The in-app MkDocs site at
tango.makegov.com/docs/*is gone; legacy URLs 301-redirect to the new host path-preserving (/docs/getting-started/pricing/→docs.makegov.com/getting-started/pricing/, etc.). Bookmarks keep working. Pricing-tier upgrade responses (upgrade_url) and the welcome-email "Docs" CTA now point at the new host directly.
Fixed¶
/api/otas/and/api/otidvs/pagination no longer 400s on page 2. Cursor-based pagination on these endpoints used to return HTTP 400 whenever the page-1 anchor row had a NULLaward_date— common on OTAs/OTIDVs because many leading rows have no resolvable award date. Page 2 now succeeds and returns the next slice as expected. No client changes required. (issue #2139)
[4.5.1] - 2026-05-09¶
Changed¶
/api/notices/:?shape=office(organization_id)is now valid. The office payload on notices gainsorganization_idas a 7th key, matching the surface already exposed on/api/opportunities/and the awards endpoints. The other six keys (office_code,office_name,agency_code,agency_name,department_code,department_name) are unchanged.
Performance¶
/api/notices/is materially faster. Theofficeexpand no longer fetches fullOrganizationrows on every request. Uncached?limit=100drops from ~0.36 s to ~0.07–0.1 s. No query parameter changes needed; same shapes work faster. (issue #2236)
[4.5.0] - 2026-05-09¶
Fixed¶
?shape=case_id,docketsno longer 500. Namingdocketsas a leaf in the protests shape — without an inner expand likedockets(case_number)— used to raise an internal serializer error. The leaf is now equivalent todockets(*)and returns the default docket projection. Detail endpoint mirrors list. (issue #2222)
Performance¶
- Five more endpoints are materially faster. The organization expand on
/api/forecasts/,/api/grants/,/api/itdashboard/,/api/opportunities/(theofficeexpand), and awards (/api/contracts/,/api/idvs/,/api/otas/,/api/otidvs/) no longer fetches fullOrganizationrows from the database. Uncached?limit=100timing across eight endpoints: ~8.7 s total → ~1.9 s total (−78%); opportunities alone 3.4 s → 0.4 s (−88%), forecasts 2.1 s → 0.15 s (−93%). No query parameter changes needed — same shapes work faster. (issue #2225, PR #2233)
/api/contracts/,/api/idvs/,/api/otas/,/api/otidvs/:?shape=awarding_office(organization_id)and?shape=funding_office(organization_id)are now valid. The office payload gainsorganization_idas a 7th key (the other six —office_code,office_name,agency_code,agency_name,department_code,department_name— are unchanged). Rows that hit the legacy JSON fallback return{"organization_id": null, ...}.
/api/protests/is materially faster. End-to-end on a default?limit=100request, latency drops from ~3.8 s to ~80 ms. No API changes; same response shape.
[4.4.0] - 2026-05-08¶
Added¶
- Agency filter accepts more identifier forms. The
agency/awarding_agency/funding_agencyfilters on every endpoint that exposes one now resolve the same way: hand them a name, abbreviation, or code (CGAC, FPDS, AAC, and others) and Tango figures out the matching agency subtree. See the new Agency search guide. (issue #2207)
Changed¶
- Trimmed
?ordering=whitelist on/api/vehicles/. Removedawardee_countandvehicle_contracts_valuefrom the v4.3.0 ordering surface — both wereSubqueryannotations onVehicleStatswith no backing index, and their matching*_min/maxfilters were intentionally deferred. The remaining surface isvehicle_obligations,latest_award_date,total_obligated,award_date,last_date_to_order,fiscal_year,idv_count,order_count. (issue #2202 follow-up)
Breaking¶
- Comma
,is no longer anANDseparator in filter values; multi-value uses|only. Across every endpoint that accepts smart-text filter values (naics,psc, set-asides, business types, entity / agency text searches, etc.), a comma is now a literal character in the token value rather than anANDseparator.?awarding_agency=HHS,DODwas previously treated asHHS AND DOD(which was always empty — a single record can't equal two different values) and is now treated as a single literal value"HHS,DOD"(also always empty, but for the literal reason). For union semantics use|:?awarding_agency=HHS|DODreturns rows in either subtree. For intersection semantics, repeat the query parameter or use multiple separate filter parameters —AND-joining values inside one parameter has no clear use case in this API. Mixed inputs like?field=A,B|Cno longer raise a400 "Cannot combine AND and OR in the same query"error; they're parsed as"A,B"OR"C". The change also fixes the long-standing footgun where federal entity / agency names containing literal commas ("ENERGY, DEPARTMENT OF","GENERAL DYNAMICS, INC.") silently produced empty results when pasted into a filter. Out of scope:/api/gsa_elibrary_contracts/?sin=,?uei=,?search=continue to honor the prior comma-as-AND grammar (separate code path). (issue #2214)
Removed¶
- AND-via-comma is fully removed from documentation, swagger, and help text. Comma-as-AND no longer works anywhere downstream of
BaseSmartFilter/BasePerformantFilterand the swagger / public docs no longer advertise it. Multi-value uses|(OR) only. Consumers using,for intersection semantics should migrate to multiple distinct query parameters (e.g.?naics=541511&fiscal_year=2024rather than trying to AND values inside a single param). The swagger description-builder (make_description) now raisesValueErrorif a future internal call site passes"conjunctive": True, so the option cannot be reintroduced silently. Public docs (docs/guides/patterns/agency-search.md,docs/webhooks-user-guide.md,docs/index.md, forecasts API user guide) rewritten to drop OR-vs-AND framing in favor of OR-only language. (issue #2214)
Fixed¶
- Multi-value
?agency=A|Breturned zero results. Pipe-separated values onagency/awarding_agency/funding_agencynow correctly return the union of both subtrees (?awarding_agency=HHS|DODreturns rows from either) on every endpoint. Single-value queries are unchanged. (issue #2210, also reported externally asmakegov/tango-public#51)
- Numeric Federal Hierarchy keys (
fh_key) now resolve to the canonical agency. Previously, passing an L2fh_key(e.g. NIH's100019747) could resolve to a deep descendant office (an L3 within NIH whose name happened to contain "AGENCY") instead of NIH itself. The agency now picks the canonical match and the subtree expansion gives the full agency's records. Same behavior change applies to numeric CGAC-style codes when the leading zero is stripped (?agency=89now resolves to DOE rather than a sub-office under DOE).
- Long pipe-separated agency queries no longer return HTTP 400 on award endpoints.
?awarding_agency=DOD|HHS|VA|DHS|DOE|TREAS|USDA|DOJ|DOT|GSA(and similar 10-token OR queries) now return HTTP 200 with the union of every named department's awards on/api/contracts/,/api/idvs/, and other award endpoints. Empty / pipe-only inputs (|||) and all-unresolvable inputs (FAKE1|FAKE2|FAKE3) likewise return HTTP 200 with an empty result set rather than 400. A single typo'd agency name still returns 400 (e.g.?awarding_agency=DEPRTMENTOFENERGY).
- Single-word agency abbreviations no longer accidentally match an unrelated office. Some L3+ offices in the Federal Hierarchy data have a
nameliterally equal to a department abbreviation (e.g. an L3 office named"HHS"); previously, querying?agency=HHScould match that office instead of the canonical L1 HHS department, returning a narrow result set. Abbreviations now consistently resolve to the canonical agency. Multi-word canonical names like?agency=ENERGY, DEPARTMENT OFare unchanged.
?agency=A|Bon/api/forecasts/,/api/grants/,/api/protests/no longer over-matches via partial-text fallback. Where a token resolves cleanly to an agency, only that agency's subtree is matched. The previous behavior fell back to a partial-text match on the legacy text column when paired with multi-value input, which over-matched compound codes (e.g.?agency=HHS|...on grants matched every grant with"HHS-..."in itsagency_code— 1500+ instead of 40). Unresolvable tokens still fall back to the legacy text column as before.
[4.3.0] - 2026-05-07¶
Added¶
- Vehicle list filtering (
/api/vehicles/). New query parameters cover enum / code fields (vehicle_type,type_of_idc,contract_type,set_aside,who_can_use), reference codes (naics_code,psc_code,program_acronym), org hierarchy (agency,organization_id), numeric ranges (total_obligated_min/max,idv_count_min/max,order_count_min/max), and dates (fiscal_year,award_date_after/before,last_date_to_order_after/before).vehicle_type,type_of_idc, andcontract_typeaccept pipe-separated multi-value selections via theORsmart-filter grammar (e.g.?vehicle_type=A|B|C). The?ordering=whitelist is expanded withtotal_obligated,award_date,last_date_to_order,fiscal_year,idv_count,order_count,awardee_count, andvehicle_contracts_valuealongside the existingvehicle_obligationsandlatest_award_date. Examples:GET /api/vehicles/?vehicle_type=A,GET /api/vehicles/?vehicle_type=A|B,GET /api/vehicles/?naics_code=541512&total_obligated_min=1000000,GET /api/vehicles/?ordering=-total_obligated. (issue #2202) - Awardee search (
/api/vehicles/{uuid}/awardees/?search=). Filter the awardees response by entity-aware full-text search across IDV fields (PIID, key, solicitation identifier, NAICS, PSC) and the recipient entity (legal name, address, etc.). Examples:GET /api/vehicles/{uuid}/awardees/?search=ACCENTURE,GET /api/vehicles/{uuid}/awardees/?search=GS-35F-0119Y. The response count reflects filtered results. (issue #2202) /api/forecasts/: neworganizationshape expand. Each forecast row is now linked to a canonicalagencies.Organizationresolved deterministically from the existingagencytext field (12 distinct production acronyms —HHS,DHS,DOI,GSA,DOE,DOT,VA,DOL,NRC,NSF,COMMERCE,TREASURY). Use?shape=organization(*)for the canonical 7-key office payload (organization_id,office_code,office_name,agency_code,agency_name,department_code,department_name). Theorganizationexpand is also in the default response shape (no?shape=needed). The legacyagencytext field and filter are unchanged./api/grants/: neworganizationshape expand. Each grant opportunity is now linked to a canonicalagencies.Organizationresolved from the free-textagency_codefield — the resolver walks compound codes likeHHS-NIH11/HHS-FDA/HHS-CDC-GHC/DOS-ECA/USDOJ-OJP-BJAsegment-wise to the most-specific aliased sub-agency (NIH, FDA, CDC, the State Dept bureau, BJA, etc.) rather than collapsing to the parent department. Codes whose only recognizable token is the dept prefix (DOS-MEXcountry codes, etc.) resolve to the L1 department.?shape=organization(*)returns the canonical 7-key office payload;organizationis in the default response shape. The legacyagencytext field and filter are unchanged./api/itdashboard/: neworganizationshape expand (free tier — same gating asagency_name). Each IT Dashboard investment is now linked to a canonicalagencies.Organization, resolved deterministically from(agency_code, bureau_code)(preferred) and(agency_name, bureau_name)(fallback).?shape=organization(*)returns the canonical 7-key office payload. A small minority of investments (e.g., U.S. Army Corps of Engineers) currently resolve tonulldue to gaps in the canonical org data — tracked in #2176./api/protests/: neworganizationshape expand (free tier — same gating as the existingagencytext field). Each protest case is now linked to a canonicalagencies.Organizationresolved deterministically from the free-textagencyfield — handles hierarchical names (Department of the Army : NAVSEA : Surface Warfare), DoD branches (Army / Navy / Air Force resolve to their AGENCY-typed orgs under DoD, not to DoD itself), and synthetic GAO buckets (Independent Government Entities/Legislative Agenciesskip the dept lookup).?shape=organization(*)returns the canonical 7-key office payload. The deterministic FK is the canonical "what agency is this protest against?" signal — distinct from (and complementary to) the existing Bayesian?shape=resolved_agency(*)expand which returns a best-guess match with confidence and rationale. Both signals can be requested in the same call.
Changed¶
organizationis now in the default response shape for/api/forecasts/,/api/grants/,/api/itdashboard/, and/api/protests/. Previously opt-in via?shape=organization(*); now always present. Renders as the canonical 7-key office payload — same shape asawards,opportunities, andvehicles. Droporganizationfrom your?shape=parameter if you don't want it./api/opportunities/officepayload now includesorganization_id(was 6 keys, now 7). Additive change — same UUID is resolvable as before, just no longer requires a separate query to round-trip from the office to the underlying Organization. Other six keys (office_code,office_name,agency_code,agency_name,department_code,department_name) unchanged.- Bare
?shape=<expand-name>syntax now works on every dataset that has the expand registered withrelation="select"or"prefetch". Previously rejected with a 400unknown_fielderror on most endpoints —?shape=organizationreturns 200 on forecasts / grants / itdashboard / protests / vehicles / opportunities. The parens form (organization(*)) was always working and is unchanged.
[4.2.2] - 2026-05-05¶
Fixed¶
?shape=opportunity(...)on/api/vehicles/now returns the full Opportunity shape instead of a 400. The v4.2.1 implementation shipped a 4-field stub that only allowedopportunity_id,solicitation_title,solicitation_description, andsolicitation_date. Any consumer requesting a field the pre-cutovervehicle.opportunityFK exposed —title,sam_url,first_notice_date,office(...), or any other Opportunity field — received a 400 from shape validation. The fix resolves the fullOpportunitybysolicitation.opportunity_idand routes through the complete Opportunity shape so every field and nested expand (includingoffice(office_code,office_name,agency_code,agency_name,department_code,department_name)andset_aside(code,description)) works as it did pre-cutover. Available on both list and retrieve; opt-in via?shape=opportunity(...)(not in the default response shape). On list pages, all Opportunity rows for the page are fetched in a single batched query, soopportunity(...)is safe to request alongside other shapes. Closes #2193.solicitation_identifierno longer leaks the internalACRO:storage prefix on synthetic GWAC vehicles. Previously, certain GWAC vehicles returned values like"solicitation_identifier": "ACRO:SEWP". TheACRO:prefix is an internal storage detail (collision avoidance with real solicitation numbers in the unique index); the API response now strips it, so SEWP-family vehicles surface as"SEWP","SEWP IV","SEWP V", etc. Useis_synthetic_solicitation(boolean) andprogram_acronym(string) to identify GWAC-recovery vehicles. Refs #2193.
Changed¶
Deprecation: trueHTTP header removed from/api/vehicles/. v4.2.1 added an unconditionalDeprecation: trueheader on every list and retrieve response, advertising deprecation ofagency_details,competition_details, and?shape=opportunity(...). We're walking it back: there are no committed first-class replacements foragency_details.funding_officeor for uniquecompetition_detailskeys (extent_competed,set_aside,solicitation_procedures,number_of_offers_received), no sunset date, and?shape=opportunity(...)is now restored to full pre-cutover parity. The fields continue to work; tooling that was watching for the header should expect it to be absent until we publish a real migration plan.
Breaking¶
?shape=opportunity(solicitation_title)/opportunity(solicitation_description)/opportunity(solicitation_date)no longer validate. These three fields are top-level Vehicle fields, not Opportunity model fields — the v4.2.1 stub erroneously accepted them insideopportunity(...). Use?shape=solicitation_title,solicitation_description,solicitation_date(top-level, no nesting) instead — these come back in the default list shape and are cheaper than the fullopportunity(...)expand.opportunity(opportunity_id)continues to work —opportunity_idis also an Opportunity model field, AND it's available as a top-level Vehicle field. SDK consumers (tango-python,tango-node) verified clean. If you adopted the v4.2.1 stub shape between #2177 and #2193 landing, move the three companion fields to top-level.
[4.2.1] - 2026-05-03¶
Added¶
/api/vehicles/responses now includeis_synthetic_solicitation(boolean). True for a small set of GWAC vehicles where the underlying IDVs lacked a real solicitation identifier but had a recognizableprogram_acronym(8ASTARS,SEWP IV,COMMITS NEXGEN, etc.) — the API keys the vehicle off the program acronym so these GWACs surface in results instead of being dropped. SAM enrichment (solicitation_title/description/date/opportunity_id) is always null on synthetic rows. Use this flag to distinguish them in your own filtering or display logic./api/vehicles/responses now include three new top-level fields on both list and retrieve:program_acronym(the vehicle's program acronym, e.g."OASIS+", when present),idv_count(number of IDVs in the vehicle),total_obligated(sum of obligations across the vehicle's IDVs). Retrieve responses also include a nestedmetricsobject with 12 new metrics:competed_rate,award_concentration_hhi,order_concentration_hhi,top_recipient_share,avg_offers_received,avg_order_value,max_order_value,using_agency_count,recent_obligations_24mo,recent_orders_24mo,days_since_last_order,obligation_to_ceiling_ratio. Request the bag via?shape=metrics(*)or specific keys via?shape=metrics(competed_rate,top_recipient_share).organizationfield on/api/vehicles/list and retrieve responses — the awarding Organization expanded to the canonical 7-key office payload (organization_id,office_code,office_name,agency_code,agency_name,department_code,department_name). The same payload now also populatesagency_details.awarding_officeon both list and detail responses (was previously null on list and sometimes null on detail when per-IDV office signals were missing). Cached for 1 year per Organization.description(string) anddescriptions(array) on/api/vehicles/are populated again. Both had been returning null since v4.2.0; that's now fixed. The legacy compose-a-single-string behavior (descriptionreturns the longest common substring acrossdescriptions) is unchanged.
Changed¶
- Response keys are now alphabetized at every nesting level on
/api/vehicles/list and detail. Leaves and nested expand objects interleave by key name. The order is stable and independent of the order keys appear in your?shape=parameter —?shape=uuid,organizationand?shape=organization,uuidproduce the same JSON. Affects every shape-using endpoint (vehicles, contracts, opportunities, entities, etc.) — if you've been relying on response-key order, switch to explicit key access. The OpenAPI schema (Swagger UI) and SDK type definitions are unchanged.
Changed (internal data path; API surface preserved)¶
/api/vehicles/{uuid}/agency_detailsandcompetition_detailsJSON blobs and the?shape=opportunity(...)expand are now recomputed at request time (from the vehicle's IDVs and theawards_vehicle_solicitationcompanion respectively) instead of being stored columns. No deprecation announced: see the #2193 / 4.2.2 entry for the walk-back. The fields keep working. List responses includeagency_details.awarding_officepopulated from the same source as the top-levelorganizationfield —agency_details.funding_officeis null at the vehicle grain (see below).- Top-level
organization_idon/api/vehicles/retrieve responses is dropped from the default shape — it's redundant withorganization.organization_id(the same UUID, nested inside the office payload). Still requestable explicitly via?shape=organization_id, but the default response no longer surfaces it. - Funding-side organization signal removed from vehicles.
agency_details.funding_officeis null at the vehicle grain. Vehicles are not the right grain for funding-org analysis: a single vehicle is funded by many agencies via task orders, and task-order data already carries funding-side detail. Consumers needing funding-org rollups should query/api/vehicles/<uuid>/orders/and aggregate from there.
[4.2.0] - 2026-04-30¶
Added¶
- G2X integration (#1881): new
/api/news/and/api/events/endpoints (G2X User group required), a/api/company/rag/endpoint for unified company/people/news lookups, andg2x_about/g2x_ai_summary/g2x_employee_countfields available via?shape=on/api/entities/when a linked G2X profile exists - eBuy opportunities now available on
/api/opportunities/via?domain=ebuy(eBuy only) or?domain=sam,ebuy(union with SAM). eBuy results are served live from the G2X GraphQL API — no stored snapshot; filter byrequest_type,contract_type,set_aside_type,buyer_department,closes_after, orsearch. On upstream outage,?domain=sam,ebuydegrades to SAM-only with anupstream_warningsfield so SAM queries stay up vehicleis now available as a shape expand on/api/contracts/. Use?shape=vehicle(uuid,solicitation_title)to attach the contract's parent vehicle to each result, or?shape=vehicle(*)for the curated field set (uuid,solicitation_identifier,solicitation_title,solicitation_description,agency_id,vehicle_type,type_of_idc,contract_type,who_can_use,solicitation_date,award_date,last_date_to_order,fiscal_year,naics_code,psc_code,set_aside,description). Contracts without a parent IDV — or whose parent IDV is not part of any vehicle — returnnull. For richer vehicle detail (awardees, opportunity, totals), follow the returneduuidto/api/vehicles/{uuid}/. Resolution adds one query per page, not per row.
[4.1.1] - 2026-04-27¶
Added¶
cageis now an alias forcage_codeacross the API. Query entities with?cage=or?cage_code=(sending both is rejected — pick one). Awardrecipientshapes accept either key:recipient(cage)returnscage,recipient(cage_code)returnscage_code, andrecipient(*)returnscage_codeonly. If you want the shortcagekey, ask for it explicitly (refs #1350).
Fixed¶
- Filtering subawards by
?awarding_agency=<name|code>or?funding_agency=<name|code>now uses the same hierarchical resolution as the contracts and opportunities endpoints. Department queries (e.g.?awarding_agency=DOE,?awarding_agency=089) return all subawards under that department's subtree; agency codes (e.g.?awarding_agency=8900for DOE,?awarding_agency=7530for CMS) return that agency's subtree; abbreviations (e.g.?awarding_agency=CMS) resolve via the canonical alias table. Result counts will shift compared to the previous literal-code-match behavior — most notably, name-based queries that previously returned 0 (e.g.?awarding_agency=CMS) now return the full agency subtree, semantically aligned with/api/contracts/?awarding_agency=.... Closes #2076.
[4.1.0] - 2026-04-26¶
Internal / Infra¶
- Internal ETL pipeline rewrite for awards data freshness. No API surface or query-behavior changes (refs #2082).
[4.0.14] - 2026-04-26¶
Changed¶
- Latency spikes on
/metrics/endpoints during scheduled refreshes should be noticeably smaller after an internal pipeline rewrite. API surface is unchanged — the same/metrics/endpoints with?group_by=agency/?group_by=departmentparameters continue to work. - Filtering opportunities or notices by
?agency=<name|code>now uses the same hierarchical resolution as the contracts agency filter. Department queries (e.g.?agency=DOE,?agency=089) return the whole department subtree; agency codes (e.g.?agency=8900for DOE,?agency=8960for FERC) return just that agency's subtree. Result counts will shift compared to the previous full-text-search behavior — the new counts are semantically correct subtree matches rather than token co-occurrence. Closes #2058.
Fixed¶
- Forecasts loader: reduced log noise and false Sentry alerts.
sanitize_yeardata-quality events are now DEBUG-level; empty records is a no-op rather than an error; partial validation failures log as warnings (visible in admin, no Sentry alert). Closes #1974, closes #1935. - Filtering contracts by a department's full legal name (e.g.
?awarding_agency=ENERGY, DEPARTMENT OF) now correctly returns the whole department's contracts instead of 0 results. The embedded comma in federal inverted names was being misinterpreted as a target/context separator. Queries for?awarding_agency=DOEand the numeric CGAC/FPDS codes are unchanged. Closes #2079. - Filtering contracts by
awarding_agencyorfunding_agencyusing a real (but currently inactive) agency code such as5706,8933, or8944now returns an HTTP 200 response with the matching contracts, instead of an HTTP 400 "No agency found matching" error. Queries for agency codes that don't exist at all continue to return 400 as before. Closes #2057. - Filtering contracts by a numeric agency code (e.g.
awarding_agency=8900,funding_agency=8900) now returns the correct subtree of contracts instead of narrowing to a single sub-office. A 4-digit FPDS code resolves to that agency and its descendants; a 3-digit CGAC code resolves to the whole department (matching whatawarding_agency=DOEreturns). Name queries are unchanged. Closes #2056. - Filtering opportunities or notices by a numeric agency code (e.g.
?agency=8900) now resolves to the correct subtree instead of matching agencies whose search index happens to contain that token. Fixes over-broad matches for short numeric queries and makes sibling-agency queries (like DOE8900vs FERC8960under the same department) correctly distinct. Closes #2058.
[4.0.12] - 2026-04-22¶
Added¶
- The
/welcome/page now displays your active API key(s) with copy-to-clipboard and visibility toggle — no longer need to navigate to your profile to find your key after signing up
Changed¶
- The welcome page's API keys card now matches the richer layout used on your profile: full-width card at the top of the page with your daily requests remaining, per-key name + "Active" badge, click-to-copy, show/hide, and created date + rate limits. "Your plan" and "Quick links" now sit side-by-side below it so nothing important is hidden below the fold
[4.0.11] - 2026-04-21¶
- No user-facing changes
[4.0.10] - 2026-04-21¶
- No user-facing changes
[4.0.9] - 2026-04-20¶
- No user-facing changes
[4.0.8] - 2026-04-20¶
- No user-facing changes
[4.0.7] - 2026-04-18¶
- No user-facing changes
[4.0.6] - 2026-04-17¶
- No user-facing changes