All docs

The drop-in widgets

Three React components your users see on your site, in your brand — API keys, users & groups, and AI chat with your API. They all follow one pattern: a page with the component, and a server route on your backend that holds the APIblaze credential and answers one question — who is signed in? The browser never holds a secret.

WidgetYour users…Server credential
<ChatWidget/>chat with your API — book, query, act in plain EnglishAPIBLAZE_CHAT_DP_KEY (an API key for your proxy)
<ApiKeyWidget/>mint, rotate & revoke their own API keysAPIBLAZE_CP_KEY (widget key)
<UsersGroupsWidget/>manage their users & nested groups (admins)the SAME APIBLAZE_CP_KEY

Everything below is copy-paste; the values you supply are «highlighted». One npm install apiblaze ships all three.

AI chat with your API — on your own site

A floating chat bubble (or an inline card) where your signed-in users book, query and act on your API by typing a sentence. The AI runs server-side behind your proxy — it can only use your API's published tools, every call is authorized and metered like any other request, and the browser never holds a credential or an AI key.

How will the chat authenticate?One key on your server pays and calls for everyone — simplest setup.

First, one environment variable

The chat widget calls your API as a consumer, so it needs a plain API key for your proxy — the same kind your own users mint. (The keys & groups widgets below use a different, producer-side widget key.) npx apiblaze create does not print one — callers sign in by default — so mint it: npx apiblaze apikeys mint --tenant «acme» (no --for: the widget tells the proxy who is chatting), or grab one from your <ApiKeyWidget/> page or your proxy's hosted dev portal. Put it in .env:

APIBLAZE_CHAT_DP_KEY=sk_prod_…

This key also decides the tenant. Every APIblaze key is minted inside a tenant (a customer workspace), and the chat endpoint derives the tenant from the key — never from a URL — so the whole conversation (identity, groups, authorization rules, billing) runs in that tenant's world. Mint the key in the tenant you want the chat to live in.

How the pieces fit — two files

  1. 1The component <ChatWidget/> renders the chat (a bottom-right bubble by default). It streams every reply live — including “Checking availability…” progress lines while the AI calls your API — from one URL on your site.
  2. 2The server route answers it. createApiblazeChat(...) returns { handler } you export as POST. It holds the proxy key, asks your getUser who is chatting, and pipes the answer stream straight through.

getUser here is even smaller than the other widgets' — just { userId }. Return null when nobody is signed in and the widget simply isn't offered chat on your dime.

Step 1 — the server route. Pick your login, as before:

app/api/apiblaze/chat/route.ts — NextAuth / Auth.js v5
import { createApiblazeChat } from 'apiblaze/server';
import { auth } from '@/auth';

const chat = createApiblazeChat({
  project: 'acme',                             // ① your proxy's name
  apiKey: process.env.APIBLAZE_CHAT_DP_KEY!,   // ② an API key for that proxy
  getUser: async () => {
    const session = await auth();
    if (!session?.user) return null;           // signed out → no chat
    return { userId: session.user.id };        // ③ which person is chatting
  },
});

export const POST = chat.handler;

Step 2 — the page. Mount it once (e.g. in your layout) and the bubble follows your users everywhere:

app/chat/page.tsx — or your root layout
import { ChatWidget } from 'apiblaze/react';

export default function Page() {
  return (
    <ChatWidget
      endpoint="/api/apiblaze/chat"
      title="Chat with Acme" avatar="⚡"
      welcome="Hi! Ask me anything about your account."
      suggestions={['What can you do?', 'Show my recent orders']}
      storageKey={`apiblaze-chat:acme:${user.id}`}   // per-user transcript (shared machines!)
      theme={{ accent: '#7C3AED' }}
    />
  );
}

The props you'll actually use

mode"bubble" (floating launcher, the default) or "inline" (a card in your layout).
title · avatar · welcomeThe chrome: header text, emoji, and the first message shown before any exchange.
suggestionsUp to 4 tappable prompt chips shown on an empty chat — seed the questions you want asked.
storageKeyWhere the transcript lives (sessionStorage — survives page hops, dies with the tab). Make it per-user whenever your page has logins, or the next person at a shared machine can read the previous chat.
themeFull white-label: accent, bubbles, launcher, radius, fonts — same philosophy as the other widgets.

Steer the assistant — from the dashboard, not from code

Dashboard → your proxy → LLMSteer the assistant: a short note like “always suggest the daily special”. It's injected server-side beneath the assistant's safety rules (tools only, no invented endpoints), and applies to every chat surface on the proxy — this widget, the dev portal's Chat tab, and npx apiblaze apichat.

Who pays, and the honest limits

  • You fund the AI by default, with guardrails you set on the same LLM tab: per-person and per-day spending caps, chats per person per day, and a proxy-wide daily ceiling. When a limit is hit the widget shows a friendly “come back tomorrow” — never an error.
  • With an API key, one relay key = one shared allowance and one chat at a time across your site — ideal for a demo or a low-traffic page. For per-person allowances, per-person permissions and real concurrency, flip the selector above to Auth with login (OAuth): each user chats as themselves.
  • Building your own chat UI? You don't need the widget: the chat endpoint speaks the standard Vercel AI SDK stream protocol, so useChat, AI Elements and assistant-ui work against it directly.

“Get an API key” on your own site

