SDKs

Quota provides two JavaScript/TypeScript SDK packages: @usequota/core for any runtime, and @usequota/nextjs for Next.js-specific features like React hooks and route handlers.

Core SDK (@usequota/core)

The core SDK is a framework-agnostic client that works in any JavaScript or TypeScript environment: Node.js, Deno, Bun, Cloudflare Workers, React Native, edge functions, and more. It provides:

  • QuotaClient — typed HTTP client for the Quota API (balance, billing, user management, packages, OAuth)
  • Typed error classes for structured error handling
  • SSE streaming parser for chat completions
  • Web Crypto webhook signature verification (works in edge runtimes)
  • Token storage interface with an in-memory implementation

Installation

npm install @usequota/core

Usage

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

const quota = new QuotaClient({
  apiKey: process.env.QUOTA_API_KEY,
  baseUrl: "https://api.usequota.ai", // optional, this is the default
});

// Check balance
const balance = await quota.getBalance();

// Make a chat completion (OpenAI-compatible)
const response = await quota.createChatCompletion({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello!" }],
});

// Verify webhook signatures (Web Crypto, works on edge)
import { verifyWebhookSignature } from "@usequota/core";

const isValid = await verifyWebhookSignature({
  payload: rawBody,
  signature: request.headers.get("X-Quota-Signature")!,
  secret: process.env.QUOTA_WEBHOOK_SECRET!,
});

If you are building a Next.js app, use @usequota/nextjs instead — it re-exports everything from core plus adds React-specific features.


Next.js SDK (@usequota/nextjs)

The @usequota/nextjs package builds on the core SDK and adds middleware, React hooks, server utilities, and pre-built components for integrating Quota into Next.js apps.

Installation

npm install @usequota/nextjs

Quick Start

Three pieces: middleware to handle OAuth callbacks, a provider for React context, and hooks to access user data.

1. Middleware

Create middleware.ts at your project root:

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

export default createQuotaMiddleware({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  baseUrl: "https://api.usequota.ai",
  callbackPath: "/api/quota/callback",   // default
  storageMode: "client",                  // or "hosted"
});

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

2. Provider

Wrap your app in QuotaProvider:

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

export default function Layout({ children }) {
  return (
    <QuotaProvider
      clientId={process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!}
      baseUrl="https://api.usequota.ai"
    >
      {children}
    </QuotaProvider>
  );
}

3. Hooks

import { useQuotaAuth, useQuotaBalance } from "@usequota/nextjs";

function MyComponent() {
  const { isAuthenticated, login, logout } = useQuotaAuth();
  const { balance, refetch } = useQuotaBalance();

  if (!isAuthenticated) return <button onClick={login}>Connect Wallet</button>;

  return (
    <div>
      <p>Balance: ${balance}</p>
      <button onClick={logout}>Disconnect</button>
    </div>
  );
}

Storage Modes

Client Mode (default)

OAuth tokens are stored in httpOnly cookies on the user's browser. Your app reads them to make API calls.

createQuotaMiddleware({
  clientId: "...",
  clientSecret: "...",
  storageMode: "client",
});

Hosted Mode

Tokens are stored server-side by Quota. Your app receives an external_user_id and sends it via the X-Quota-User header. This avoids handling tokens directly.

createQuotaMiddleware({
  clientId: "...",
  clientSecret: "...",
  storageMode: "hosted",
  getExternalUserId: async (request) => {
    // Return your app's user ID for the current request
    const session = await getSession(request);
    return session.userId;
  },
});

See Hosted Users API for how the server-side flow works.

Hooks

useQuota()

Access the full Quota context including user, loading state, error, login, logout, and refetch.

useQuotaUser()

Returns the current QuotaUser or null if not authenticated.

useQuotaAuth()

const {
  isAuthenticated, // boolean
  isLoading,       // boolean
  login,           // () => void — redirects to OAuth
  logout,          // () => Promise<void> — clears cookies
} = useQuotaAuth();

useQuotaBalance()

const {
  balance,   // number | null (in micro-dollars; divide by 1,000,000 for display)
  isLoading, // boolean
  error,     // Error | null
  refetch,   // () => Promise<void>
} = useQuotaBalance();

useQuotaPackages()

Fetches available purchase packages from the public /v1/packages endpoint. No authentication is required. Results are cached for the component lifetime — the hook auto-fetches on first render and subsequent calls return cached data.

