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

@usequota/core

v3.0.0

Published

Framework-agnostic SDK for Quota — typed HTTP client, OAuth, webhooks, and SSE streaming

Readme

@usequota/core

Framework-agnostic SDK for Quota — the AI credit wallet and multi-provider inference API.

Works anywhere JavaScript runs: Node.js, Deno, Bun, Cloudflare Workers, React Native. Typed HTTP client, OAuth token management, webhook verification (via Web Crypto), and SSE streaming with zero framework dependencies.

Using Next.js? Use @usequota/nextjs instead — it adds route handlers, middleware, React hooks, and cookie-based auth on top of this package.

Full docs: usequota.ai/docs

The three integration paths

Pick which one fits your app, then come back here for the SDK pieces.

  • Developer API key — you cover the AI cost. Use an sk-quota-… key. Simplest path; best for prototypes and internal tools.
  • Sign in with Quota — Quota provides identity and a wallet per user. End users pay from their own balance.
  • Connect Quota Wallet — your app already has auth; you attach a Quota wallet to each existing user. End users pay from their own balance.

This SDK supports all three. For the Sign-in / Connect-Wallet flows specifically, the OAuth round-trip is yours to wire up — see the docs recipes — but token storage, refresh, and signed requests are all here.

Install

npm install @usequota/core

Create a client

API key auth (developer-pays)

import { QuotaClient } from "@usequota/core";

const client = new QuotaClient({
  apiKey: process.env.QUOTA_API_KEY,
});

const user = await client.getMe();
console.log(user.email);

OAuth mode (user-pays — Sign in with Quota or Connect Wallet)

import { QuotaClient } from "@usequota/core";

const client = new QuotaClient({
  accessToken: storedAccessToken,
  refreshToken: storedRefreshToken,
  clientId: process.env.QUOTA_CLIENT_ID,
  clientSecret: process.env.QUOTA_CLIENT_SECRET,
  onTokenRefresh: async (tokens) => {
    // Persist refreshed tokens to your database
    await db.updateTokens(userId, tokens);
  },
});

When the access token expires, the client automatically refreshes it using the refresh token and calls onTokenRefresh so you can persist the new tokens.

Check balance

const { balance } = await client.getBalance();

if (balance < 100) {
  console.log("Low credits -- purchase more at usequota.ai");
}

Stream AI completions with parseSSEStream

import { QuotaClient, parseSSEStream } from "@usequota/core";

const client = new QuotaClient({ apiKey: process.env.QUOTA_API_KEY });

const response = await fetch(`${client.baseUrl}/v1/chat/completions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.QUOTA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-4o",
    messages: [{ role: "user", content: "Explain quantum computing" }],
    stream: true,
  }),
});

for await (const chunk of parseSSEStream<{
  choices: Array<{ delta: { content?: string } }>;
}>(response.body!)) {
  process.stdout.write(chunk.choices[0].delta.content ?? "");
}

Verify webhooks

import { verifyWebhookSignature, parseWebhook } from "@usequota/core";

// Low-level: verify a signature yourself
const isValid = await verifyWebhookSignature({
  payload: rawBody,
  signature: request.headers.get("x-quota-signature")!,
  secret: process.env.QUOTA_WEBHOOK_SECRET!,
});

// High-level: parse and verify in one step
const event = await parseWebhook(request, process.env.QUOTA_WEBHOOK_SECRET!);
console.log(event.type, event.data);

createWebhookHandler

For a complete request-in, response-out handler:

import { createWebhookHandler } from "@usequota/core";

const handler = createWebhookHandler(process.env.QUOTA_WEBHOOK_SECRET!, {
  "balance.low": async (event) => {
    await sendLowBalanceEmail(event.data.user_id);
  },
  "user.connected": async (event) => {
    await syncUser(event.data);
  },
});

// Use with any framework that gives you a Request object:
// Deno: Deno.serve(handler)
// Bun:  Bun.serve({ fetch: handler })
// Cloudflare Workers: export default { fetch: handler }

Error handling

All Quota errors extend QuotaError and can be narrowed with instanceof:

import {
  QuotaClient,
  QuotaInsufficientCreditsError,
  QuotaNotConnectedError,
  QuotaTokenExpiredError,
  QuotaRateLimitError,
} from "@usequota/core";

const client = new QuotaClient({ apiKey: process.env.QUOTA_API_KEY });

try {
  await client.getBalance();
} catch (error) {
  if (error instanceof QuotaInsufficientCreditsError) {
    // 402 -- user needs more credits
    console.log(`Balance: ${error.balance}, Required: ${error.required}`);
  } else if (error instanceof QuotaNotConnectedError) {
    // 401 -- user hasn't connected their Quota account
    console.log(error.hint); // "Connect your Quota account to use this feature"
  } else if (error instanceof QuotaTokenExpiredError) {
    // 401 -- token expired and could not be refreshed
    // Re-authenticate the user
  } else if (error instanceof QuotaRateLimitError) {
    // 429 -- wait and retry
    console.log(`Retry after ${error.retryAfter} seconds`);
  }
}

OAuth token exchange

For apps that implement the OAuth flow manually:

import { exchangeCodeForToken, refreshAccessToken } from "@usequota/core";

// After receiving the authorization code from the OAuth redirect:
const tokens = await exchangeCodeForToken({
  code: authorizationCode,
  redirectUri: "https://yourapp.com/callback",
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});

// Later, refresh the token:
const newTokens = await refreshAccessToken({
  refreshToken: tokens.refresh_token,
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});

Token storage adapters

Implement QuotaTokenStorage to plug in your own persistence layer:

import { QuotaTokenStorage } from "@usequota/core";

const redisTokenStorage: QuotaTokenStorage = {
  async getTokens(request) {
    const userId = getUserIdFromRequest(request);
    const data = await redis.get(`quota:${userId}`);
    return data ? JSON.parse(data) : null;
  },
  async setTokens(tokens, request) {
    const userId = getUserIdFromRequest(request);
    await redis.set(`quota:${userId}`, JSON.stringify(tokens));
  },
  async deleteTokens(request) {
    const userId = getUserIdFromRequest(request);
    await redis.del(`quota:${userId}`);
  },
};

InMemoryTokenStorage is included for testing and prototyping.

Contributing

Any change under src/ (except tests) requires a version bump in package.json. See agent/WORKFLOWS.md → SDK Versioning for semver guidance and the bump+publish sequence. A CI check fails any PR that touches src without bumping. @usequota/nextjs depends on this package — major bumps here require a matching dep-range update in packages/quota-nextjs/package.json.

License

MIT