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/nextjs

v4.0.0

Published

Next.js SDK for Quota — AI credit billing middleware, hooks, and components

Downloads

950

Readme

@usequota/nextjs

Next.js SDK for Quota — AI credit wallet and multi-provider inference API.

Full docs: usequota.ai/docs

The three integration paths

Pick one. This SDK supports all three.

| You want… | Use | Who pays | | --- | --- | --- | | To try the API, or to absorb AI cost yourself | API key (sk-quota-…) | Your developer wallet | | A new app, no auth yet — Quota provides identity and a wallet per user | <QuotaSignInButton /> (recipe) | The signed-in user's wallet | | An existing app with its own login — attach Quota credits to your users | <QuotaConnectButton /> (recipe) | The connected user's wallet |

This README covers the SDK surface (createQuotaRouteHandlers, hooks, components, server helpers). For end-to-end walkthroughs, go to the docs.

Installation

npm install @usequota/nextjs

Quick Start

1. Set up environment variables

# .env.local
QUOTA_CLIENT_ID=your_client_id
QUOTA_CLIENT_SECRET=your_client_secret
NEXT_PUBLIC_QUOTA_CLIENT_ID=your_client_id

2. Mount the route handlers

Create lib/quota.ts and re-export from each route file. createQuotaRouteHandlers mints handlers for authorize, callback, me, logout, status, packages, checkout, and disconnect in one call — supports PKCE, HMAC-signed state, and OIDC id_token verification out of the box.

// lib/quota.ts
import { createQuotaRouteHandlers } from "@usequota/nextjs/server";

export const {
  authorize,
  callback,
  me,
  logout,
  status,
  packages,
  checkout,
  disconnect,
} = createQuotaRouteHandlers({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});
// app/api/quota/callback/route.ts
export { callback as GET } from "@/lib/quota";

// app/api/quota/authorize/route.ts
export { authorize as GET } from "@/lib/quota";

// (repeat for me, logout, status, packages, checkout, disconnect)

3. Add QuotaProvider

Wrap your app with the provider in app/layout.tsx:

import { QuotaProvider } from "@usequota/nextjs";
import "@usequota/nextjs/styles.css"; // Required for pre-built UI components

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <QuotaProvider clientId={process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!}>
          {children}
        </QuotaProvider>
      </body>
    </html>
  );
}

4. Create API routes

Create the following API routes:

app/api/quota/me/route.ts - Fetch user data:

import { getQuotaUser } from "@usequota/nextjs/server";

export async function GET() {
  const user = await getQuotaUser({
    clientId: process.env.QUOTA_CLIENT_ID!,
    clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  });

  if (!user) {
    return new Response("Unauthorized", { status: 401 });
  }

  return Response.json(user);
}

app/api/quota/logout/route.ts - Handle logout:

import { clearQuotaAuth } from "@usequota/nextjs/server";

export async function POST() {
  await clearQuotaAuth();
  return Response.json({ success: true });
}

app/api/quota/packages/route.ts - List available credit packages (required for useQuotaPackages and <QuotaBuyCredits />; proxies the public /v1/packages endpoint server-side so the browser request stays same-origin):

// Re-export the packages handler from your createQuotaRouteHandlers() setup.
// See the "Route Handlers" section below for the createQuotaRouteHandlers call.
export { packages as GET } from "@/lib/quota";

5. Use in components

"use client";

import { useQuota } from "@usequota/nextjs";

export function MyComponent() {
  const { user, isLoading, login } = useQuota();

  if (isLoading) return <div>Loading...</div>;
  if (!user) return <button onClick={login}>Sign in with Quota</button>;

  return (
    <div>
      <p>Welcome, {user.email}!</p>
      <p>Balance: ${(user.balance / 1_000_000).toFixed(2)}</p>
    </div>
  );
}

Features

  • OAuth 2.0 Authentication - Secure user authentication flow
  • Token Management - Automatic token refresh and cookie management
  • React Hooks - Convenient hooks for auth state and user data
  • Server-Side Utilities - Functions for API routes and server components
  • TypeScript Support - Full type safety with TypeScript
  • Hosted Mode - Optional server-side token storage for enhanced security

Choosing your sign-in component

The SDK ships two pre-built buttons. They look similar but solve different problems — pick by what your app needs from Quota.

<QuotaSignInButton /> — IdP pattern

