Tango Node.js SDK¶
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.
Features¶
- 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, 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 18+ with native
fetchand ESM-first design. - Tested Against the Real API – Integration tests (mirroring the Python SDK) keep behavior aligned.
Installation¶
Requirements: Node.js 18 or higher.
npm install @makegov/tango-node
# or
yarn add @makegov/tango-node
# or
pnpm add @makegov/tango-node
Quick Start¶
Initialize the client¶
import { TangoClient } from "@makegov/tango-node";
const client = new TangoClient({
apiKey: process.env.TANGO_API_KEY,
// baseUrl: "https://tango.makegov.com", // default
});
List agencies¶
const agencies = await client.listAgencies();
for (const agency of agencies.results) {
console.log(agency.code, agency.name);
}
Get a specific agency¶
const treasury = await client.getAgency("2000"); // Treasury
console.log(treasury.name, treasury.department?.name);
Search contracts with a minimal shape¶
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);
}
Get a fully-shaped entity¶
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);
Authentication¶
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.
With API key¶
import { TangoClient } from "@makegov/tango-node";
const client = new TangoClient({
apiKey: "your-api-key-here",
});
From environment variable (TANGO_API_KEY)¶
import { TangoClient } from "@makegov/tango-node";
const client = new TangoClient();
// If apiKey is omitted, the client will look for process.env.TANGO_API_KEY
Core Concepts¶
Dynamic Response Shaping¶
Response 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.
Flat vs nested responses¶
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.
API Methods¶
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)/listIdvs(options)/getIdv(key, options)listIdvAwards(key, options)/listIdvChildIdvs({key, ...options})/listIdvTransactions(key, options)getIdvSummary(identifier)/listIdvSummaryAwards(identifier, options)listOtas(options)/getOta(key)/listOtidvs(options)/getOtidv(key)/listOtidvAwards(key, options)listSubawards(options)
Vehicles
listVehicles(options)/getVehicle(uuid, options)/listVehicleAwardees(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)
Forecasts / Opportunities / Notices / Grants
listForecasts(options)/listOpportunities(options)/listNotices(options)/listGrants(options)searchOpportunityAttachments(options)
GSA eLibrary / Protests / IT Dashboard / Subawards / LCATs
listGsaElibraryContracts(options)/listProtests(options)/getProtest(caseNumber)listItDashboard(options)/getItDashboard(uii)listLcats(options)/listIdvLcats(key, 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/iterateVehicles
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;
results: T[];
}
Error Handling¶
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).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);
}
}
Project Structure¶
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
│ │ ├── 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
├── docs/ # Documentation
│ ├── API_REFERENCE.md
│ ├── DYNAMIC_MODELS.md
│ └── SHAPES.md
├── tests/ # Test suite (Vitest)
│ └── unit/
│ ├── client.test.ts
│ ├── errors.test.ts
│ ├── shapes.factory.test.ts
│ ├── shapes.generator.test.ts
│ ├── shapes.parser.test.ts
│ ├── shapes.schema.test.ts
│ ├── utils.dates.test.ts
│ ├── utils.http.test.ts
│ ├── utils.number.test.ts
│ └── utils.unflatten.test.ts
├── 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
Development¶
After cloning the repo:
npm install
npm run build
npm test
Useful scripts:
npm run build– Compile TypeScript todist/.npm test– Run unit and integration tests.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.
Requirements¶
- Node.js 18 or higher.
- A valid Tango API key.
Documentation¶
- API Reference - Detailed API documentation
- Shape System Guide - Comprehensive guide to response shaping
- Dynamic Models Guide - How the dynamic shaping system works.
License¶
MIT License - see LICENSE for details.
Support¶
For questions, issues, or feature requests:
- Email: [email protected]
- Issues: GitHub Issues
- Documentation: https://docs.makegov.com/sdks/node/
Contributing¶
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