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

crawlsnap

v0.5.0

Published

Official JavaScript/TypeScript SDK for CrawlSnap — the data intelligence API platform

Readme

crawlsnap

Official JavaScript / TypeScript SDK for CrawlSnap — a data intelligence platform that delivers structured, on-demand data through fast, typed APIs. Authenticate once and call any CrawlSnap data product, with first-class types, automatic retries, and pagination built in.

  • Fully typed, idiomatic client built on the native fetchzero runtime dependencies
  • Works on Node 20+, Bun, Deno, Cloudflare Workers, and the browser
  • Ships dual ESM + CommonJS with bundled .d.ts types
  • Resource namespacing: client.vectorSnap.ip(...)
  • Per-API version pinning with a stable default — your calls never silently jump to a new API version
  • Returns typed data; throws typed errors — no envelope bookkeeping
  • Built-in retries with exponential backoff, configurable timeout, AbortSignal support, auto-pagination

Installation

npm install crawlsnap

Requires Node 20+ (or any runtime with a global fetch and AbortSignal.any).

Authentication

Get an API key (sk-cs-...) from your CrawlSnap dashboard. Provide it via the environment or explicitly:

export CRAWLSNAP_API_KEY=sk-cs-...
import { CrawlSnap } from "crawlsnap";

const client = new CrawlSnap();                       // reads CRAWLSNAP_API_KEY
const client = new CrawlSnap({ apiKey: "sk-cs-..." }); // or pass it explicitly

The key is sent as Authorization: Bearer sk-cs-.... Treat it like a password — never embed it in client-side code or commit it to source control.

Quick start

import { CrawlSnap } from "crawlsnap";

const client = new CrawlSnap({ apiKey: "sk-cs-..." });

const ip = await client.vectorSnap.ip("8.8.8.8");
console.log(ip.reputation, ip.as_owner, ip.country);

Each call resolves to the typed enrichment payload directly, and throws a typed error on failure — you never inspect an is_success envelope yourself.

Payload fields use the API's wire format (snake_case, e.g. as_owner), like the OpenAI and Stripe SDKs; only the SDK surface (client, resources, methods, options) is camelCase.

Concurrency

JavaScript is async by default, so there is a single client and every method returns a Promise. Enrich many indicators at once with Promise.all:

const [ip, domain] = await Promise.all([
  client.vectorSnap.ip("8.8.8.8"),
  client.vectorSnap.domain("google.com"),
]);
console.log(ip.as_owner, domain.reputation);

Resources

| Resource | Methods | Returns | |----------|---------|---------| | vectorSnap | url · hash · ip · domain | reputation, detections, categories, relationships | | pulseSnap | url · hash · ip · domain | threat-intelligence pulse (and sandbox) summary | | subdoSnap | scan · scanIter | enumerated subdomains (paginated) | | sportSnap | live scores · fixtures · matches · competitions · teams · channels · news · search · players (35 methods) | football (soccer) data: scores, fixtures, match detail, competitions, teams, TV channels, news, search, player profiles |

const url    = await client.vectorSnap.url("https://example.com");
const file   = await client.vectorSnap.hash("44d88612fea8a8f36de82e1278abb02f");
const domain = await client.vectorSnap.domain("google.com");

const pulse  = await client.pulseSnap.ip("8.8.8.8");

const board   = await client.sportSnap.livescores();
const fixtures = await client.sportSnap.matches("tr");           // iso_code optional
const match   = await client.sportSnap.match(5542814);
const comps   = await client.sportSnap.competitions();
const league  = await client.sportSnap.competition("england", "premier-league");
const team    = await client.sportSnap.nationalTeam("brazil");
const channels = await client.sportSnap.channels();
const info    = await client.sportSnap.channelInfo("bein-connect-turkey");
const news    = await client.sportSnap.news();                    // start/iso_code optional
const results = await client.sportSnap.searchAll("messi");
const player  = await client.sportSnap.player("lionel-messi", 12345);

Every method takes its lookup value(s) as leading arguments and an optional per-call options object ({ signal, timeout, maxRetries, rawResponse }).

sportSnap covers the full football (soccer) data surface — live scores with in-match events, fixture lists (matches, matchesExtended), single-match views (match, matchExtended, matchStats, matchCommentaries, matchChannels, matchExtraBroadcasts), the competition catalog and detail (competitions, competition, competitionTables, competitionTvRights, competitionTwitter), national and club teams (popularTeams, allTeams, nationalTeam, clubTeam), TV channel directories and schedules (allChannels, channels, channelInfo, channelRepeats), the news feed (news, newsByTag, newsArticle, competitionNews, nationalTeamNews, clubTeamNews), full-text search (searchAll, searchTeams, searchCompetitions, searchMatches, searchPlayers, popularSearches), and player profiles (player).