Use when Quota IS your auth. One click signs the user in to your app and gives them a spendable AI balance. Default scopes: openid email profile credits.spend. Your app gets a verified id_token and never has to build a user table, password reset flow, or payment flow.

"use client";
import { QuotaSignInButton, useQuota } from "@usequota/nextjs";

export default function Home() {
  const { user } = useQuota();
  return user ? <p>Hi, {user.email}</p> : <QuotaSignInButton />;
}

<QuotaConnectButton /> — wallet-linking pattern

Use when your app already has its own auth (NextAuth / Auth0 / Clerk / custom) and you just want to attach Quota credits to existing users. The button assumes the user is already signed in — it does not authenticate them. The scopes carried on the resulting access token are controlled by your createQuotaRouteHandlers({ scopes }) config — pass ["credits.spend"] for a wallet-only token (no OIDC, no id_token).

"use client";
import { QuotaConnectButton } from "@usequota/nextjs";

export function BillingSettings() {
  // Render this on your settings page after your own auth is satisfied.
  return <QuotaConnectButton />;
}

Decision shortcut

| You're building… | Use | | -------------------------------------------------------------------- | ------------------------- | | A brand-new app, no existing user table | <QuotaSignInButton /> | | An existing app with its own auth, wants Quota credits on top | <QuotaConnectButton /> | | You'll pay for AI usage yourself (not your users) | No button — use a server-side API key |

Both buttons are first-class and supported. <QuotaConnectButton /> is not deprecated — it's the right tool for the wallet-linking pattern. Full decision tree and rationale: usequota.ai/docs.

API Reference

Client-Side

Hooks

  • useQuota() - Access full Quota context
  • useQuotaUser() - Get current user
  • useQuotaAuth() - Auth state and actions
  • useQuotaBalance() - User credit balance
  • useQuotaPackages() - List available credit packages (requires app/api/quota/packages/route.ts)

Components

  • <QuotaProvider> - React context provider
  • <MockQuotaProvider> - Drop-in replacement that injects a fixed auth state (see Mock provider)

Server-Side

Import from @usequota/nextjs/server:

  • getQuotaUser(config) - Get authenticated user
  • requireQuotaAuth(config) - Require auth (redirects if not logged in)
  • getQuotaPackages(config?) - Fetch available credit packages
  • createQuotaCheckout(config) - Create Stripe checkout session
  • clearQuotaAuth(config?) - Clear auth cookies
  • createQuotaRouteHandlers(config) - Generate all 6 API route handlers in one call
  • withQuotaAuth(config, handler) - Wrap a route handler with automatic Quota auth

Typed Errors

  • QuotaError - Base error class (code, statusCode, hint)
  • QuotaInsufficientCreditsError - 402, includes balance and required
  • QuotaNotConnectedError - 401, user has no Quota account connected
  • QuotaTokenExpiredError - 401, token expired and refresh failed
  • QuotaRateLimitError - 429, includes retryAfter (seconds)

Route-level sign-in gating

  • withQuotaIdentity(options) - Next.js middleware preset that gates routes behind the auth cookies set by createQuotaRouteHandlers. Reads cookies only — it does NOT handle OAuth itself.

Advanced Usage

Custom OAuth Flow

const { login } = useQuota();

// Trigger OAuth flow
login();

Server Components

import { getQuotaUser } from "@usequota/nextjs/server";

export default async function DashboardPage() {
  const user = await getQuotaUser({
    clientId: process.env.QUOTA_CLIENT_ID!,
    clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  });

  if (!user) {
    redirect("/");
  }

  return <div>Welcome, {user.email}!</div>;
}

Protected Routes

import { requireQuotaAuth } from "@usequota/nextjs/server";

export default async function ProtectedPage() {
  // Automatically redirects to login if not authenticated
  const user = await requireQuotaAuth({
    clientId: process.env.QUOTA_CLIENT_ID!,
    clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  });

  return <div>Protected content for {user.email}</div>;
}

Credit Packages

"use client";

import { useState, useEffect } from "react";
import type { CreditPackage } from "@usequota/nextjs";

