Skip to content

Repository files navigation

Tango Node.js SDK

npmNode.js VersionLicense: MIT

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, 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 Methods below 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 fetch and ESM-first design.
  • Tested Against the Real API – Integration tests (mirroring the Python SDK) keep behavior aligned.

Installation

Requirements: Node.js 20 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";constclient=newTangoClient({apiKey: process.env.TANGO_API_KEY,// baseUrl: "https://tango.makegov.com", // default});

List agencies

constagencies=awaitclient.listAgencies();for(constagencyofagencies.results){console.log(agency.code,agency.name);}

Get a specific agency

consttreasury=awaitclient.getAgency("2000");// Treasuryconsole.log(treasury.name,treasury.department?.name);

Search contracts with a minimal shape

import{TangoClient,ShapeConfig}from"@makegov/tango-node";constclient=newTangoClient({apiKey: process.env.TANGO_API_KEY});constcontracts=awaitclient.listContracts({shape: ShapeConfig.CONTRACTS_MINIMAL,keyword: "cloud services",awarding_agency: "4700",fiscal_year: 2024,limit: 10,});// Each contract is shaped according to CONTRACTS_MINIMALfor(constcofcontracts.results){console.log(c.piid,c.award_date,c.recipient.display_name);}

Get a fully-shaped entity

import{TangoClient,ShapeConfig}from"@makegov/tango-node";constclient=newTangoClient({apiKey: process.env.TANGO_API_KEY});constentity=awaitclient.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";constclient=newTangoClient({apiKey: "your-api-key-here",});

From environment variable (TANGO_API_KEY)

import{TangoClient}from"@makegov/tango-node";constclient=newTangoClient();// 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:

constcontracts=awaitclient.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:

constcontracts=awaitclient.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) / 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 candidates
  • validate(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 method
  • iterateContracts / iterateEntities / iterateOpportunities / iterateNotices
  • iterateGrants / iterateForecasts / iterateIdvs / iterateVehicles
  • iterateDibbsRfqs / iterateDibbsRfps / iterateDibbsAwards
  • iterateExclusions / iterateSbirTopics / iterateSbirSolicitations

Utility

  • getVersion() / listApiKeys()

See docs/API_REFERENCE.md for full signatures and parameters.

All list methods return a paginated response:

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

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). Exposes the API's structured 400 payload via issues and availableFields (see the API Reference).
  • TangoRateLimitError – Rate limit exceeded (429).
  • TangoTimeoutError – Request exceeded the configured timeoutMs.

Shape-related errors:

  • ShapeError
  • ShapeValidationError
  • ShapeParseError
  • TypeGenerationError
  • ModelInstantiationError

Use them in your code:

import{TangoClient,TangoAPIError,TangoValidationError}from"@makegov/tango-node";try{constresp=awaitclient.listContracts({keyword: "cloud",limit: 5});}catch(err){if(errinstanceofTangoValidationError){console.error("Bad request:",err.message);}elseif(errinstanceofTangoAPIError){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
│ │ ├── 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

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 (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 – Regenerate src/shapes/generatedOverlay.ts from the vendored contract.

See docs/DEVELOPERS.md for the conformance architecture and the cassette record/replay workflow.

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

Official Node.js SDK for the Tango API — federal contracts, IDVs, entities, opportunities, grants, vehicles, and more, with dynamic response shaping.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages