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/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.
- Comprehensive API Coverage – Agencies, business types, entities, contracts, forecasts, opportunities, notices, and grants.
- Flexible Data Access – Plain JavaScript objects backed by runtime validation and parsing, materialized via the dynamic model pipeline.
- Modern Node – Built for Node 18+ with native
fetchand ESM-first design. - Tested Against the Real API – Integration tests (mirroring the Python SDK) keep behavior aligned.
Requirements: Node 18 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 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 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 SDK mirrors the Python client's behavior for shape, flat, and flat_lists.
The Node client mirrors the Python SDK's high-level API:
listAgencies(options)getAgency(code)listBusinessTypes(options)listContracts(options)listEntities(options)getEntity(ueiOrCage, options)listForecasts(options)listOpportunities(options)listNotices(options)listGrants(options)
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[];
}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).
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
│ │ ├── 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
│ └── SHAPED.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
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.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.
- Node 18 or higher.
- A valid Tango API key.
- API Reference - Detailed API documentation
- Shape System Guide - Comprehensive guide to response shaping
- Dynamic Models Guide - ynamic shaping system** works.
MIT License - see LICENSE for details.
For questions, issues, or feature requests:
- Email: tango@makegov.com
- Issues: GitHub Issues
- Documentation: https://tango.makegov.com/docs/tango-node (Coming Soon!)
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