Framework guide

RFQ for Next.js App Router

Render the widget from a Client Component. Mint authorToken in a Route Handler. Ignore authorId, permissions, and supplierId if the browser sends them. Copy those fields from the signed-in user. Secret stays in BSW_RFQ_SECRET, never NEXT_PUBLIC_*. The same route ships in the package AGENTS.md.

Install

pnpm add @bootstrapware/rfq

Example app: pnpm --filter @bootstrapware/rfq-example dev on port 3015. Buyer and supplier routes live under /buyer and /supplier.

Client island

"use client";

import { RfqRequester, createHostedAdapter } from "@bootstrapware/rfq";
import "@bootstrapware/rfq/styles.css";

export function BuyerRfq({
  actor,
  authorToken,
  renewAuthorToken,
}: {
  actor: { id: string; permissions: string[] };
  authorToken: string;
  renewAuthorToken: () => Promise<string>;
}) {
  const adapter = createHostedAdapter({
    appId: process.env.NEXT_PUBLIC_RFQ_APP_ID!,
    publishableKey: process.env.NEXT_PUBLIC_BSW_RFQ_PUBLISHABLE_KEY!,
    scope: { tenantKey: "acme" },
    authorToken,
  });
  return (
    <RfqRequester
      scope={{ appId: process.env.NEXT_PUBLIC_RFQ_APP_ID!, tenantKey: "acme" }}
      actor={actor}
      adapter={adapter}
      authorToken={authorToken}
      renewAuthorToken={renewAuthorToken}
    />
  );
}

Server-only token route

// app/api/rfq/author-token/route.ts
import { NextResponse } from "next/server";
import { getSessionUser, rfqAccess } from "@/lib/session";

export async function POST(request: Request) {
  const user = await getSessionUser();
  if (!user) return NextResponse.json({ error: "Sign in required." }, { status: 401 });
  const body = await request.json();
  const access = await rfqAccess(user.id, String(body.tenantKey ?? ""));
  if (!access) return NextResponse.json({ error: "The requested resource was not found." }, { status: 404 });
  const response = await fetch("https://rfq.bootstrapware.co/api/v1/author-tokens", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BSW_RFQ_SECRET}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      authorId: user.id,
      appId: process.env.RFQ_APP_ID,
      tenantKey: access.tenantKey,
      permissions: access.permissions,
      supplierId: access.supplierId,
      expiresInSec: 300,
    }),
  });
  return NextResponse.json(await response.json(), { status: response.status });
}

The example route at examples/rfq-nextjs/app/api/rfq/author-token/route.ts always returns 501 until you wire the signed-in session, rfqAccess, and BSW_RFQ_SECRET. Setting the secret alone is not enough. Do not mint tokens in the browser.

Related: Identity · Persisted BYO · Files