Tawa docs
Cookbook

Next.js Route Handler

Wire a scan into an App Router API route before forwarding to your model.

A minimal pattern for an App Router Route Handler that screens the request body before doing anything else with it.

app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";
import { TawaGuard } from "@tawaguard/sdk";
import OpenAI from "openai";

// Construct once per server instance, not per request.
const tawa = await TawaGuard.create(process.env.TAWA_API_KEY!);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const BLOCKED_ACTIONS = new Set(["block", "hard_block", "soft_block"]);

export async function POST(req: NextRequest) {
  const { message } = await req.json();

  const scan = await tawa.analyze(message, {
    // Attribute the scan to the signed-in user for per-user tracking.
    cuid: req.headers.get("x-user-id") ?? undefined,
  });

  if (BLOCKED_ACTIONS.has(scan.action)) {
    return NextResponse.json(
      { error: "Message blocked by content policy.", rationale: scan.rationale },
      { status: 403 },
    );
  }

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: message }],
  });

  return NextResponse.json({ reply: completion.choices[0].message.content });
}

Streaming responses

If you're streaming the model's reply with the Vercel AI SDK's streamText, use the Vercel AI SDK integration instead of wiring analyze() in by hand — it wraps the model once and handles the pre-generation scan for every call automatically, including the caveat that a blocked stream surfaces as an error part rather than an HTTP-level rejection.

Constructing the client once

TawaGuard.create does a network round trip (authentication), so build the client at module scope — as above — rather than inside the handler. Route Handlers in a serverless environment may still cold-start per invocation, but this avoids paying the authentication round trip on every warm request.

On this page