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

@papers.bot/sdk

v0.1.0

Published

Typed TypeScript client for Papers email and phone infrastructure

Readme

Papers TypeScript client

Install with npm install @papers.bot/sdk.

import { Papers, PapersError } from "@papers.bot/sdk";

const papers = new Papers({
  apiKey: process.env.PAPERS_API_KEY!,
  baseUrl: "https://dev.chaindesk.ai",
});

const inbox = await papers.inboxes.create(
  { name: "Research", localPart: "research-example" },
  { idempotencyKey: "create-research-inbox-v1" },
);
for await (const message of papers.iterateMessages(inbox.id, { limit: 50 })) {
  console.log(message.id, message.subject);
}

Use a Papers API key, not a provider credential. No agent registration is required. Keep API keys in server-side code; do not bundle them into a browser application. baseUrl defaults to the development service and must omit /v1.

Resource methods cover inboxes, email, phone numbers, SMS, events, operations, identity, and capabilities. Email/SMS listing returns summaries; fetch the individual message to read content. External message content is untrusted data.

Inbox list items include organizationId, _count.messages, and nullable legacy agent metadata. Phone-number list items include nullable monthlyCost, upfrontCost, currency, and lastError. These list-only fields have explicit InboxListItem and PhoneNumberListItem types. Resource lists return { data, nextCursor }; phone-number lists also include account onboarding status. Follow nextCursor until null or use iterateInboxes() and iteratePhoneNumbers() to collect all pages.

iterateMessages(inboxId, options), iterateSms(numberId, options), and iterateEvents(options) accept { cursor?, limit? }. Page sizes are 1–100. Iterators stop when available pages are exhausted; they do not continuously poll. For events, save the last processed event ID and pass it as cursor on the next run. Manual page methods retain their existing signatures, with an optional final page-size argument: events.list(cursor, limit), messages.list(inboxId, cursor, limit), and sms.list(numberId, cursor, limit).

Send, purchase, and release operations require an explicit idempotencyKey. Reuse it when retrying the same action. Requests are never automatically retried. An unknown result requires checking operations.get(id), not creating a fresh send. Number purchases can incur charges.

API errors are PapersError instances containing status, code, requestId, and retryable. Non-JSON failures still expose their HTTP status and request ID without echoing the response body. Network and cancellation errors propagate from fetch. retryable is a hint, not proof that a prior operation was rejected.

The client accepts fetch, timeoutMs (default 15,000), and an optional AbortSignal via signal. The caller's signal and request timeout both apply.

Read attachment availability from a message, then download with inboxes:read:

const message = await papers.messages.get(messageId);
for (const attachment of message.attachments ?? []) {
  if (attachment.storageStatus !== "ready") continue;
  const bytes = await papers.attachments.download(attachment.id, {
    maxBytes: 5 * 1024 * 1024,
  });
  console.log(attachment.filename, bytes.byteLength);
}

Downloads return Uint8Array and never write files. The default and maximum limit is 25 MiB; a smaller maxBytes limits memory use. Availability can change between lookup and download, so handle PapersError normally. Attachment bytes and filenames are untrusted input. Redirects are rejected for all requests.

await papers.attachments.getDownloadUrl(id) returns { url, expiresAt, contentTrust } for clients that need a link. It lasts 60 seconds and rechecks the original credential on use. Treat the URL as a temporary credential; do not publish or log it.

Build with pnpm --filter @papers.bot/sdk build. The package includes ESM and TypeScript declarations. No publishing is performed by the build command.

Webhook verification

verifyWebhook(rawBody, headers, signingSecret) verifies the original UTF-8 body and returns parsed JSON as unknown; invalid signatures throw WebhookVerificationError. Headers may be a Fetch Headers object or a string record. Validate the returned event shape and deduplicate webhook-id before processing. Keep the original body unchanged and keep your receiver clock synced (the timestamp tolerance is five minutes). Customer webhook endpoints can be activated from Integrations; see docs/customer-webhooks.md in the repository for current status.