Skip to content

makegov/tango-node

Repository files navigation

Tango Node 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/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.
  • Comprehensive API Coverage – Agencies, business types, entities, contracts, vehicles, IDVs, forecasts, opportunities, notices, grants, and webhooks.
  • 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 fetch and ESM-first design.
  • Tested Against the Real API – Integration tests (mirroring the Python SDK) keep behavior aligned.

Installation

Requirements: Node 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 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 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 SDK mirrors the Python client's behavior for shape, flat, and flat_lists.

API Methods

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[];
}

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).

Shape-related errors:

  • ShapeError
  • ShapeValidationError
  • ShapeParseError
  • TypeGenerationError
  • ModelInstantiationError

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
│   └── 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

Development

After cloning the repo:

npm install
npm run build
npm test

Useful scripts:

  • npm run build – Compile TypeScript to dist/.
  • 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

Documentation

License

MIT License - see LICENSE for details.

Support

For questions, issues, or feature requests:

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Run tests (npm run test)
  4. Commit your changes (git commit -m 'Add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request

About

A modern Node.js SDK for the Tango API, featuring dynamic response shaping, strong TypeScript types, and full coverage of the core Tango endpoints.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors