Brinpage Platform — Quick Start (Next.js App Router)
Integrate Brinpage Platform in a Next.js App Router project. You can either scaffold the setup automatically (recommended) or do it manually with copy-paste.
Choose your setup
Requirements
- Node 18+ (20+ recommended)
- Next.js App Router (
app/orsrc/app/) - A Brinpage Platform account + license key
1) Create a Next.js project
npx create-next-app@latest
# Name: brinpage-quickstart
# Accept defaults
cd brinpage-quickstartYou can use an existing App Router project — steps are identical.
2A) Setup with the CLI (recommended)
Run the initializer from your Next.js project root. It will scaffold the required files for you.
npm i @brinpage/quickstart
npx brinpage-quickstart initThen open .env.local and set your key:
BRINPAGE_LICENSE_KEY="bp_xxxx"The demo page will be available at /brinpage.
2B) Setup manually (copy-paste)
Step 1: Add environment variables
Create .env.local in your Next.js project root.
# Required (server-side only)
BRINPAGE_LICENSE_KEY="bp_xxxx"
# Optional
BRINPAGE_API_BASE="https://platform.brinpage.com"
BRINPAGE_ASK_TIMEOUT_MS=30000Don’t have a license yet? Create a Platform account and generate your key.
Get your API keyImportant: never expose BRINPAGE_LICENSE_KEY to the browser. Keep it server-side (API routes / server actions only).
Step 2: Add the Brinpage HTTP client
Create file: lib/brinpage.ts (project root → ./lib/brinpage.ts) — full file below.
// lib/brinpage.ts
export type ChatMessage = {
role: "system" | "user" | "assistant";
content: string;
};
const API_BASE = process.env.BRINPAGE_API_BASE || "https://platform.brinpage.com";
const LICENSE = process.env.BRINPAGE_LICENSE_KEY || "";
export type AskOptions = {
question?: string;
messages?: ChatMessage[];
provider?: string;
model?: string;
context?: Record<string, unknown>;
extraPrompts?: string[];
tags?: string[];
debugEcho?: boolean;
};
export type AskResult = {
text: string;
raw: unknown;
};
function toQuestion(messages?: ChatMessage[]) {
if (!messages?.length) return "";
for (let i = messages.length - 1; i >= 0; i--) {
const m = messages[i];
if (m.role === "user" && m.content?.trim()) return m.content.trim();
}
return messages.map((m) => `${m.role}: ${m.content}`).join("\n").trim();
}
function safeJsonParse(rawText: string): unknown {
if (!rawText) return null;
try {
return JSON.parse(rawText) as unknown;
} catch {
return null;
}
}
function pickText(data: unknown): string {
if (data && typeof data === "object") {
const obj = data as Record<string, unknown>;
const candidates = [obj.text, obj.answer, obj.message, obj.output];
for (const c of candidates) {
if (typeof c === "string" && c.trim()) return c;
if (typeof c === "number" || typeof c === "boolean") return String(c);
}
}
return "";
}
function pickErrorMessage(data: unknown, rawText: string, status: number): string {
if (data && typeof data === "object") {
const obj = data as Record<string, unknown>;
const err = obj.error;
const msg = obj.message;
if (typeof err === "string" && err.trim()) return err.trim();
if (typeof msg === "string" && msg.trim()) return msg.trim();
}
if (rawText?.trim()) return rawText.slice(0, 400);
return `ask failed (${status})`;
}
export async function ask(opts: AskOptions): Promise<AskResult> {
if (!LICENSE) throw new Error("Missing BRINPAGE_LICENSE_KEY");
const q = (opts.question ?? toQuestion(opts.messages)).trim();
if (!q) throw new Error("Missing 'question' input");
const url = `${API_BASE}/api/sdk/ask`;
const timeoutMs = Number(process.env.BRINPAGE_ASK_TIMEOUT_MS ?? 30000);
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), timeoutMs);
let res: Response;
try {
res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
authorization: `Bearer ${LICENSE}`,
},
body: JSON.stringify({
question: q,
history: Array.isArray(opts.messages) ? opts.messages : [],
provider: opts.provider,
model: opts.model,
stream: false,
context: opts.context ?? {},
extraPrompts: opts.extraPrompts,
tags: opts.tags,
debugEcho: opts.debugEcho,
}),
cache: "no-store",
signal: controller.signal,
});
} catch (e: unknown) {
const msg =
e instanceof Error
? e.name === "AbortError"
? `ask timeout after ${timeoutMs}ms`
: e.message
: "ask failed (network error)";
throw new Error(msg);
} finally {
clearTimeout(t);
}
const rawText = await res.text();
const data = safeJsonParse(rawText);
if (!res.ok) {
throw new Error(pickErrorMessage(data, rawText, res.status));
}
return {
text: pickText(data),
raw: data ?? rawText,
};
}Step 3: Add a server route in Next.js
This route keeps your license key server-side and provides a clean endpoint for your UI.
- Create:
app/api/chat/route.ts - If using
src/app/:src/app/api/chat/route.ts
// app/api/chat/route.ts
import { NextResponse } from "next/server";
import { ask, type ChatMessage } from "@/lib/brinpage";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
type ChatBody = {
question?: string;
messages?: ChatMessage[];
provider?: string;
model?: string;
context?: Record<string, unknown>;
tags?: string[];
extraPrompts?: string[];
debugEcho?: boolean;
};
export async function POST(req: Request) {
let body: ChatBody | null = null;
try {
body = (await req.json()) as ChatBody;
} catch {
body = null;
}
if (!body) {
return NextResponse.json({ ok: false, error: "Invalid JSON body" }, { status: 400 });
}
try {
const out = await ask({
question: body.question,
messages: body.messages,
provider: body.provider,
model: body.model,
context: body.context,
tags: body.tags,
extraPrompts: body.extraPrompts,
debugEcho: body.debugEcho,
});
return NextResponse.json({ ok: true, text: out.text, raw: out.raw }, { status: 200 });
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "Unknown error";
return NextResponse.json({ ok: false, error: msg }, { status: 500 });
}
}Step 4: Add a minimal chat UI
Manual option: this demo replaces app/page.tsx (or src/app/page.tsx). If you prefer not to replace your home page, use the CLI option (it creates /brinpage instead).
3) Run & Verify
Start Next.js:
npm run dev
# http://localhost:3000- If you used the CLI option, open
http://localhost:3000/brinpageand send “Hello”. - If you used the manual option, open
http://localhost:3000and send “Hello”. - Test the API directly:bash
curl -s -X POST http://localhost:3000/api/chat \ -H "content-type: application/json" \ -d '{"question":"Hello from curl"}'
Troubleshooting
- 401 / 403 from Platform: verify
BRINPAGE_LICENSE_KEYis set in.env.localand you restartednpm run dev. - “Missing BRINPAGE_LICENSE_KEY”: you created
.env.localbut didn’t add the key or the server wasn’t restarted. - 404 at /api/chat: your route is in the wrong place. It must be
app/api/chat/route.ts(orsrc/app/api/chat/route.ts). - “Unexpected end of JSON input”: the server returned a non-JSON response. Check the Network tab and the Next.js terminal logs to see the underlying error.
- Timeout errors: increase
BRINPAGE_ASK_TIMEOUT_MS(e.g. 60000) in.env.local.
Final project layout
your-project/
app/ (or src/app/)
api/
chat/
route.ts ← created
brinpage/
page.tsx ← created (CLI option)
page.tsx ← only replaced in manual option
lib/
brinpage.ts ← created
.env.local ← created/updated
package.json