Skip to content

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.

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.

app/lib/alter.ts
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.

For identity-mode request() calls, resolve the calling user’s JWT inside the request handler and pass it as a per-call option:

app/api/events/route.ts
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.

app/calendar/actions.ts
"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.

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.

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.

app/connect/start/route.ts
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();
}
}
app/connect/callback/route.ts
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.

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

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.