Reference
Next.js
App Router patterns: Server Actions, Route Handlers, per-request identity.
The SDK does not ship a Next.js plugin. The recommended pattern uses the App Router’s request-scoped helpers — cookies(), headers(), or whatever the application uses to extract the calling user’s JWT — and passes the JWT to the SDK through a per-request constructor or the per-call userToken option.
This page assumes the App Router. The same patterns apply to the Pages Router, but per-request identity is more awkward there because there is no async context primitive built into the framework.
Create a server-only client
Section titled “Create a server-only client”Keep client construction in a server-only module. The examples create one client per handler and close it in finally, which works consistently across long-lived servers and request-isolated deployments.
import { App } from "@alter-ai/alter-sdk";import "server-only";
export function createAlterApp(): App { return new App({ apiKey: process.env.ALTER_API_KEY! });}The "server-only" import prevents the module from being bundled into client code. Without it, a stray client-side import would expose the API key reference to the browser build and fail at runtime because the SDK uses Node-only APIs.
Per-request identity
Section titled “Per-request identity”For identity-mode request() calls, resolve the calling user’s JWT inside the request handler and pass it as a per-call option:
import { NextRequest, NextResponse } from "next/server";import { HttpMethod } from "@alter-ai/alter-sdk";import { createAlterApp } from "@/app/lib/alter";import { getUserJwt } from "@/app/lib/auth";
export async function GET(req: NextRequest) { const userJwt = await getUserJwt(); if (!userJwt) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
const alter = createAlterApp(); try { const response = await alter.request( HttpMethod.GET, "https://api.example.com/v1/resources", { provider: "<provider-id>", userToken: userJwt, reason: `Resource fetch for ${req.nextUrl.searchParams.get("date")}`, }, ); return NextResponse.json(await response.json(), { status: response.status }); } finally { await alter.close(); }}userToken on the per-call options overrides any constructor userTokenGetter. The constructor pattern works too — wire userTokenGetter to cookies() / headers() and let the SDK pull the JWT on demand — but the per-call form makes the data flow explicit at every call site.
Server Actions
Section titled “Server Actions”"use server";
import { HttpMethod } from "@alter-ai/alter-sdk";import { createAlterApp } from "@/app/lib/alter";import { getUserJwt } from "@/app/lib/auth";
export async function listResources() { const userJwt = await getUserJwt(); if (!userJwt) throw new Error("unauthorized");
const alter = createAlterApp(); try { const response = await alter.request( HttpMethod.GET, "https://api.example.com/v1/resources", { provider: "<provider-id>", userToken: userJwt }, ); return await response.json(); } finally { await alter.close(); }}Server Actions run on every form post or useTransition callback. Treat them exactly like a Route Handler — resolve the JWT first, pass it to the SDK.
Per-request Agent
Section titled “Per-request Agent”When a request maps to one agent run (an LLM call, a tool invocation), construct the Agent inside the handler and close it in finally:
import { Agent } from "@alter-ai/alter-sdk";
export async function POST(req: NextRequest) { const userJwt = await getUserJwt(); if (!userJwt) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
const agent = new Agent({ apiKey: process.env.AGENT_API_KEY!, userTokenGetter: () => userJwt, });
try { return await agent.trace({ runId: crypto.randomUUID() }, async () => { const response = await agent.request(/* … */); return NextResponse.json(await response.json()); }); } finally { await agent.close(); }}The try/finally ensures agent.close() runs when the block exits, even on thrown errors.
Connect flow
Section titled “Connect flow”Mint a Connect session in a Server Action or Route Handler, redirect the user’s browser to connectUrl, then poll on the callback. The session token is short-lived; thread it through the redirect URL or a server-side store.
import { NextRequest, NextResponse } from "next/server";import { createAlterApp } from "@/app/lib/alter";import { getUserJwt } from "@/app/lib/auth";
export async function GET(req: NextRequest) { const userJwt = await getUserJwt(); if (!userJwt) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
const alter = createAlterApp(); try { const session = await alter.createConnectSession({ allowedProviders: [req.nextUrl.searchParams.get("provider")!], returnUrl: `${req.nextUrl.origin}/connect/callback`, userToken: userJwt, }); return NextResponse.redirect(session.connectUrl); } finally { await alter.close(); }}import { NextRequest, NextResponse } from "next/server";import { createAlterApp } from "@/app/lib/alter";
export async function GET(req: NextRequest) { const sessionToken = req.nextUrl.searchParams.get("session"); if (!sessionToken) return NextResponse.json({ error: "missing_session" }, { status: 400 });
const alter = createAlterApp(); try { const results = await alter.pollConnectSession(sessionToken, { timeoutMs: 2_000 }); return NextResponse.json({ grants: results.map((r) => r.grantId) }); } finally { await alter.close(); }}In production, drive the callback through a postMessage from a popup, or use Next.js streaming + Server-Sent Events to push the completion event back to the client. Polling on every callback request is fine for small deployments but wastes a round-trip when the user finishes consent quickly.
Runtime
Section titled “Runtime”The SDK requires Node.js 20+ and imports Node-only APIs for cryptography and asynchronous context. Use the Next.js Node.js runtime, not the Edge runtime.
export const runtime = "nodejs";