export function BuyCredits() {
  const [packages, setPackages] = useState<CreditPackage[]>([]);

  useEffect(() => {
    fetch("/api/packages")
      .then((res) => res.json())
      .then(setPackages);
  }, []);

  const handleBuy = async (packageId: string) => {
    const res = await fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ packageId }),
    });
    const { url } = await res.json();
    window.location.href = url;
  };

  return (
    <div>
      {packages.map((pkg) => (
        <button key={pkg.id} onClick={() => handleBuy(pkg.id)}>
          Buy {pkg.credits} credits for {pkg.price_display}
        </button>
      ))}
    </div>
  );
}

Mock provider

<MockQuotaProvider> is a drop-in replacement for <QuotaProvider> that injects a fixed auth state instead of fetching from your API routes. Use it in docs sites, Storybook decorators, customer playgrounds, and unit tests — anywhere you want to render real SDK components against a fake user without standing up an OAuth client or a backend.

import { MockQuotaProvider, QuotaBalance, QuotaUserMenu } from "@usequota/nextjs";

const fakeUser = {
  id: "usr_demo",
  email: "[email protected]",
  balance: 5_000_000, // $5.00
};

export function HeaderPreview() {
  return (
    <MockQuotaProvider user={fakeUser}>
      <QuotaBalance format="dollars" />
      <QuotaUserMenu />
    </MockQuotaProvider>
  );
}

Pass user={null} to render the signed-out branch. login(), logout(), and refetch() are no-ops in mock mode, so click handlers won't navigate or fetch — perfect for demos and snapshot tests.

Props

| Prop | Type | Default | Purpose | | ----------- | ------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | user | QuotaUser \| null | seeded mock user | Override the authenticated user. null renders the signed-out branch. | | claims | IdTokenClaims \| null | null | Verified OIDC id_token claims. Set this to render IdP-mode components (anything reading useQuota().claims) against the mock. | | idToken | string \| null | null | Raw id_token JWT for components that read useQuota().idToken directly (e.g. forwarding identity to your own backend). | | isLoading | boolean | false | Force the loading branch. | | error | Error \| null | null | Force the error branch. | | baseUrl | string | https://api.usequota.ai | Base URL — also used to decide which /v1/packages fetch to intercept (see packages below). | | packages | CreditPackage[] \| Promise<...> \| undefined | undefined | When set, intercepts ${baseUrl}/v1/packages fetches and returns this list. Lets <QuotaBuyCredits />'s zero-config picker render without a live API. |

The default mock user is { id: "usr_mock_001", email: "[email protected]", balance: 5_000_000 } (5,000,000 credits = $5.00). DEFAULT_MOCK_PACKAGES is also exported — the canonical 4-package set (starter / basic / plus / pro) matching what production /v1/packages returns.

Storybook decorator pattern (parameters.quota)

Wire the mock provider into every story via a global decorator, then let each story configure its own auth and packages state through parameters.quota. This is how @usequota/nextjs's own Storybook is set up.

// .storybook/preview.tsx
import type { Preview } from "@storybook/react";
import { MockQuotaProvider } from "@usequota/nextjs";

const preview: Preview = {
  decorators: [
    (Story, context) => (
      <MockQuotaProvider {...(context.parameters.quota || {})}>
        <Story />
      </MockQuotaProvider>
    ),
  ],
};

export default preview;

Each story sets its own parameters.quota:

// MyHeader.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { DEFAULT_MOCK_PACKAGES } from "@usequota/nextjs";
import { MyHeader } from "./MyHeader";

const meta: Meta<typeof MyHeader> = { component: MyHeader };
export default meta;

export const SignedIn: StoryObj<typeof MyHeader> = {
  parameters: {
    quota: {
      user: { id: "u_1", email: "[email protected]", balance: 5_000_000 },
      packages: DEFAULT_MOCK_PACKAGES, // so <QuotaBuyCredits /> picker renders
    },
  },
};

export const SignedOut: StoryObj<typeof MyHeader> = {
  parameters: { quota: { user: null } },
};

The packages parameter accepts an array, a Promise<CreditPackage[]> (loading simulation), or a rejected promise (error simulation) — see the PickerOpen / PickerLoading / PickerError stories on <QuotaBuyCredits /> for the full pattern.

Webhook Verification

Quota can send webhooks to notify your application about events. The SDK provides utilities to verify webhook signatures and handle events securely.

Set up webhook endpoint

Add your webhook secret to .env.local:

QUOTA_WEBHOOK_SECRET=your_webhook_secret

Create webhook handler

app/api/quota/webhook/route.ts:

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

