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

zpi-sdk

v0.2.0

Published

Universal, zero-dependency TypeScript SDK for the Zest API (Zapi) scraper platform.

Readme

Quick start  •  Why zpi-sdk  •  Install  •  What you can build  •  Errors  •  Runtimes  •  Docs

[!NOTE] This README is a high-level overview. The complete API reference, guides, and recipes live in the documentation site at https://zpi.web.id/docs.


Quick start

Grab an API key from zpi.web.id, construct a client, and call any scraper — run() returns the unwrapped result data directly:

import { ZpiClient } from 'zpi-sdk'

const client = new ZpiClient({ apiKey: 'zpi_...' })

const data = await client.run('social:instagram', 'profile', { username: 'instagram' })
console.log(data)

That is the whole integration — no HTTP method to pick, no path templates to learn:

  • The SDK figures out the right verb (GET/POST) automatically and remembers it per endpoint.
  • Endpoints with path params like resolve/:url? Just put url in the params object — done. run('bypass-tools:encurtador', 'resolve', { url: '...' }) and run('bypass-tools:encurtador', 'resolve/:url', { url: '...' }) both work.

Build with AI

zpi-sdk ships a built-in MCP client behind the zpi-sdk/mcp subpath, so AI agents can discover and call every scraper as MCP tools. It speaks JSON-RPC over the remote /mcp Streamable-HTTP server — hand-rolled, so it stays zero-dependency:

import { createMcpClient } from 'zpi-sdk/mcp'

const mcp = createMcpClient({ apiKey: 'zpi_...' })

const tools = await mcp.listTools()           // handshake runs lazily on first call
const result = await mcp.callTool('run_scraper', { /* tool args */ })

The mcp module never loads from the root entry, so the core stays lean. See the full guide → zpi.web.id/docs.

Why zpi-sdk

  • Zero dependencies — no runtime deps, no node builtins; one injectable fetch seam powers every runtime from Node to the browser.
  • One method, every scraperclient.run('social:instagram', 'profile', params) covers the whole catalog; no per-scraper wrappers to learn.
  • Zero ceremony — no { method: 'GET' } guessing (auto-detected + memoized per endpoint) and path params (/:id, /:url) are just regular fields in params.
  • Typed error hierarchy — every failure is a ZpiError subclass (ZpiRateLimitError, ZpiPlanGateError, …) with typed fields like retryAfterSec and upgradeUrl. No status-code guessing.
  • Streaming out of the boxclient.stream(…) returns an async iterable of SSE events, reading via body.getReader() so it streams incrementally everywhere.
  • Bulk jobs — submit many items, job.wait() with progress callbacks; submits auto-reuse an Idempotency-Key, so retries never create duplicate jobs.
  • Public catalog discovery — list scrapers, categories, endpoint schemas, and stats without auth.
  • Webhook verification built inzpi-sdk/webhooks verifies X-Zpi-Signature (HMAC-SHA256, timing-safe) and returns typed events.
  • Typed codegennpx zpi codegen generates per-scraper bindings from the live catalog, narrowing run()'s params with full autocomplete.
  • Safe retries — only network errors and 429/502/503/504 are retried (exponential backoff + jitter); a POST is never blind-retried without an idempotencyKey. API key redacted from every error and log.

Install

npm i zpi-sdk      # or: pnpm add zpi-sdk  •  yarn add zpi-sdk  •  bun add zpi-sdk

Requires Node.js v20+. Deno needs no install step — import via the npm: specifier:

import { ZpiClient } from 'npm:zpi-sdk'

Zero runtime dependencies, dual ESM/CJS with .d.ts + .d.cts types, and a bin (zpi) for codegen.

All configuration is optional beyond the API key:

const client = new ZpiClient({
  apiKey: 'zpi_...',
  baseURL: 'https://api.zpi.web.id', // override the default base URL
  timeoutMs: 30_000,                 // per-request timeout
  maxRetries: 2,                     // retry budget for retryable failures
  fetch: globalThis.fetch,           // inject your own fetch (tests, proxies, edge runtimes)
})

What you can build

Run any scraper

const profile = await client.run('social:instagram', 'profile', { username: 'instagram' })
const video = await client.run('downloader:tiktok', 'video', { url: 'https://tiktok.com/...' })
const gold = await client.run('finance:goldprice', 'latest', {})

