npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

blitz-api-js

v0.3.0

Published

Typed TypeScript SDK for the Blitz API — B2B data, search, and enrichment.

Readme

blitz-api-js

The typed TypeScript SDK for the Blitz API — B2B data, search, and enrichment.

  • Fully typed — Zod-validated response models with inferred TypeScript types and editor autocomplete; ships .d.ts (ESM) and .d.cts (CJS).
  • Async, fetch-based — a single BlitzAPI class; every method returns a Promise. Works on Node 20+ and any runtime with a global fetch.
  • Resilient — built-in client-side rate limiting, retries with backoff on 429/5xx/network errors, and a typed exception hierarchy.
  • Forward-compatible — fields the API adds later are preserved, never dropped or rejected.
  • 1:1 with the API — request and response field names are snake_case, exactly matching docs.blitz-api.ai.

Create and manage API keys at app.blitz-api.ai.

Installation

npm install blitz-api-js
# or: pnpm add blitz-api-js   /   yarn add blitz-api-js

Requires Node.js 20+. Ships both ESM and CommonJS builds.

Quickstart

import { BlitzAPI } from "blitz-api-js";

// api_key defaults to the BLITZ_API_KEY environment variable.
const client = new BlitzAPI();

// Health-check the key before a batch job.
const info = await client.account.key_info();
console.log(info.valid, info.remaining_credits, info.max_requests_per_seconds);

// LinkedIn profile URL -> verified work email.
const email = await client.enrichment.email({
  person_linkedin_url: "https://www.linkedin.com/in/example-person",
});
if (email.found) console.log(email.email);

// Search people with typed, autocompleted filters (snake_case, 1:1 with the API).
// List methods are paginated; iterate one page's items via `.data` (or stream
// every page with `for await` — see Pagination below).
const page = await client.search.people({
  company: { industry: { include: ["Software Development"] } },
  people: { job_level: ["VP"] },
  max_results: 10,
});
for (const person of page.data) {
  console.log(person.full_name, person.headline);
}

CommonJS works too:

const { BlitzAPI } = require("blitz-api-js");

Authentication

Pass the key explicitly or via the BLITZ_API_KEY environment variable:

const explicit = new BlitzAPI({ api_key: "sk_..." }); // explicit
const fromEnv = new BlitzAPI();                        // reads BLITZ_API_KEY

The key is sent in the x-api-key header. Never expose it in client-side code — always call the API from your backend.

Endpoints

All methods are grouped into four namespaces:

| Namespace | Methods | | --- | --- | | client.account | key_info() | | client.search | people(), companies(), employee_finder(), waterfall_icp() | | client.enrichment | email(), phone(), email_to_person(), phone_to_person(), company(), domain_to_linkedin(), linkedin_to_domain() | | client.utils | current_date(), company_employment_distribution() |

Each method takes a single options object (snake_case keys) and returns a typed, Zod-validated response (also snake_case). Enum-backed filter fields (e.g. industry, job_level, continent) accept either a known value — autocompleted from a union like Industry — or any raw string, so a value missing from the vendored taxonomy never blocks you.

import { INDUSTRY } from "blitz-api-js"; // the full value array (534 industries)
import type { CompanyFilter, Industry } from "blitz-api-js";

The three search list methods — people, companies, employee_finder — return a paginated PagePromise instead of a plain response (see Pagination). waterfall_icp and the enrichment/utils/account methods return their response directly.

Pagination

search.people and search.companies are cursor-paginated; search.employee_finder is page-paginated. Each returns a PagePromise you can either await for the first page or for await to stream every item across all pages — each page is fetched on demand, through the client's rate limiter.

max_results is the page size, not a total. It's "results per page" (1–50), and the API bills 1 credit per result returned. A bare for await streams every match up to the server-side limit (people: 50k results / 1k pages; employee finder: 10k), which can be a lot of credits. To bound it, pass max_items (a client-side total cap — never sent on the wire), break out of the loop, or drive pages manually.

// Stream up to max_items results across pages, then stop fetching:
for await (const person of client.search.people({ company: {/* … */}, max_results: 50, max_items: 200 })) {
  console.log(person.full_name);
}

// Or collect into an array (also honors max_items):
const people = await client.search.people({ max_results: 50, max_items: 200 }).collect();

// Or take the first page and drive pagination manually (uncapped — your loop, your call):
const page = await client.search.companies({ max_results: 25 });
page.data; // Company[] — items on this page
page.response.total_results; // the full parsed response (snake_case, 1:1 with the API)
if (page.has_next_page()) {
  const next = await page.get_next_page();
}

// Or iterate page-by-page:
const first = await client.search.employee_finder({ company_linkedin_url: "…", max_results: 50 });
for await (const p of first.iter_pages()) {
  console.log(`page ${p.response.page}/${p.response.total_pages} — ${p.data.length} items`);
}

max_items caps how many items for await / collect() yield and stops fetching once reached; it bounds the total fetched to within one page (`≈ ceil(max_items / max_results)

  • max_results), so tune max_results too for tight spend control. Auto-pagination otherwise stops when the API signals the end (cursor: null, or pagebeyondtotal_pages). search.waterfall_icp` returns a single ranked result set and is not paginated.

Configuration

const client = new BlitzAPI({
  api_key: undefined, // falls back to BLITZ_API_KEY
  base_url: "https://api.blitz-api.ai",
  timeout: 30, // default per-request timeout, seconds (via AbortSignal.timeout)
  max_retries: 3, // retries on 429 / 5xx / pre-response network errors
  rate_limit_rps: 5, // client-side token bucket; null to disable
  fetch: undefined, // custom fetch implementation (tests / runtimes)
});

// Override the timeout for a single call — pass an options object as the last
// argument to any method (it never appears on the wire):
await client.enrichment.email({ person_linkedin_url: "…" }, { timeout: 5 });

The client-side rate limiter is a token bucket: it admits at most rate_limit_rps requests per second so a single client instance stays under the API's limit (5 req/s by default; check your key's limit via (await client.account.key_info()).max_requests_per_seconds). Across multiple processes you may still hit 429 — the retry path handles that.

Error handling

import {
  APIConnectionError,
  APIResponseValidationError,
  APIStatusError,
  APITimeoutError,
  AuthenticationError,
  BlitzError,
  InsufficientCreditsError,
  NotFoundError,
  RateLimitError,
  ServerError,
} from "blitz-api-js";

try {
  await client.enrichment.email({ person_linkedin_url: "..." });
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    // 402 — out of credits
  } else if (err instanceof AuthenticationError) {
    // 401 — bad key
  } else if (err instanceof APIResponseValidationError) {
    // 2xx, but the body wasn't valid JSON or didn't match the schema (err.cause has details)
  } else if (err instanceof APIStatusError) {
    console.log(err.status_code, err.message, err.body, err.request_id);
  } else if (err instanceof BlitzError) {
    // base class for everything this SDK raises
  }
}

429 and 5xx are retried automatically (with backoff + jitter) up to max_retries; 401/402/404 throw immediately. A pre-response network error (DNS failure, connection refused) is retried, then surfaces as APIConnectionError. Timeouts are not retried — with fetch we can't tell whether the request already reached the (per-result-billed) server, so a timeout surfaces immediately as APITimeoutError rather than risk a double charge. Raise the per-call timeout for genuinely slow endpoints instead.

Forward compatibility

Response objects keep their snake_case wire keys and preserve unknown fields — if the API adds a property before this SDK models it, the value is still present on the parsed object (typed as unknown). Known fields stay precisely typed.

Development

See CONTRIBUTING.md for local setup, the test/type/lint commands, the enum code generator, and the automated release process.

License

MIT