const {
  packages,  // PurchasePackage[]
  isLoading, // boolean
  error,     // Error | null
  refetch,   // () => Promise<void>
} = useQuotaPackages();

Components

QuotaConnectButton

Pre-styled OAuth login button with loading states.

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

<QuotaConnectButton
  variant="primary"         // "primary" | "secondary" | "ghost"
  onSuccess={() => {}}
  onError={(err) => {}}
  showWhenConnected={false} // hide after connect
/>

QuotaBalance

Displays the user's dollar balance.

<QuotaBalance format="dollars" showRefresh />

QuotaBuyCredits

Button that opens a Stripe checkout session for purchasing balance.

<QuotaBuyCredits packageId="basic" checkoutPath="/api/quota/checkout" />

QuotaUserMenu

Dropdown menu showing avatar, balance, and actions (add funds, disconnect). Supports keyboard navigation and ARIA.

<QuotaUserMenu showBuyCredits />

Server Utilities

Use these in Server Components, Route Handlers, or Server Actions.

getQuotaUser

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

const user = await getQuotaUser({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  storageMode: "client", // or "hosted"
});
// Returns QuotaUser | null

requireQuotaAuth

Same as getQuotaUser but redirects to / if not authenticated.

getQuotaPackages

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

const packages = await getQuotaPackages();
// [{ id: "starter", name: "Starter", price_cents: 500, balance_display: "$4.05" }]

createQuotaCheckout

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

const checkoutUrl = await createQuotaCheckout({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  packageId: "basic-500",
  successUrl: "https://yourapp.com/success",
  cancelUrl: "https://yourapp.com/pricing",
});

clearQuotaAuth

Deletes auth cookies (access token, refresh token, external user ID).

Webhook Utilities

createWebhookHandler

Returns a Next.js Route Handler that verifies signatures and routes events to your handler functions.

// app/api/webhooks/quota/route.ts
import { createWebhookHandler } from "@usequota/nextjs";

export const POST = createWebhookHandler(
  process.env.QUOTA_WEBHOOK_SECRET!,
  {
    "balance.low": async (event) => {
      // Send notification to user
    },
    "user.connected": async (event) => {
      // Update your database
    },
    "usage.completed": async (event) => {
      // Log usage analytics
    },
  }
);

verifyWebhookSignature

Low-level helper if you need custom verification logic:

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

const isValid = verifyWebhookSignature({
  payload: rawBody,
  signature: request.headers.get("X-Quota-Signature")!,
  secret: process.env.QUOTA_WEBHOOK_SECRET!,
});

parseWebhook

Reads the request body, verifies the signature, and returns the typed event object. Throws on invalid signatures.

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

const event = await parseWebhook(request, process.env.QUOTA_WEBHOOK_SECRET!);
// event.type, event.data, event.id, event.created_at

Route Handler Factory

createQuotaRouteHandlers generates all six API route handlers your app needs in one call. No more writing boilerplate for each route.

Before: 6 separate route files

// app/api/quota/authorize/route.ts — custom OAuth redirect logic
// app/api/quota/callback/route.ts  — token exchange + cookie setting
// app/api/quota/status/route.ts    — read cookies, fetch user, refresh tokens
// app/api/quota/packages/route.ts  — proxy to Quota API
// app/api/quota/checkout/route.ts  — parse body, create Stripe session
// app/api/quota/disconnect/route.ts — clear cookies

After: one config + thin route exports

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

export const {
  authorize,
  callback,
  status,
  packages,
  checkout,
  disconnect,
} = createQuotaRouteHandlers({
  clientId: process.env.QUOTA_CLIENT_ID!,
  clientSecret: process.env.QUOTA_CLIENT_SECRET!,
  // All other options are optional with sensible defaults
});

Then create thin route files that re-export the handlers:

// 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
export { packages as GET } from "@/lib/quota";

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

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

Config reference

OptionTypeDefault
clientIdstringrequired
clientSecretstringrequired
baseUrlstringhttps://api.usequota.ai
storageMode"client" | "hosted"client
cookiePrefixstringquota
cookieMaxAgenumber604800 (7 days)
callbackPathstring/api/quota/callback
successRedirectstring/
errorRedirectstring/
tokenStorageQuotaTokenStorage-
getExternalUserIdfunctionrequired for hosted mode
callbacks.onConnectfunction-
callbacks.onDisconnectfunction-

