Skip to content

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

License

Notifications You must be signed in to change notification settings

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, 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 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

No packages published