JSPM

@usequota/nextjs

0.3.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q42207F
  • License MIT

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

Package Exports

  • @usequota/nextjs
  • @usequota/nextjs/server
  • @usequota/nextjs/styles.css

Readme

@usequota/nextjs

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

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. Configure middleware

Create middleware.ts in your project root:

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

export const middleware = createQuotaMiddleware({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});

export const config = {
  matcher: "/api/quota/callback",
};

3. Add QuotaProvider

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

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

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 });
}

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

API Reference

Client-Side

Hooks

  • useQuota() - Access full Quota context
  • useQuotaUser() - Get current user
  • useQuotaAuth() - Auth state and actions
  • useQuotaBalance() - User credit balance

Components

  • <QuotaProvider> - React context 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)

Middleware

  • createQuotaMiddleware(config) - Create OAuth callback middleware

Storage Modes

Client Mode (Default)

Tokens are stored in httpOnly cookies:

export const middleware = createQuotaMiddleware({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  storageMode: "client", // default
});

Hosted Mode

Tokens stored server-side by Quota (more secure):

export const middleware = createQuotaMiddleware({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  storageMode: "hosted",
  getExternalUserId: async (request) => {
    // Return your user's ID from your auth system
    const session = await getSession(request);
    return session.userId;
  },
});

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>
  );
}

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";

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);
  }
}

License

MIT