Path segments (competition country/slug, team country/team, player slug/id) come from the url fields in list, search, and detail payloads. isoCode is a two-letter region code that resolves region-specific broadcast channels — omit it (or pass "") to use the server default; start paginates news feeds (omit or 0 for the first page).

API versioning

Each CrawlSnap data product is versioned independently, and the version is just a value the SDK puts in the request path — not something baked into your call site. A direct resource call targets that product's stable default version for this SDK release:

await client.vectorSnap.ip("8.8.8.8");        // default VectorSnap version (stable)

The default is pinned per SDK release and never moves on its own: upgrading the SDK does not silently retarget your calls at a newer API version. When a product ships a new version, you opt in explicitly — per product, without touching the others:

await client.vectorSnap.v1.ip("8.8.8.8");     // explicitly VectorSnap v1
await client.pulseSnap.url("https://x.com");   // unaffected — PulseSnap default

When a product's default version is bumped in a future SDK release, it ships as a deliberate, documented change — so you upgrade at your own pace.

Error handling

Failures throw a typed error instead of returning an error envelope. Narrow with instanceof:

import {
  NotFoundError,
  RateLimitError,
  QuotaExceededError,
  AuthenticationError,
  CrawlSnapError,
} from "crawlsnap";

try {
  const res = await client.vectorSnap.domain("example.com");
} catch (err) {
  if (err instanceof NotFoundError) {
    // 404 — no data for this indicator
  } else if (err instanceof QuotaExceededError) {
    console.log(err.message); // 402 — out of credits / monthly quota
  } else if (err instanceof RateLimitError) {
    console.log(err.retryAfter); // 429 — daily limit; seconds to wait
  } else if (err instanceof AuthenticationError) {
    // 401 — missing / invalid key
  } else if (err instanceof CrawlSnapError) {
    console.error(err); // base class for every SDK error
  }
}

| HTTP | Error | Notes | |------|-------|-------| | 400 | BadRequestError | invalid indicator | | 401 | AuthenticationError | missing / invalid key | | 402 | QuotaExceededError | out of credits or monthly quota | | 403 | SubscriptionInactiveError | subscription not active | | 404 | NotFoundError | no data for the indicator | | 429 | RateLimitError | daily limit; .retryAfter (seconds) | | 5xx | ServerError | server / upstream failure | | — | APIConnectionError / APITimeoutError | network failure / client timeout |

Every status error carries .statusCode, .message, .requestId, and .body (share the request id with support to speed up debugging).

Pagination

subdoSnap is paginated. Stream every subdomain across all pages — the cursor is handled for you:

for await (const subdomain of client.subdoSnap.scanIter("example.com")) {
  console.log(subdomain);
}

Or page manually:

let page = await client.subdoSnap.scan("example.com");
while (page.cursor) {
  page = await client.subdoSnap.scan("example.com", { cursor: page.cursor });
}

Configuration

const client = new CrawlSnap({
  apiKey: "sk-cs-...",
  baseURL: "https://api.crawlsnap.com",
  timeout: 30_000,   // ms
  maxRetries: 3,
  fetch: customFetch, // optional: proxies, custom runtimes, testing
});

| Option | Default | Description | |--------|---------|-------------| | apiKey | $CRAWLSNAP_API_KEY | Your sk-cs- key | | baseURL | $CRAWLSNAP_BASE_URL or https://api.crawlsnap.com | API host override | | timeout | 30000 | Per-request timeout (ms) | | maxRetries | 2 | Retries for 429 / 5xx / connection errors | | fetch | global fetch | Custom fetch implementation |

Retries use exponential backoff and honor the Retry-After header on 429.

Per-call options

Each method accepts an options object that overrides the client defaults and adds an AbortSignal and a raw-response escape hatch:

const controller = new AbortController();

const ip = await client.vectorSnap.ip("8.8.8.8", {
  signal: controller.signal, // cancel the request (and any pending retry wait)
  timeout: 5_000,
  maxRetries: 0,
});

Raw response

Pass rawResponse: true to get the full envelope (status, headers, request id) instead of just the data:

const raw = await client.vectorSnap.ip("8.8.8.8", { rawResponse: true });
console.log(raw.statusCode, raw.requestId, raw.isSuccess, raw.data);

Development

The types in src/models.ts are generated from the public OpenAPI contract; the client facade is hand-written. To refresh the types after the contract changes:

npm run regenerate   # re-bundles the contract and regenerates src/models.ts

Build, typecheck, and test:

npm install
npm run build
npm run typecheck
npm test             # vitest, mocked fetch — no network