export const POST = createWebhookHandler(process.env.QUOTA_WEBHOOK_SECRET!, {
  "balance.low": async (event) => {
    // Send email notification when user balance is low
    await sendLowBalanceEmail(event.data.user_id, event.data.current_balance);
  },
  "user.connected": async (event) => {
    // Track new user connection
    await analytics.track("user_connected", event.data);
  },
  "usage.completed": async (event) => {
    // Log usage for analytics
    await logUsage(event.data);
  },
});

Webhook event types

Quota sends the following webhook events:

  • user.connected - User completed OAuth connection
  • user.disconnected - User disconnected their account
  • balance.updated - User's credit balance changed
  • balance.low - User's balance fell below threshold
  • usage.completed - API request completed and billed

Manual signature verification

For custom implementations:

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

export async function POST(req: Request) {
  try {
    // Option 1: Parse and verify in one step
    const event = await parseWebhook(req, process.env.QUOTA_WEBHOOK_SECRET!);

    // Option 2: Manual verification
    const payload = await req.text();
    const signature = req.headers.get("x-quota-signature")!;

    const isValid = verifyWebhookSignature({
      payload,
      signature,
      secret: process.env.QUOTA_WEBHOOK_SECRET!,
    });

    if (!isValid) {
      return Response.json({ error: "Invalid signature" }, { status: 400 });
    }

    const event = JSON.parse(payload);

    // Handle event...

    return Response.json({ received: true });
  } catch (error) {
    return Response.json({ error: error.message }, { status: 400 });
  }
}

Route Handler Factory

createQuotaRouteHandlers generates all API route handlers for Quota integration in one call, replacing 6 separate route files with boilerplate.

// lib/quota.ts
import { createQuotaRouteHandlers } from "@usequota/nextjs/server";

export const {
  authorize, // GET - initiates OAuth flow
  callback, // GET - handles OAuth callback
  status, // GET - returns connection status + balance
  packages, // GET - returns credit packages
  checkout, // POST - creates Stripe checkout session
  disconnect, // POST - disconnects user's Quota account
} = createQuotaRouteHandlers({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});

Then create thin route files:

// app/api/quota/authorize/route.ts
export { authorize as GET } from "@/lib/quota";

// app/api/quota/callback/route.ts
export { callback as GET } from "@/lib/quota";

// app/api/quota/status/route.ts
export { status as GET } from "@/lib/quota";

// app/api/quota/packages/route.ts
// Required for useQuotaPackages and <QuotaBuyCredits />. The handler
// proxies the public /v1/packages endpoint server-side so the browser
// request stays same-origin (no CORS config needed on api.usequota.ai).
export { packages as GET } from "@/lib/quota";

// app/api/quota/checkout/route.ts
export { checkout as POST } from "@/lib/quota";

withQuotaAuth

Wraps a route handler with automatic Quota authentication, token refresh, and error handling:

// app/api/summarize/route.ts
import { withQuotaAuth } from "@usequota/nextjs/server";

export const POST = withQuotaAuth(
  {
    clientId: process.env.QUOTA_CLIENT_ID!,
    clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  },
  async (request, { user, accessToken }) => {
    // user is guaranteed to exist
    // accessToken can proxy requests through Quota
    const response = await fetch(
      "https://api.usequota.ai/v1/chat/completions",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model: "gpt-4o-mini",
          messages: [{ role: "user", content: "Hello" }],
        }),
      },
    );
    return Response.json(await response.json());
  },
);

Typed Errors

Handle specific failure modes with instanceof checks:

import {
  QuotaInsufficientCreditsError,
  QuotaNotConnectedError,
  QuotaRateLimitError,
  QuotaError,
} from "@usequota/nextjs/server";

try {
  await callQuotaAPI();
} catch (e) {
  if (e instanceof QuotaInsufficientCreditsError) {
    console.log(e.balance, e.required); // current balance, credits needed
  }
  if (e instanceof QuotaNotConnectedError) {
    // redirect to login
  }
  if (e instanceof QuotaRateLimitError) {
    await delay(e.retryAfter * 1000); // seconds until retry
  }
  if (e instanceof QuotaError) {
    console.log(e.code, e.statusCode, e.hint);
  }
}

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. This package depends on @usequota/core and @usequota/types; when those see a major bump, update the ^X.Y.Z ranges here in lockstep.

License

MIT