A modern Node.js SDK for the Tango API, featuring dynamic response shaping, strong TypeScript types, and full coverage of the core Tango endpoints.
This is the Node.js/TypeScript port of the official Tango Python SDK.
- Dynamic Response Shaping – Ask Tango for exactly the fields you want using a simple shape syntax.
- Type-Safe by Design – Shape strings are validated against Tango schemas and mapped to generated TypeScript types.
- Full Tango API surface – Awards (contracts, IDVs, OTAs, OTIDVs, subawards, vehicles, GSA eLibrary), opportunities + notices, forecasts, grants, protests, IT Dashboard, budget accounts, exclusions, SBIR/STTR, DIBBS, entities (with sub-resources), agencies/organizations/offices/departments, lookups (NAICS, PSC, MAS SINs, assistance listings, business types), metrics, resolve/validate, webhooks. See
## API Methodsbelow for the full list. - Flexible Data Access – Plain JavaScript objects backed by runtime validation and parsing, materialized via the dynamic model pipeline.
- Modern Node.js – Built for Node.js 20+ with native
fetchand ESM-first design. - Tested Against the Real API – Integration tests (mirroring the Python SDK) keep behavior aligned.
Requirements: Node.js 20 or higher.
npm install @makegov/tango-node
# or
yarn add @makegov/tango-node
# or
pnpm add @makegov/tango-nodeimport { TangoClient } from "@makegov/tango-node";
const client = new TangoClient({
apiKey: process.env.TANGO_API_KEY,
// baseUrl: "https://tango.makegov.com", // default
});const agencies = await client.listAgencies();
for (const agency of agencies.results) {
console.log(agency.code, agency.name);
}const treasury = await client.getAgency("2000"); // Treasury
console.log(treasury.name, treasury.department?.name);import { TangoClient, ShapeConfig } from "@makegov/tango-node";
const client = new TangoClient({ apiKey: process.env.TANGO_API_KEY });
const contracts = await client.listContracts({
shape: ShapeConfig.CONTRACTS_MINIMAL,
keyword: "cloud services",
awarding_agency: "4700",
fiscal_year: 2024,
limit: 10,
});
// Each contract is shaped according to CONTRACTS_MINIMAL
for (const c of contracts.results) {
console.log(c.piid, c.award_date, c.recipient.display_name);
}import { TangoClient, ShapeConfig } from "@makegov/tango-node";
const client = new TangoClient({ apiKey: process.env.TANGO_API_KEY });
const entity = await client.getEntity("ABC123DEF456", {
shape: ShapeConfig.ENTITIES_COMPREHENSIVE,
});
console.log(entity.uei, entity.legal_business_name, entity.primary_naics);The Node.js SDK uses the same model as the Python one: you can either pass the API key directly or read it from TANGO_API_KEY.
import { TangoClient } from "@makegov/tango-node";
const client = new TangoClient({
apiKey: "your-api-key-here",
});import { TangoClient } from "@makegov/tango-node";
const client = new TangoClient();
// If apiKey is omitted, the client will look for process.env.TANGO_API_KEYResponse shaping is the core feature of Tango. Instead of always receiving huge objects with every field, you describe the fields you want with a compact shape string:
const contracts = await client.listContracts({
shape: "key,piid,award_date,recipient(display_name),total_contract_value",
keyword: "software",
limit: 5,
});Shapes:
- Reduce payload size (often massively).
- Keep responses focused on what your app actually uses.
- Drive type safety – the SDK maps the shape to a TypeScript type.
The Node.js SDK includes:
- A shape parser that validates shape strings.
- A schema registry that knows what fields exist on each resource.
- A type generator and model factory that convert raw API JSON into strongly-typed objects.
By default, nested fields are returned as nested objects:
// shape:
"key,piid,recipient(display_name,uei)";
//
contract.recipient.display_name;
contract.recipient.uei;You can request a "flat" representation that uses dotted keys and then unflattens into nested objects on the client:
const contracts = await client.listContracts({
shape: ShapeConfig.CONTRACTS_MINIMAL,
flat: true,
});The Node.js SDK mirrors the Python client's behavior for shape, flat, and flat_lists.
The Node.js client mirrors the Python SDK's high-level API. Selected highlights:
Agencies / Offices / Organizations / Departments
listAgencies(options)/getAgency(code)listOffices(options)/getOffice(code)listOrganizations(options)/getOrganization(identifier)listDepartments(options)/getDepartment(code)
Contracts / IDVs / OTAs / OTIDVs / Subawards
listContracts(options)/getContract(key, options)/getContractSubawards(key, options)/getContractTransactions(key, options)listIdvs(options)/getIdv(key, options)listIdvAwards(key, options)/listIdvChildIdvs({key, ...options})/listIdvTransactions(key, options)listOtas(options)/getOta(key)/listOtidvs(options)/getOtidv(key)/listOtidvAwards(key, options)listSubawards(options)/getSubaward(key)
Vehicles
listVehicles(options)/getVehicle(uuid, options)/listVehicleAwardees(uuid, options)/listVehicleOrders(uuid, options)
Entities
listEntities(options)/getEntity(ueiOrCage, options)listEntityContracts(uei, options)/listEntityIdvs(uei, options)/listEntityOtas(uei, options)listEntityOtidvs(uei, options)/listEntitySubawards(uei, options)/listEntityLcats(uei, options)getEntityMetrics(uei, months, periodGrouping)/getEntityBudgetFlows(uei)
Forecasts / Opportunities / Notices / Grants
listForecasts(options)/listOpportunities(options)/listNotices(options)/listGrants(options)getForecast(id, options)/getOpportunity(opportunityId, options)/getNotice(noticeId, options)/getGrant(grantId, options)searchOpportunityAttachments(options)
GSA eLibrary / Protests / IT Dashboard / LCATs
listGsaElibraryContracts(options)/getGsaElibraryContract(uuid, options)listProtests(options)/getProtest(caseNumber)listItDashboard(options)/getItDashboard(uii)listLcats(options)/listIdvLcats(key, options)
Budget Accounts
listBudgetAccounts(options)/getBudgetAccount(id, options)getBudgetAccountQuarters(id, options)/getBudgetAccountRecipients(id, options)
Exclusions / SBIR / DIBBS
listExclusions(options)/getExclusion(exclusionKey, options)listSbirTopics(options)/getSbirTopic(topicId, options)listSbirSolicitations(options)/getSbirSolicitation(solicitationId, options)listDibbsRfqs(options)/getDibbsRfq(uuid, options)listDibbsRfps(options)/getDibbsRfp(uuid, options)listDibbsAwards(options)/getDibbsAward(uuid, options)
Reference / Lookups
listBusinessTypes(options)/getBusinessType(code)listNaics(options)/getNaics(code)/getNaicsMetrics(code, months, periodGrouping)listPsc(options)/getPsc(code)/getPscMetrics(code, months, periodGrouping)listMasSins(options)/getMasSin(sin)listAssistanceListings(options)/getAssistanceListing(number)listMetrics(options)/listAgencyAwardingContracts(code, options)/listAgencyFundingContracts(code, options)
Resolve / Validate
resolve(input)— resolve a free-text name to ranked entity/org candidatesvalidate(input)— validate a PIID, solicitation number, or UEI
Webhooks
listWebhookEventTypes()listWebhookEndpoints(options)/getWebhookEndpoint(id)createWebhookEndpoint(...)/updateWebhookEndpoint(id, patch)/deleteWebhookEndpoint(id)testWebhookEndpoint(endpointId)(preferred) /testWebhookDelivery(options?)(legacy alias)getWebhookSamplePayload(options?)listWebhookAlerts(options)/getWebhookAlert(id)/createWebhookAlert(input)updateWebhookAlert(id, patch)/deleteWebhookAlert(id)
Async iteration helpers
iterate(method, options)— generic async iterator over any supported list methoditerateContracts/iterateEntities/iterateOpportunities/iterateNoticesiterateGrants/iterateForecasts/iterateIdvs/iterateVehiclesiterateDibbsRfqs/iterateDibbsRfps/iterateDibbsAwardsiterateExclusions/iterateSbirTopics/iterateSbirSolicitations
Utility
getVersion()/listApiKeys()
See docs/API_REFERENCE.md for full signatures and parameters.
All list methods return a paginated response:
interface PaginatedResponse<T> {
count: number;
next: string | null;
previous: string | null;
pageMetadata: Record<string, unknown> | null;
meta: Record<string, unknown> | null;
agencyWarnings: string[];
unresolvedAgencyTokens: Record<string, string[]>;
resolvedAgencies: Record<string, Array<Record<string, unknown>>>;
cursor: string | null;
results: T[];
}meta surfaces response-level metadata from the API, and the three agency* fields are parsed views of its agency-filter diagnostics — see API Reference § Pagination for how to use them to catch silently-narrowed agency filters.
cursor is extracted from next on keyset-paginated endpoints so you can pass it straight back as the next request's cursor option.
Errors are surfaced as typed exceptions, aligned with the Python SDK:
TangoAPIError– Base error for unexpected issues.TangoAuthError– Authentication problems (e.g., invalid API key, 401).TangoNotFoundError– Resource not found (404).TangoValidationError– Invalid request parameters (400). Exposes the API's structured 400 payload viaissuesandavailableFields(see the API Reference).TangoRateLimitError– Rate limit exceeded (429).TangoTimeoutError– Request exceeded the configuredtimeoutMs.
Shape-related errors:
ShapeErrorShapeValidationErrorShapeParseErrorTypeGenerationErrorModelInstantiationError
Use them in your code:
import { TangoClient, TangoAPIError, TangoValidationError } from "@makegov/tango-node";
try {
const resp = await client.listContracts({ keyword: "cloud", limit: 5 });
} catch (err) {
if (err instanceof TangoValidationError) {
console.error("Bad request:", err.message);
} else if (err instanceof TangoAPIError) {
console.error("Tango API error:", err.message);
} else {
console.error("Unexpected error:", err);
}
}tango-node/
├── src/ # Source TypeScript
│ ├── client.ts # TangoClient implementation
│ ├── config.ts # Default base URL + shape presets
│ ├── errors.ts # Error classes (API, auth, validation, etc.)
│ ├── index.ts # Public API exports
│ ├── types.ts # Shared types (options, PaginatedResponse)
│ ├── models/ # Lightweight model interfaces (Contract, Entity, etc.)
│ ├── shapes/ # Shape system (parser, generator, factory)
│ │ ├── explicitSchemas.ts # Predefined schemas for resources
│ │ ├── generatedOverlay.ts # Machine-generated schema overlay (see scripts/)
│ │ ├── factory.ts # Instantiate typed models from data
│ │ ├── generator.ts # Type generation from shape specs
│ │ ├── index.ts # Shapes exports
│ │ ├── parser.ts # Shape string parser
│ │ ├── schema.ts # Schema registry + validation
│ │ ├── schemaTypes.ts # Schema data structures
│ │ └── types.ts # Shape spec types
│ ├── utils/ # Helpers
│ │ ├── dates.ts # Date/time parsing utilities
│ │ ├── http.ts # HTTP client wrapper
│ │ ├── number.ts # Numeric parsing/formatting
│ │ └── unflatten.ts # Unflatten dotted-key responses
│ └── webhooks/ # Signing, receiver, simulator, CLI
├── contracts/ # Vendored API contract + conformance baselines
├── scripts/ # Conformance gates, overlay generator, live smoke scripts
├── docs/ # Documentation
│ ├── API_REFERENCE.md
│ ├── CLIENT.md
│ ├── DEVELOPERS.md
│ ├── DYNAMIC_MODELS.md
│ ├── SHAPES.md
│ └── WEBHOOKS.md
├── tests/ # Test suite (Vitest)
│ ├── unit/ # Offline unit tests (fetchImpl mocks)
│ ├── integration/ # Cassette-replayed integration tests
│ ├── cassettes/ # Recorded API interactions (JSON)
│ ├── production/ # Env-gated live smoke suite (TANGO_LIVE_TESTS)
│ ├── scripts/ # Tests for the conformance gates
│ └── webhooks/ # Receiver / simulator / CLI tests
├── dist/ # Build output (compiled JS + d.ts) from `npm run build`
├── eslint.config.js # ESLint flat config
├── .prettierrc # Prettier config
├── package.json # Package metadata/scripts
├── tsconfig.json # TypeScript config
├── README.md # Usage docs
├── CHANGELOG.md # Version history
└── LICENSE # MIT license
After cloning the repo:
npm install
npm run build
npm testUseful scripts:
npm run build– Compile TypeScript todist/.npm test– Run unit and integration tests (integration replays committed cassettes offline).npm run coverage– Get test coverage report.npm run lint– Run ESLint.npm run format– Run Prettier.npm run typecheck– TS type checking without emit.npm run check-conformance– SDK filters/shapes vs the vendored API contract.npm run check-shape-coverage– Reverse gate: every contract shape field is captured by the SDK.npm run generate-shape-overlay– Regeneratesrc/shapes/generatedOverlay.tsfrom the vendored contract.
See docs/DEVELOPERS.md for the conformance architecture and the cassette record/replay workflow.
- Node.js 20 or higher.
- A valid Tango API key.
- API Reference - Detailed API documentation
- Shape System Guide - Comprehensive guide to response shaping
- Dynamic Models Guide - How the dynamic shaping system works.
MIT License - see LICENSE for details.
For questions, issues, or feature requests:
- Email: tango@makegov.com
- Issues: GitHub Issues
- Documentation: https://docs.makegov.com/sdks/node/
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
npm run test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request