The same two-file pattern: your signed-in users mint, rotate and revoke their own API keys — on your page, in your brand. The browser talks only to your backend; your backend holds the APIblaze credential. You write exactly three values — everything else is paste-as-is.

First, one environment variable

APIblaze Dashboard → Developers → create a Widget key (not a full admin key), then put it in .env:

APIBLAZE_CP_KEY=sk_prod_…

The same key powers both widgets — you never need a second one.

How the pieces fit — two files

  1. 1The component <ApiKeyWidget/> renders the keys UI. When it needs data it makes one fetch() to a URL on your site (default /api/apiblaze/keys) — it never sees your APIblaze key and never calls APIblaze directly.
  2. 2The server route answers that fetch. createApiblazeKeys(...) returns { handler } — a normal request handler you export as GET/POST. It holds your secret cpKey and calls the one function you write — getUser — to learn who is asking, then talks to APIblaze server-to-server on their behalf (listing / minting / revoking only that user's keys).

The widget finds your route by its URL, not by any variable name — so const apiblazeKeys = … below is just your local name (call it whatever you like). Moving the route? Set the widget's endpoint prop to the new path. getUser is the only code you actually write.

Step 1 — the server route. Pick whatever you already use to log people in:

app/api/apiblaze/keys/route.ts — NextAuth / Auth.js v5
import { createApiblazeKeys } from 'apiblaze/server';
import { auth } from '@/auth';                 // your NextAuth config file

const apiblazeKeys = createApiblazeKeys({
  cpKey: process.env.APIBLAZE_CP_KEY!,
  getUser: async () => {
    const session = await auth();
    if (!session?.user) return null;           // not signed in → widget asks them to

    return {
      tenant: session.user.companyId,          // ① your org/team id — or session.user.id
      userId: session.user.id,                 // ②
      email:  session.user.email ?? undefined, // ③
    };
  },
});

export const GET = apiblazeKeys.handler;
export const POST = apiblazeKeys.handler;

Step 2 — the page. Identical for everyone, nothing to fill in:

app/keys/page.tsx — paste as-is
import { ApiKeyWidget } from 'apiblaze/react';

export default function Page() {
  return <ApiKeyWidget theme={{ accent: '#7C3AED' }} />;
}

The only three values you supply

① tenantWhich of your customers this user belongs to. It is the wall between customers — two tenants never see each other's keys, users or groups.
Use your company / team / workspace / organisation id. No such concept in your app? Pass the user's own id — then every user simply gets their own private space.
② userIdWhich person is signed in. They own the keys they create.
Your user's primary key. Must be stable, and never reused for a different human.
③ emailOptional, but recommended — it puts a real identity on this user. Sent once (never on every call) to link userId ↔ email, so the same person is recognised across surfaces — e.g. when they later sign into your API's dev portal with that email, or when an admin adds them by email.
It's also the hook for admin rights: an email on the tenant's admin allowlist becomes a tenant admin in the Users & Groups widget. Omit it and the user still works — they're just identified only by userId.

Where those come from, whatever you use to log people in

Your logintenantuserId
NextAuthsession.user.organizationIdsession.user.id
ClerkorgId (from auth())userId
Auth0 / WorkOSorg_id claimsub
Your own databasethe account / company row idthe user row id
No teams at allthe same value as userIdyour user id

“Organisation”, “org”, “workspace”, “team”, “account” and “company” all mean the same thing here: the customer you bill. That is your tenant.

  • Eligibility is decided on your server, from your session — never in the browser. Return keyTypes: false from getUser to deny a user API access entirely; a list of two or more types shows a picker.
  • Keys are durable by default (no silent expiry); set keyExpiresInSeconds for expiring, re-revealable keys. Durable secrets are shown once, masked afterwards.
  • Everything is white-label through the theme prop: accent, radius, fonts.

Users & groups on your own site

Literally the same thing again with one word changed: same widget key, same getUser, one more route. Your customers' admins then manage their own users and (nested) groups from your page — and group membership drives authorization at the proxy.

It's your keys route with one word changed. Same two files (route + page), same getUser, same APIBLAZE_CP_KEY — only createApiblazeKeyscreateApiblazeGroups. Compare the tab below to your keys route: identical but the one word. (Running both widgets and don't want to write getUser twice? Move it to a shared file and import it in both — optional.)
app/api/apiblaze/groups/route.ts — your keys route, one word changed
import { createApiblazeGroups } from 'apiblaze/server';   // ← the only change vs. the keys route
import { auth } from '@/auth';

const apiblazeGroups = createApiblazeGroups({
  cpKey: process.env.APIBLAZE_CP_KEY!,                   // the SAME widget key
  getUser: async () => {                                 // the SAME getUser
    const session = await auth();
    if (!session?.user) return null;
    return {
      tenant: session.user.companyId,                    // ① which CUSTOMER
      userId: session.user.id,                           // ② which PERSON
      email:  session.user.email ?? undefined,           // ③ links identity (dev-portal, admin rights)
    };
  },
});

export const GET = apiblazeGroups.handler;
export const POST = apiblazeGroups.handler;
  • Groups nest, and rules walk the whole tree (OpenFGA underneath) — maria ∈ reservationists ⊂ admin passes an admin check.
  • Identities seen in real traffic surface in the widget so admins can pull them into groups.
  • Resolved membership is forwarded to your backend as abz.groups on every request.
  • To enforce access from group membership, add a rule — see Authorization rules.