Returned handlers

HandlerMethodPurpose
authorizeGETInitiates the OAuth flow
callbackGETHandles OAuth callback, exchanges code for tokens
statusGETReturns connection status and balance
packagesGETReturns available purchase packages
checkoutPOSTCreates a Stripe checkout session
disconnectPOSTDisconnects the user's Quota account

withQuotaAuth

Wraps an API route handler with automatic Quota authentication. It reads the access token, fetches the user, refreshes expired tokens, and returns structured error responses for common failure modes.

// 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 here
    // accessToken can be used to proxy requests through Quota
    const body = await request.json();

    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: body.messages,
        }),
      }
    );

    return Response.json(await response.json());
  }
);

What the wrapper does

  1. Reads the access token from cookies (or tokenStorage adapter)
  2. Fetches the user from GET /v1/me
  3. If the token is expired, refreshes it automatically
  4. Passes { user, accessToken } to your handler
  5. Catches any QuotaError thrown inside your handler and returns the correct HTTP status and JSON body

Handler context

PropertyTypeDescription
userQuotaUserThe authenticated user (guaranteed non-null)
accessTokenstringOAuth access token (empty string in hosted mode)

Typed Errors

The SDK exports typed error classes that let you handle specific failure modes with instanceof checks. Import from @usequota/nextjs/server.

Error classes

ClassCodeStatusExtra properties
QuotaError(varies)(varies)code, statusCode, hint
QuotaInsufficientCreditsErrorinsufficient_credits402balance, required
QuotaNotConnectedErrornot_connected401
QuotaTokenExpiredErrortoken_expired401
QuotaRateLimitErrorrate_limit_exceeded429retryAfter (seconds)

Catching errors

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

try {
  const response = await fetch("https://api.usequota.ai/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
    body: JSON.stringify({ model: "gpt-4o-mini", messages }),
  });

  if (!response.ok) {
    // errorFromResponse() maps HTTP responses to typed errors
    const { errorFromResponse } = await import("@usequota/nextjs/server");
    const error = await errorFromResponse(response);
    if (error) throw error;
  }
} catch (e) {
  if (e instanceof QuotaInsufficientCreditsError) {
    // e.balance  — current balance (if available)
    // e.required — credits needed (if available)
    showBuyCreditsDialog();
  }

  if (e instanceof QuotaNotConnectedError) {
    redirectToLogin();
  }

  if (e instanceof QuotaRateLimitError) {
    // e.retryAfter — seconds to wait
    await new Promise(r => setTimeout(r, e.retryAfter * 1000));
    retry();
  }

  if (e instanceof QuotaError) {
    // e.code, e.statusCode, e.hint
    console.error(`Quota error [${e.code}]: ${e.message}`);
  }
}

Configuration Reference

QuotaConfig (Middleware)

OptionTypeDefaultDescription
clientIdstringrequiredOAuth client ID
clientSecretstringrequiredOAuth client secret
baseUrlstringhttps://api.usequota.aiQuota API base URL
callbackPathstring/api/quota/callbackOAuth callback route
storageMode"client" | "hosted"clientWhere tokens are stored
getExternalUserIdfunction-Required for hosted mode
cookie.prefixstringquotaCookie name prefix
cookie.maxAgenumber604800Cookie max age in seconds (7 days)

QuotaProviderProps

PropTypeDefaultDescription
clientIdstringrequiredOAuth client ID (use NEXT_PUBLIC_ env var)
baseUrlstringhttps://api.usequota.aiQuota API base URL
callbackPathstring/api/quota/callbackOAuth callback route
apiPathstring/api/quota/meUser info API route
fetchStrategy"eager" | "lazy"eagerWhen to fetch user data

fetchStrategy

Controls when QuotaProvider fetches user data. The default "eager" strategy fetches on mount, which is ideal when you need the user state immediately. Set to "lazy" to defer the fetch until refetch() is called manually. This is useful when you want to avoid unnecessary network requests on pages where user data may not be needed right away.

// Eager (default): fetches user data on mount
<QuotaProvider clientId="..." fetchStrategy="eager">

// Lazy: does NOT fetch on mount; call refetch() when needed
<QuotaProvider clientId="..." fetchStrategy="lazy">