Streaming

For chunked / SSE endpoints, iterate the async iterable returned by client.stream(...):

for await (const event of client.stream('ai:chat', 'completions', { prompt: 'hi' })) {
  // StreamEvent = SseEvent ({ event?, data, id? }) | string
  console.log(typeof event === 'string' ? event : event.data)
}

Bulk jobs

Submit many items at once, then wait() for completion with optional progress reporting:

const job = await client.bulk.submit('social:instagram', 'profile', [
  { url: 'https://instagram.com/a' },
  { url: 'https://instagram.com/b' },
])

const result = await job.wait({
  onProgress: (j) => console.log(j.succeeded, '/', j.total),
})

for (const item of result.items ?? []) {
  console.log(item.status, item.data ?? item.error)
}

Catalog discovery (public, no auth)

const { items } = await client.catalog.list({ cat: 'social', limit: 20 })
const detail = await client.catalog.get('social:instagram')
const schema = await client.catalog.schema('social:instagram', 'profile')
const stats = await client.catalog.stats('social:instagram')

Handle webhooks

Verify and parse incoming webhook deliveries (bulk.completed, quota.warning, request.error, …) with the zero-dependency zpi-sdk/webhooks helper — Web Crypto HMAC with a timing-safe compare, so it runs on Node, Bun, Deno, and edge workers:

import { parseWebhook } from 'zpi-sdk/webhooks'

// In your HTTP handler — pass the RAW body string, not the parsed JSON:
const event = await parseWebhook(rawBody, {
  signature: req.headers['x-zpi-signature'],
  secret: process.env.ZPI_WEBHOOK_SECRET,
})
// → { id, event: 'bulk.completed' | …, data, deliveredAt }
// Throws ZpiWebhookVerifyError on a bad signature or malformed payload.

Typed codegen

Generate per-scraper bindings from the live catalog — run()'s params become fully autocompleted:

npx zpi codegen                 # → ./zpi-sdk.gen.d.ts
npx zpi codegen --out ./types/zpi.gen.d.ts --filter social

The output is types-only (zero runtime import); regenerate any time the catalog drifts.

Error handling

Every failure throws a ZpiError (or a subclass) carrying status, code?, raw, and requestId? — catch and branch on the class:

import { ZpiClient, ZpiPlanGateError, ZpiRateLimitError, ZpiError } from 'zpi-sdk'

try {
  await client.run('social:instagram', 'profile', { username: 'instagram' })
} catch (e) {
  if (e instanceof ZpiPlanGateError) console.log('upgrade required:', e.requiredPlan, e.upgradeUrl)
  else if (e instanceof ZpiRateLimitError) console.log('retry after', e.retryAfterSec, 's')
  else if (e instanceof ZpiError) console.log('zpi error:', e.status, e.code, e.raw)
}

| Class | When | Notable fields | | --- | --- | --- | | ZpiAuthError | Missing / invalid API key (401) | — | | ZpiPlanGateError | Endpoint requires a higher plan (403) | requiredPlan, upgradeUrl | | ZpiRateLimitError | Rate limit hit (429) | limit, used, window, retryAfterSec | | ZpiInvalidParamsError | Params failed validation (400/422) | errors[] | | ZpiExecError | The scraper ran but failed | error, errors, context | | ZpiNetworkError / ZpiTimeoutError / ZpiAbortError | Transport failure / timeout / abort | cause | | ZpiMcpError | JSON-RPC error from /mcp | code, data |

The full taxonomy (bulk, idempotency, 404/405/5xx classes) is documented at zpi.web.id/docs.

Runtime support

zpi-sdk ships dual ESM/CJS entry points with type declarations for both module systems, and is verified to load on:

| Runtime | ESM | CJS | | ------- | --- | --- | | Node.js >=20 | ✅ | ✅ | | Bun | ✅ | ✅ | | Deno (npm: specifier) | ✅ | ✅ | | Browser | ✅ | — |

Package managers: npm, pnpm, yarn, and bun are all supported.

Documentation

Issues & feedback

Hit a problem or have a feature request? Open an issue.

License

Distributed under the MIT License. See LICENSE for details.