Brinpage

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

Option A

CLI (recommended)

Installs a tiny helper and generates the required files for you. You only add your license key.

bash
npm i @brinpage/quickstart
npx brinpage-quickstart init

This creates lib/brinpage.ts, app/api/chat/route.ts (or src/app/...), a demo page at /brinpage, and updates .env.local if needed.

Option B

Manual (copy-paste)

No install. Just add 2 files + environment variables, and optionally a minimal chat UI.

Add .env.local
Create lib/brinpage.ts
Create app/api/chat/route.ts
Add a UI (replace app/page.tsx)

Requirements

  • Node 18+ (20+ recommended)
  • Next.js App Router (app/ or src/app/)
  • A Brinpage Platform account + license key

1) Create a Next.js project

bash
npx create-next-app@latest
# Name: brinpage-quickstart
# Accept defaults
cd brinpage-quickstart

You 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.

bash
npm i @brinpage/quickstart
npx brinpage-quickstart init

Then open .env.local and set your key:

.env
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.

.env
# Required (server-side only)
BRINPAGE_LICENSE_KEY="bp_xxxx"

# Optional
BRINPAGE_API_BASE="https://platform.brinpage.com"
BRINPAGE_ASK_TIMEOUT_MS=30000

Don’t have a license yet? Create a Platform account and generate your key.

Get your API key

Important: 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.

typescript
// 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
typescript
// 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).

Loading code…

3) Run & Verify

Start Next.js:

bash
npm run dev
# http://localhost:3000
  • If you used the CLI option, open http://localhost:3000/brinpage and send “Hello”.
  • If you used the manual option, open http://localhost:3000 and 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_KEY is set in .env.local and you restarted npm run dev.
  • “Missing BRINPAGE_LICENSE_KEY”: you created .env.local but 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 (or src/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

bash
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