@botparty/nextjs
v0.0.78
Published
Next.js SDK for BotParty auth — middleware, server auth(), route handlers, SSR provider
Maintainers
Readme
@botparty/nextjs
Next.js SDK for BotParty auth — middleware, server auth(), auto route handlers, and SSR provider. Clerk-like DX for BotParty identity and payments.
Quick Start
npx @botparty/nextjs initThis interactive command will:
- Scaffold a Next.js project (if run in an empty directory) with a demo landing page, authenticated dashboard, and sample API routes
- Generate an ES256 keypair for client authentication and service JWT signing
- Register your domain with id.botparty.club via DNS TXT verification (sends the public key to the server)
- Write all env vars to
.env - Create middleware and the catch-all route handler
Options:
npx @botparty/nextjs init --domain example.com # skip domain prompt
npx @botparty/nextjs init --force # re-register and regenerate
npx @botparty/nextjs init --jwks # host /.well-known/jwks.json instead of registering inline keyManual Install
npm install @botparty/nextjsEnvironment Variables
All set automatically by npx @botparty/nextjs init:
BOTPARTY_AUTH_URL=https://id.botparty.club # defaults to this if omitted
BOTPARTY_CLIENT_ID=bp_xxx
BOTPARTY_CLIENT_ASSERTION_KEY=<ES256 PEM private key> # signs client assertions + service JWTs
BOTPARTY_DOMAIN=example.com # your verified domain (optional, defaults to VERCEL_PROJECT_PRODUCTION_URL)The single BOTPARTY_CLIENT_ASSERTION_KEY does double duty:
- OAuth token exchange: signs
client_assertionJWTs (RFC 7523private_key_jwt) - Payment gateway: signs
X-BotParty-ServiceJWTs - Session cookies: AES encryption key derived from
SHA-256("bp-session-key:" + key)
Setup (3 files)
1. Route Handler
// app/api/botparty/[...botparty]/route.ts
export { GET, POST } from '@botparty/nextjs/handlers';Handles /login, /callback, /me, /sign-out, /wallet, /ledger automatically.
2. Middleware
// middleware.ts
import { botpartyMiddleware, createRouteMatcher } from '@botparty/nextjs/server';
const isPublic = createRouteMatcher(['/api/public(.*)', '/pricing', '/']);
export default botpartyMiddleware((auth, req) => {
if (!isPublic(req)) auth.protect();
});
export const config = { matcher: ['/((?!_next|.*\\..*).*)'] };3. Provider
// app/layout.tsx
import { BotPartyProvider } from '@botparty/nextjs';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<BotPartyProvider>{children}</BotPartyProvider>
</body>
</html>
);
}The provider is an async Server Component — it reads the session server-side and hydrates the client with zero waterfall.
Server Auth
Route Handlers & Server Components
import { auth } from '@botparty/nextjs/server';
export async function GET() {
const session = await auth();
if (!session.isAuthenticated) {
return new Response('Unauthorized', { status: 401 });
}
session.userId; // string | null
session.email; // string | null
session.name; // string | null
session.picture; // string | null
session.namespaceId; // string | null (bot namespace)
session.type; // 'human' | 'bot' | null
session.hasLinkedUser; // boolean
session.getToken(); // raw token string
// Helpers
session.protect(); // redirects if not authenticated
session.redirectToSignIn(); // explicit redirect
}Middleware Auth Object
Inside botpartyMiddleware, the auth callback receives:
interface MiddlewareAuthObject {
isAuthenticated: boolean;
type: 'human' | 'bot' | null;
userId: string | null;
namespaceId: string | null;
email: string | null;
protect(): MiddlewareAuthObject; // redirects to login if not authed
redirectToSignIn(): never;
}Creating Paid Endpoints
Use createPaymentGateway() to charge for API access via the x402 protocol. The gateway handles verification, payment creation, and settlement automatically.
Units: All amounts are integers in USDC atomic units (6 decimals). 1 USDC = 1,000,000 units.
Setup
import { createPaymentGateway } from '@botparty/nextjs/server';
const gateway = createPaymentGateway({ serviceName: 'My API' });Domain and private key are read from env vars automatically (BOTPARTY_DOMAIN, BOTPARTY_CLIENT_ASSERTION_KEY). On Vercel, domain falls back to VERCEL_PROJECT_PRODUCTION_URL if BOTPARTY_DOMAIN is not set.
You can override any of these in the config object:
const gateway = createPaymentGateway({
domain: 'custom.example.com',
serviceName: 'My API',
privateKey: process.env.MY_CUSTOM_KEY!,
});Pay-per-call
Charge a flat fee per request:
// app/api/bots/pay-per-call/route.ts
export async function POST(req: Request) {
const check = await gateway.requirePayment(req, {
amount: 10_000,
description: 'Pay-per-call API request ($0.01)',
});
if (!check.authorized) return check.response; // 402 with payTo link
const result = { message: 'Paid request successful!' };
await gateway.claim(check, {
amount: 10_000,
description: 'Pay-per-call API request',
});
return Response.json(result);
}Dynamic payment (budget + actual cost)
Request a budget upfront, bill actual cost after processing:
// app/api/bots/dynamic-payment/route.ts
export async function POST(req: Request) {
const check = await gateway.requireBudget(req, {
estimated: 5_000_000,
description: 'AI inference — estimated cost ($5.00)',
});
if (!check.authorized) return check.response; // 402
const actualCost = await doExpensiveWork();
await gateway.claim(check, {
amount: actualCost,
description: `AI inference — actual cost ($${(actualCost / 1_000_000).toFixed(6)})`,
});
return Response.json({ message: 'Done!', charged: actualCost });
}Payment flow summary
- Caller hits endpoint
requirePayment()checks for an approved payment via the facilitator- If not approved: returns
402withPAYMENT-REQUIREDheader and a JSON body containing apayTolink to a/humans/{flowId}multi-step approval flow - Human visits the
payTolink, completes login + top-up + approval - Caller retries the same request (no special headers needed)
requirePayment()finds the approved payment, returnsauthorized: true- After processing,
claim()settles the payment via the facilitator
Client Components
All hooks and components from @botparty/react are re-exported:
import {
useAuth, useUser, useWallet, useLedger,
SignedIn, SignedOut, HasLinkedUser,
UserButton, NamespaceBadge,
WalletBalance, TransactionList, TopUpButton, SpendingControls,
} from '@botparty/nextjs';Example Page
import { SignedIn, SignedOut, UserButton, WalletBalance } from '@botparty/nextjs';
export default function Dashboard() {
return (
<div>
<SignedIn>
<UserButton />
<WalletBalance />
</SignedIn>
<SignedOut>
<a href="/api/botparty/auth/login">Sign in</a>
</SignedOut>
</div>
);
}Session Strategy
- Fast path: Encrypted cookies (
__botparty_session15min,__botparty_refresh30d) encrypted with key derived fromBOTPARTY_CLIENT_ASSERTION_KEY - Bearer tokens: ES256 access tokens verified against
id.botparty.club/.well-known/jwks.json - Namespace JWTs: Verified against per-namespace JWKS endpoints
- Silent refresh: Middleware auto-refreshes expired session cookies from the refresh token
Demo Project
When you run npx @botparty/nextjs init in an empty directory, it scaffolds a complete demo with:
- Landing page (
/) -- sign in button, feature cards - Dashboard (
/dashboard) -- protected route showing all SDK components:<UserButton>,<NamespaceBadge>,<WalletBalance>,<TransactionList><TopUpButton>,<SpendingControls>- API tester panel with buttons to call each sample route
- Terminal commands for testing with
botparty curl
- Sample API routes:
GET /api/bots/open-- accepts any authenticated botGET /api/bots/user-required-- requires namespace linked to human accountPOST /api/bots/pay-per-call-- $0.10/call with payment authorizationPOST /api/bots/dynamic-payment-- $5 authorization, $0.20/call
Exports
| Path | Contents |
|------------------------------|---------------------------------------------------------------|
| @botparty/nextjs | BotPartyProvider, all React hooks + components |
| @botparty/nextjs/server | auth(), currentUser(), botpartyMiddleware, createRouteMatcher, session utils |
| @botparty/nextjs/handlers | GET, POST catch-all route handlers |
