Changelog¶
All notable changes to @makegov/tango-node will be documented in this file.
This project follows Semantic Versioning.
[1.3.0] - 2026-08-14¶
Changed¶
- Node.js 20 is now the minimum supported version (
engines.node >= 20); the CI matrix moves from 18/20/22 to 20/22/24. Node 18 has been end-of-life since April 2025, and the security-patched test toolchain below requires 20+. Nothing in the published code changed — existing installs on Node 18 keep working, but they are no longer tested or supported. - Dev-dependency security upgrade clearing all six open Dependabot alerts (vitest 2.1.9 → 4.x, @vitest/coverage-v8 to match, tsx to the esbuild-0.28 line — the published package is unaffected, its only runtime dependency remains
commander). One test-only fix for vitest 4's stricter mocks: constructable mocks now use regular-function implementations.
[1.2.0] - 2026-08-14¶
Added¶
-
DIBBS, exclusions, and SBIR/STTR endpoint support (parity with tango-python v1.3.0). Six endpoint families had no SDK support at all — no models, no methods. Added
listDibbsRfqs/getDibbsRfq,listDibbsRfps/getDibbsRfp,listDibbsAwards/getDibbsAward,listExclusions/getExclusion,listSbirTopics/getSbirTopic, andlistSbirSolicitations/getSbirSolicitation, with every filter param in the API contract exposed as a typed option, explicit shape schemas (including the nested organization/awardee/topic/document expands),ShapeConfigdefaults, and async iterators (iterateDibbsRfqs,iterateDibbsRfps,iterateDibbsAwards,iterateExclusions,iterateSbirTopics,iterateSbirSolicitations, plus the matchingIterableListMethodentries for the genericiterate()). New model interfaces:DibbsRfq,DibbsRfp,DibbsAward,Exclusion,SbirTopic,SbirSolicitation.Two API behaviors are worth knowing.
is_open(DIBBS) andis_currently_excluded(exclusions) are derived at query time, so filter with theopen/activeoptions rather than shaping on those fields. And DIBBStotal_contract_priceis the order total repeated on every line item — never sum it across rows; deduplicate on award + delivery-order number first. - Full typed filter surface onlistBudgetAccounts(parity with tango-python and the API contract).ListBudgetAccountsOptionsnow declares everybudget/accountsfilter param — the exact /__gte/__ltetriplet for all 26 numeric lifecycle, ratio, and trend fields (requested_ba,enacted_ba,apportioned,obligated_total,outlayed_total,unobligated_balance, the contract/assistance breakdowns, the*_pct/*_cappedratios, YoY + 5-year-CAGR trends, andactual_vs_requested_contract), plus the__in/__icontainsvariants of the categorical filters (federal_account_symbol,fiscal_year,agency_code,bureau_name,bea_category,subfunction_code,account_title__icontains). -getGsaElibraryContract(uuid, options)for/api/gsa_elibrary_contracts/{uuid}/(parity with tango-python), with the standardshape/flat/flatLists/joineroptions and theGSA_ELIBRARY_CONTRACTS_MINIMALdefault shape. - Typed filter options that previously worked only through the index-signature escape hatch:keyonlistContracts/listIdvs/listOtas/listOtidvs,cageonlistEntities,idonlistForecasts,opportunity_idonlistOpportunities,previous_uiionlistItDashboard,naics_codeonlistProtests(sent verbatim, not remapped tonaics), andhas_awardsonlistPsc. The filter-shape conformance gate now reports zero index-signature warnings. - Generated shape-coverage overlay (parity with tango-python v1.4.0):src/shapes/generatedOverlay.ts, machine-generated by the newscripts/generate-shape-overlay.tsfrom the vendored contract pluscontracts/observed_shape_types.json(live-API type observations vendored from tango-python).SchemaRegistrymerges the overlay over the curated explicit schemas, so the typed shape API now accepts every field and expand the API returns — including entityrelationships(type, source), previously-unmapped models (Naics,PSC,MasSin,BudgetAccount,AssistanceListing,BusinessType), and all the code/description expands that were flattened to scalars. The reverse shape-coverage gate now reports zero gaps andcontracts/shape_coverage_baseline.jsonis empty (416 → 0). - Agency-filter diagnostics onPaginatedResponse(parity with tango-python v1.5.0). Every list method now surfaces the API'smetapayload, plus three parsed views:agencyWarnings(human-readable notes about dropped or loosely-matched agency tokens),unresolvedAgencyTokens(tokens that matched no organization, keyed by filter name), andresolvedAgencies(the organizations each token actually resolved to — the only way to catch a token fuzzy-matching an agency you did not intend). All three are total: absent or malformedmetayields empty values, never a throw. On thePaginatedResponsetype,metaand the three parsed views are declared as optional properties — responses built by the client always populate them, but existing code that constructs the type without them keeps compiling. - Structured shape errors onTangoValidationError(parity with tango-python's.issues/.available_fields): newissuesandavailableFieldsgetters expose the API's structured 400 payload — entries like{"path": "tradeoff_process", "reason": "unknown_field"}and the endpoint's valid field set — instead of leaving callers to parseresponseDataby hand. - Vendored the canonical API filter/shape contract atcontracts/filter_shape_contract.json(API 4.22.0), so conformance checking is fully offline — no token, no sibling checkout. - New reverse shape-coverage gatescripts/check-shape-coverage.ts(npm scriptcheck-shape-coverage): walks every resource's shape tree in the vendored contract against the SDK's explicit schema registry and fails on any field or expand the SDK does not capture, unless recorded incontracts/shape_coverage_baseline.jsonas tracked backlog. - Accepted-gaps baselines:contracts/conformance_baseline.json(missing filters + unimplemented resources) andcontracts/shape_coverage_baseline.json(known shape-coverage gaps). Baselined gaps report as warnings; anything new is an error. - Recorded integration test layer (parity with tango-python's VCR-cassette suite): a record/replay harness (tests/integration/harness.ts) around the SDK's injectablefetchImpl, with JSON cassettes intests/cassettes/recorded against the live API. Default runs replay offline — a missing cassette is a hard failure so drift is loud, while an absent cassettes directory (a fork without the corpus) skips the suite with a warning.TANGO_REFRESH_CASSETTES=truere-records serially against the live API;TANGO_USE_LIVE_API=truebypasses cassettes. Cassettes never store request headers, keep only an allowlisted response-header subset, and the recorder throws rather than serialize API-key material anywhere in an interaction (asserted by unit tests intests/unit/integration-harness.test.ts). - Per-resource integration tests (tests/integration/*.test.ts, 44 tests / 44 cassettes) covering contracts (including cursor pagination and shaping), entities, IDVs, vehicles, opportunities, notices, grants, forecasts, agencies + organizations, protests, budget accounts (fiscal-year range round-trip), DIBBS, exclusions, SBIR, NAICS/PSC reference data, subawards, and edge cases (404, invalid ordering, empty result page). - Env-gated production smoke suite (tests/production/smoke.test.ts, the node port of tango-python'stests/production/): runs only withTANGO_LIVE_TESTS=trueplusTANGO_API_KEY, asserting light live-API invariants (pagination shape, shaping, rate-limit header parsing). Excluded from default runs and CI byvitest.config.ts.
Changed¶
- Both conformance baselines shrank with the new resources:
dibbs/*,exclusions, andsbir/*leftunimplemented_resourcesincontracts/conformance_baseline.json, and theirunmapped_resourceentries leftcontracts/shape_coverage_baseline.json(422 → 416 known gaps, then 416 → 0 with the generated overlay above). scripts/check-filter-shape-conformance.tsnow defaults to the vendored contract instead of a checked-out tango API repo (TANGO_CONTRACT_PATHor--manifeststill point it at one), covers every resource in the 4.22.0 contract in its resource map, and treats an unimplemented resource as an error unless baselined.- Removed the dead legacy
.eslintrc.cjs— the flateslint.config.jshas been the operative ESLint config since the flat-config migration, and the leftover file only invited divergent edits.
Fixed¶
- The six new list methods (
listDibbsRfqs,listDibbsRfps,listDibbsAwards,listExclusions,listSbirTopics,listSbirSolicitations) leaked a caller-suppliedjoinerto the server as a bare query param and ignored it when unflatteningflat: trueresponses (always unflattening on the default.).joineris now threaded the same way aslistIdvs: sent only alongsideflat=true, and used as the unflatten separator. listBudgetAccounts: thefiscal_year_gte,fiscal_year_lte, andaccount_titleoptions were sent verbatim, which the API silently ignores. They are kept as legacy aliases and now remapped to the forms the API understands (fiscal_year__gte,fiscal_year__lte,account_title__icontains); an explicitly passed dunder param wins over its alias.- Docs: removed the stale
getIdvSummary/listIdvSummaryAwardssections fromREADME.mdanddocs/API_REFERENCE.md— those methods were removed from the SDK in 1.1.0. Documented the full new surface (DIBBS/exclusions/SBIR, budget accounts and their filter surface,getGsaElibraryContract, the newly typed filters,PaginatedResponsemeta diagnostics, structuredTangoValidationErrordetails), completed theShapeConfigpreset table indocs/SHAPES.md, and rewrote the maintainer half ofdocs/DEVELOPERS.mdaround the conformance gates and the cassette record/replay workflow (the old text still claimed the SDK had no cassette mechanism).
CI¶
package-lock.jsonis now committed, and the CI + publish workflows install withnpm ci --ignore-scriptsinstead ofnpm install— installs are reproducible from the lockfile instead of re-resolving dependency ranges on every run.- The test job's Node 20 leg runs the suite once with
--coverage(instead of a second full pass), the defaultnpx vitest runnow includes the integration suite replayed offline from the committed cassettes, and both setup-node steps cache the npm store off the committed lockfile. No coverage fail-under gate, matching tango-python. - The
conformancejob is now a hard gate that runs both conformance directions offline against the vendored contract on every PR — it no longer needsTANGO_API_REPO_ACCESS_TOKENand no longer skips silently without it. When the token is configured, both checks additionally run as hard gates against the fresh contract at makegov/tango HEAD, with a re-vendor warning when the vendored copy has drifted. - The
conformancejob also regeneratessrc/shapes/generatedOverlay.tsand fails on any diff, so a contract or curated-schema change that alters generator output can't land without a regenerated overlay.scripts/generate-shape-overlay.tshonors the sameTANGO_CONTRACT_PATH/--contractoverride as the two check scripts.
[1.1.0] - 2026-05-29¶
Changed (breaking)¶
- Removed
getIdvSummaryandlistIdvSummaryAwards. These called/api/idvs/{id}/summary/and/api/idvs/{id}/summary/awards/, which have never existed in the Tango API (no OpenAPI backing), so no consumer could have been using them successfully. UsegetIdv+listIdvAwardsinstead.
Fixed¶
Contractinterface: removed dead fields (id,award_id,recipient_name,award_amount,awarding_agency,funding_agency) and added the real API fields fromContractListSerializer(key,piid,obligated,total_contract_value,base_and_exercised_options_value,awarding_office,funding_office,naics_code,psc_code,set_aside,solicitation_identifier,parent_award,legislative_mandates,subawards_summary,place_of_performance). All fields optional (the endpoint is shape-on-demand). The deprecated fields remain declared (marked@deprecated) for one minor cycle and will be removed in2.0.0. New exported types:OrganizationOfficePayload,PlaceOfPerformance,SubawardsSummary,LegislativeMandates,ParentAwardReference,EntityBasic.listContracts: no longer sendspage=1to the cursor-only/api/contracts/endpoint. When no cursor is supplied, neitherpagenorcursoris sent and the API returns the first page by default. The stale code comment claiming page-based pagination support has been corrected.- Shape validation: registered the
ContractOrIDVCompetitionnested schema (alias ofCompetition) so nested selections likecompetition(extent_competed,number_of_offers_received)on contract / IDV shapes validate instead of raising a shape-validation error.
Added¶
- Budget accounts surface (tango v4.6.8):
listBudgetAccounts,getBudgetAccount,getBudgetAccountQuarters,getBudgetAccountRecipients. New exportedBudgetAccountinterface andListBudgetAccountsOptions. - Singleton detail GETs:
getContract,getContractSubawards,getContractTransactions,getForecast,getGrant,getNotice,getOpportunity,getSubaward. getEntityBudgetFlows(uei)for/api/entities/{uei}/budget-flows/.listVehicleOrders(uuid, options)for/api/vehicles/{uuid}/orders/(parity with Python).grantIdfilter onlistGrants(camelCase alias mapped to thegrant_idAPI param;grant_idalso accepted directly).
CI¶
- Added
ci.ymlPR + push-to-main gate (lint, typecheck, build, test on Node 18/20/22). The filter/shape conformance check is a separate job that skips cleanly until aTANGO_API_REPO_ACCESS_TOKENsecret for the private manifest repo is configured, at which point it becomes a hard gate. - Gave the webhooks real-HTTP-server round-trip tests an explicit 20s timeout (the default 5s was tight on the slower Node 18 CI runtime).
[1.0.0] - 2026-05-13¶
First stable release. tango-node is now at full feature parity with both the Tango API and tango-python for the surface that remains after the subject-based webhook removal (see "Removed" below). Every read method and every endpoint/alert/signing helper available on tango_python.TangoClient has an idiomatic camelCase counterpart on TangoClient, the SDK's docs are auto-published to docs.makegov.com/sdks/node/ via the composer pipeline (makegov/docs#15 / makegov/docs#16), and from 1.x on we'll only ship breaking changes on a major bump.
Changed (breaking)¶
createWebhookEndpoint({ name, ... })—nameis now required. Previously the SDK silently fell back to the callback URL's host whennamewas omitted, which masked the server'sunique(user, name)constraint until the second duplicate endpoint. Raising client-side gives a clearer error and matchestango-python1.0.0's behavior.createWebhookAlert({ filters, ... })—filtersis now validated as a non-empty plain object. Previously{}and arrays passed the check; the error message claimed "non-empty object" but didn't enforce it. Matches the server-side validation.
Fixed¶
tests/unit/client.iterate.test.ts— corrected the comment above the firstpageassertion; previous text claimed the first call should NOT carry a page, but the assertion (correctly) expectspage=1sincelistContractsdefaults to it.docs/DEVELOPERS.md—listContracts({ offset: 25 })example replaced with{ page: 2 }and "manual offset tracking" → "manual page/cursor tracking" (the method has never acceptedoffset).CHANGELOG.md— corrected "4 new unit test files" to "5"; the parenthesized list always contained 5 paths.- Lint cleanup ahead of the npm publish — 44 redundant
as AnyRecord/as <T>type-assertion casts dropped acrosssrc/(@typescript-eslint/no-unnecessary-type-assertionfires under the newer plugin minor that CI resolves).src/webhooks/receiver.ts: dropped the unusedAddressInfoimport, simplifiedDelivery.bodyJson: unknown | null→unknown(the latter already includesnull), and restructuredWebhookReceiver.run()to avoidconst receiver = this(replaced with arrow-function closures overgetUrl/getDeliveries/stop). No behavior change — tests still pass 220/220. eslint.config.js— disabled the coreno-undefrule for TS files. TypeScript itself does undefined-symbol checking with knowledge of TS-only types (AsyncDisposable,Disposable, etc.); the core rule double-fires and false-flags those. Matches the typescript-eslint upstream guidance.
Internal¶
- Pinned
devDependenciesto exact versions (dropped^frompackage.json). The previous unpinned ranges + gitignoredpackage-lock.jsonmeant CI re-resolved deps on every run and could pick up minor versions with stricter behavior that local installs hadn't seen yet — that's how the lint mismatch above slipped past local checks.
Docs¶
- README updated for the docs-review sweep:
- Added
TangoTimeoutErrorto the documented error class list (it has been exported fromsrc/errors.tssince v0.4 but the README omitted it). - Replaced the "(Coming Soon!)" marker on the docs link with the live
https://docs.makegov.com/sdks/node/URL. - Rewrote the "Comprehensive API Coverage" feature bullet — the old enumeration listed fewer than half of the actually-implemented domains. New bullet points at the canonical "API Methods" section for the full surface.
- Added
- New
docs/CLIENT.md—TangoClientconstructor reference, environment variables, full retry/backoff semantics (includingRetry-Afterhandling), error-handling patterns,fetchImplinjection, and staging/local targeting. Ported fromdocs.makegov.com/sdks/node/client.mdahead of the docs-site auto-pull cutover (makegov/docs#15 / makegov/docs#16). docs/API_REFERENCE.mdenriched with notes from the docs-sitemethods.mdthat hadn't been folded in yet:listContractspage/cursor mutual exclusion,getIdvSummary/listIdvSummaryAwardsdeprecation (server returns 404),listIdvLcatsclarification,listOrganizationslevelsemantics,createWebhookEndpointsnake_case canonical vs camelCase legacy aliases (nameis required either way per the 1.0.0 change above),testWebhookEndpointpost-#2252 cleanup ({ endpoint: <id> }is canonical), andcreateWebhookAlertfield-rename notes (namevssubscription_name,filtersvsfilter_definition, singularquery_type, update-writable field list).
CI¶
- New
.github/workflows/docs-dispatch.yml— fires on push tomainwhendocs/**,README.md, orCHANGELOG.mdchanges and dispatchesexternal_updatedatmakegov/docsso the public docs site rebuilds with the latest SDK content. Required for the makegov/docs#15 auto-pull pipeline.
Parity closure — all previously-tracked gaps addressed¶
Every gap surfaced in the May 2026 parity audit is now closed:
Typed filter Options interfaces¶
listForecasts,listOpportunities,listNotices,listGrants— previouslyListOptionsBase & Record<string, unknown>with zero typed filters; now ship fullOptionsinterfaces (ListForecastsOptions,ListOpportunitiesOptions,ListNoticesOptions,ListGrantsOptions) enumerating every filter kwarg from the Python signatures.ListNoticesOptionsdeliberately omitsordering(server rejects it).ListContractsOptionsexpanded to enumerate all 27+ Python kwargs (award_date*,awarding_agency,funding_agency,obligated_gte/lte,pop_*,expiring_*,keyword/recipient_name/recipient_uei/set_aside_type/naics_code/psc_codealiases,sort+order→ordering, etc.).ListEntitiesOptionsexpanded with the 12 Python kwargs (cage_code,naics,name,psc,purpose_of_registration_code,socioeconomic,state,total_awards_obligated_gte/lte,uei,zip_code).ListVehiclesOptionsexpanded with all 21 filter kwargs (vehicle_type,type_of_idc,contract_type,who_can_use,total_obligated_min/max, etc.).ListIdvsOptionsexpanded with the full IDV filter surface.ListOtasOptions/ListOtidvsOptionsexpanded with the missing_gte/_lteranges (award_date_gte/lte,fiscal_year_gte/lte,expiring_gte/lte,pop_start_date_gte/lte,pop_end_date_gte/lte).ListAgenciesOptionsadded (was inline{ page?, limit? }).
All Options interfaces keep a [key: string]: unknown index signature for forward-compatibility with new server-side filters that haven't been ported yet — typed fields give autocomplete and typo-protection on known filters; unknown fields still pass through.
ShapeConfig preset parity¶
Added: PROTESTS_MINIMAL, OTAS_MINIMAL, OTIDVS_MINIMAL, SUBAWARDS_MINIMAL, GSA_ELIBRARY_CONTRACTS_MINIMAL, ORGANIZATIONS_MINIMAL, VEHICLE_ORDERS_MINIMAL, ITDASHBOARD_INVESTMENTS_MINIMAL, ITDASHBOARD_INVESTMENTS_COMPREHENSIVE.
Updated to match Python field lists: ENTITIES_COMPREHENSIVE (now uses federal_obligations(*) expansion), VEHICLES_MINIMAL (extended to mirror Python's larger field list), VEHICLES_COMPREHENSIVE (dropped competition_details(*) per SDK v0.6.0; added program_acronym, is_synthetic_solicitation, metrics(*), organization expansion).
Explicit schemas¶
Added 11 schemas + registry entries: ORGANIZATION_OFFICE_SCHEMA, VEHICLE_METRICS_SCHEMA, ORGANIZATION_SCHEMA, OTA_SCHEMA, OTIDV_SCHEMA, SUBAWARD_SCHEMA, PROTEST_DOCKET_SCHEMA, PROTEST_SCHEMA, GSA_ELIBRARY_IDV_REF_SCHEMA, GSA_ELIBRARY_CONTRACT_SCHEMA, ITDASHBOARD_INVESTMENT_SCHEMA. Wired into the EXPLICIT_SCHEMAS registry under canonical model names.
Also extended VEHICLE_SCHEMA with the fields the new VEHICLES_* presets reference: is_synthetic_solicitation, program_acronym, organization (expandable to OrganizationOffice), metrics (expandable to VehicleMetrics), idv_count, total_obligated, latest_award_date, opportunity_id, description.
Typed return models¶
client.resolve(input)→Promise<ResolveResult>with typedResolveCandidate[](wasPromise<{ candidates: AnyRecord[]; count: number }>).client.validate(input)→Promise<ValidateResult>(wasPromise<AnyRecord>).client.getAgency(code)→Promise<AgencyRecord>(wasPromise<AnyRecord>).client.getProtest(caseNumber)→Promise<ProtestRecord>with typeddocket,resolved_*, etc.
All new types exported from package root.
Observability — rateLimitInfo + lastResponseHeaders¶
Two new instance properties on TangoClient mirroring Python's rate_limit_info / last_response_headers:
await client.listContracts({ limit: 5 });
console.log(client.rateLimitInfo);
// { remaining: 98, limit: 100, resetIn: 60, retryAfter: null, limitType: "per_minute" }
console.log(client.lastResponseHeaders?.["x-request-id"]);
Both null until the first request completes; populated after every successful request. HttpClient parses X-RateLimit-{Remaining,Limit,Reset,Type} + Retry-After headers into a RateLimitInfo snapshot.
listContracts cursor pagination (non-breaking)¶
ListContractsOptions now accepts cursor alongside page. When cursor is supplied, the request omits page and uses keyset pagination (Python parity); otherwise falls back to page-based. PaginatedResponse gained a cursor field auto-extracted from next so callers can client.listContracts({ cursor: resp.cursor }) for the next page without parsing the URL themselves.
WebhookReceiver framework¶
New src/webhooks/receiver.ts. WebhookReceiver dataclass-style class with start(), stop(), url, deliveries, onDelivery callback, optional forwardTo mirror, history cap, signature verification (via existing verifySignature). Two run patterns shipped:
await using rx = await new WebhookReceiver(opts).run();(modern,Symbol.asyncDispose).await WebhookReceiver.withRunning(opts, async (rx) => { ... });(portable callback scope).
Pure node:http + native fetch — no new dependencies.
Webhook simulator¶
New src/webhooks/simulate.ts. sign(payload, secret) returns a SignedRequest (body bytes, signature header value, content-type). deliver({ targetUrl, payload, secret, ... }) signs and POSTs via native fetch with AbortSignal.timeout(). Includes a stableStringify helper that matches Python's json.dumps(sort_keys=True, separators=(",", ":")) byte-for-byte so signatures are reproducible across languages.
Webhook CLI — tango-node webhooks¶
New bin/tango-node (entry in src/bin/tango-node.ts) backed by commander. Subcommands: listen, simulate, trigger, fetch-sample, list-event-types, endpoints {list, get, create, delete} — mirroring the Python tango webhooks CLI. New runtime dep: commander@^12.1.0.
Conformance gate¶
New scripts/check-filter-shape-conformance.ts + npm run check-conformance script. Walks src/client.ts with the TypeScript compiler AST, extracts each list* method's Options interface, and validates against the canonical manifest at tango/contracts/filter_shape_contract.json. JSON output + exit codes match the Python conformance script. Current state: 0 errors, 0 warnings — parity verified.
Test coverage¶
Net +74 tests across the new work (220 total now passing, up from 146 pre-audit):
- ShapeConfig preset parity (
config.shapes.parity) - Explicit schema parity (
shapes.schema.parity, 12 tests) - WebhookReceiver lifecycle, signature paths, forwarding, history cap,
withRunning+Symbol.asyncDispose(webhooks/receiver, 21 tests) - Webhook simulator stable-stringify, sign-verify round-trip, deliver against a real
http.createServer, timeout (webhooks/simulate, 17 tests) - CLI subcommand wiring (
webhooks/cli, 7 tests) - Conformance script (
scripts/conformance, 5 tests) - Observability + cursor pagination + typed return models (
client.observability, 11 tests)
Internal¶
tsxadded as a devDependency to run the new conformance script.HttpClientconstructor body unchanged; just adds two readonly fields (lastResponseHeaders,rateLimitInfo) populated on every successful response.
Removed¶
- Subject-based webhook subscriptions are gone. The Tango API is dropping the
/api/webhooks/subscriptions/surface for subject delivery (see makegov/tango#2267);tango-nodemirrors that here. Removed methods:listWebhookSubscriptions,getWebhookSubscription,createWebhookSubscription,updateWebhookSubscription,deleteWebhookSubscription. Removed types:WebhookSubscription,WebhookSubscriptionCreateInput,WebhookSubscriptionUpdateInput,WebhookSubscriptionPayload,WebhookSubscriptionPayloadRecord,WebhookSubjectTypeDefinition,WebhookSampleSubject,ListWebhookSubscriptionsOptions.WebhookEventTypesResponseno longer carriessubject_types/subject_type_definitions;WebhookEventTypeno longer carriesdefault_subject_type; sample-payload responses no longer carrysample_subjects/sample_subscription_requests. UsecreateWebhookAlert(filter-based delivery via/api/webhooks/alerts/) — that's the only remaining subscription path.
SemVer-major (0.3.0 → 0.4.0).
Added¶
API parity — read methods¶
- Lookups:
listNaics,getNaics,listPsc,getPsc,listMasSins,getMasSin,listAssistanceListings,getAssistanceListing,listOrganizations,getOrganization,listOffices,getOffice,listDepartments(@deprecatedJSDoc),getDepartment,getBusinessType. - Awards completeness:
listOtas,getOta,listOtidvs,getOtidv,listOtidvAwards,listSubawards,listGsaElibraryContracts,listLcats(accepts{ uei }or{ idvKey }). - Other resources:
listProtests,getProtest,listItDashboard,getItDashboard,listMetrics(parameterized overownerTypesince the API exposes metrics only under owner-scoped paths). - Utility endpoints:
resolve(input)(POST/api/resolve/— returns{ candidates, count }),validate(input)(POST/api/validate/).
API parity — typed wrappers for Python's get_*_metrics helpers¶
getEntityMetrics(uei, months, periodGrouping)getNaicsMetrics(code, months, periodGrouping)getPscMetrics(code, months, periodGrouping)
API parity — entity, IDV, and agency sub-resources¶
listEntityContracts,listEntityIdvs,listEntityOtas,listEntityOtidvs,listEntitySubawards,listEntityLcatslistIdvLcats(key, options?)— typed sibling of the genericlistLcats({ idvKey })listAgencyAwardingContracts,listAgencyFundingContracts
Webhook write API¶
- Endpoints:
createWebhookEndpoint(nownameis first-class; defaults to URL host if omitted),updateWebhookEndpoint,deleteWebhookEndpoint.testWebhookEndpoint(endpointId)is the canonical method;testWebhookDeliveryis kept as an auto-resolving variant (omitendpointIdto let the API pick the sole endpoint). - Alerts (filter-subscription API):
listWebhookAlerts,getWebhookAlert,createWebhookAlert,updateWebhookAlert,deleteWebhookAlert.WebhookAlertCreateInputnow has an optionalendpointfield — required for multi-endpoint accounts, optional for single-endpoint accounts (the API auto-resolves). Server support landed in makegov/tango#2256.
New typed input interfaces exported from the package root: WebhookEndpointCreateInput, WebhookEndpointUpdateInput, WebhookAlertCreateInput, WebhookAlert, plus options types for the new sub-resources.
Webhook signature helpers (parity with tango_python.webhooks.signing)¶
verifySignature(body, header, secret)— constant-time HMAC-SHA256 verification. Accepts"sha256=<hex>"and bare-hex forms. Returnsboolean, never throws.generateSignature(body, secret)— emits"sha256=<hex>"matching the dispatcher format.parseSignatureHeader(header)— returns{ algorithm, signature } | nullfor cleaner branching in receivers.
All exported from the package root; receivers don't need to instantiate TangoClient.
Async iterator pagination¶
For convenience, list methods now have async-iterator wrappers that handle next / cursor for you:
for await (const contract of client.iterateContracts({ awarding_agency: "9700" })) {
console.log(contract.piid, contract.total_contract_value);
}
Typed iterators: iterateContracts, iterateEntities, iterateOpportunities, iterateNotices, iterateGrants, iterateForecasts, iterateIdvs, iterateVehicles. Iteration is sequential (no concurrent requests) to respect API rate limits.
Retry with exponential backoff¶
HttpClient now automatically retries failed requests:
- Retries on 5xx, 408 (Request Timeout), 429 (Too Many Requests), network errors, and client-side timeouts.
- Does not retry on other 4xx — those surface as the appropriate
Tango*error immediately. - Exponential backoff: base
retryBackoffMs(default 250ms), doubled per attempt, capped at 10s. - Honors
Retry-Afterheaders (delta-seconds and HTTP-date) on 429/503.
Constructor surface¶
retries(default3) andretryBackoffMs(default250) options onTangoClientOptions. Setretries: 0to disable.timeoutaccepted as a shorthand alias fortimeoutMs(both in ms;timeoutMswins if both are supplied).
Environment variable fallback¶
TANGO_BASE_URLenv var is now read whenbaseUrlis not passed to the constructor — parity withTANGO_API_KEY.
Misc¶
searchOpportunityAttachments,getVersion,listApiKeysround out parity with the Python SDK's introspection / search surface.- Shape generator now accepts
naics(code,description)/psc(code,description)as canonical expands on Contract, Opportunity, Notice, Forecast, and Vehicle (IDV already had them). Mirrorsmakegov/tango#2259. (refsmakegov/tango#2265)
Changed¶
createWebhookEndpointand related write methods accept the canonical Tango API payload shape in addition to the previous camelCase wrappers — see the new typed input interfaces.testWebhookEndpoint/testWebhookDeliverynow send the canonical{ endpoint }body key instead of the deprecated{ endpoint_id }(server still accepts both as aliases). Tracks makegov/tango#2252.ListSubawardsOptions.orderingnarrowed fromstringto the literal union"last_modified_date" | "-last_modified_date", matching the server-side enum (no other values are accepted; others 400). Tracks makegov/tango#2254.- Shape generator rewrites legacy
naics_code(...)/psc_code(...)expand spellings to canonicalnaics(...)/psc(...)before validation, matching the server's_EXPAND_ALIASESmap. Scalarnaics_code/psc_code(no parens) is untouched and still returns the raw column value. (refsmakegov/tango#2265,makegov/tango#2259)
Fixed¶
ShapeConfig.IDVS_COMPREHENSIVEno longer includesbase_and_exercised_options_value, which is not a valid IDV shape field — the API was returning400 Invalid shapeon this preset. Now aligned withtango_python.IDVS_COMPREHENSIVE. Also reconciledrecipient.cage_code→recipient.cageto match the Python preset exactly.createWebhookAlertnow plumbs an explicitendpointUUID through to the API. Multi-endpoint accounts can now create alerts directly instead of relying on the server's single-endpoint auto-resolution. Tracks makegov/tango#2256.Subawardschema matches the server'sSubawardSerializer. The previousSUBAWARD_SCHEMA(ported from the broken Python schema) declared two fields the server has never exposed (id,amount) and was missing every real field — includingpiid,key,awarding_office/funding_office/place_of_performance/subaward_details/fsrs_details/highly_compensated_officers/usaspending_permalink, and the denormalizedprime_awardee_*/recipient_*lookup columns. Shape strings that referenced any real field (e.g.shape: "piid") would fail client-side validation withunknown_field.SUBAWARD_SCHEMAis now derived directly fromawards.serializers.subawards.SubawardSerializerand the resource's runtimeavailable_fields. New nested schemasSubawardDetails,FsrsDetails,SubawardPlaceOfPerformance, andHighlyCompensatedOfficerare registered so the corresponding shape expansions validate end-to-end.
Internal¶
- Live smoke harnesses at
scripts/smoke-{reads,writes,extras,parity}.tsexercise every new method against a running Tango instance. All four requireTANGO_API_KEYin the environment (hard-fail if unset — no fallback). - 5 new unit test files (
tests/unit/{client.parity,client.iterate,client.baseurl,webhooks.signing,config.shapes}.test.ts) added; total suite is now 16 files / 111 tests / 82% line coverage. - ESM build (
tsc -p tsconfig.json) clean.
[0.3.0] - 2026-02-09¶
Added¶
- Vehicles endpoints:
listVehicles,getVehicle, andlistVehicleAwardees(supports shaping + flattening). (refsmakegov/tango#1327) - IDV endpoints:
listIdvs,getIdv,listIdvAwards,listIdvChildIdvs,listIdvTransactions,getIdvSummary,listIdvSummaryAwards. (refsmakegov/tango#1327) - Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs
makegov/tango#1275)
Changed¶
- HTTP client now supports PATCH/PUT/DELETE for non-GET endpoints.
joineris now respected when unflatteningflat=trueresponses on supported endpoints.
[0.1.0] - 2025-11-21¶
- Initial Node.js port of the Tango Python SDK.
- Basic project scaffolding for client, models, and shapes.
- ESM + TypeScript build configuration.
[0.1.4] - 2025-11-21¶
- Added tests and cleaned up formatting and structure of SDK.