ResiResi — try the whole thing
A complete, runnable demo driven by one script. You play a reservation platform whose restaurant tenants need API keys and staff groups. Put APIblaze in front of the backend, drop in two widgets, write one plain-English rule — and watch John lose the ability to open Maria's reservation while his own still works.
What it is
One fictional company, so every APIblaze idea has somewhere concrete to land.
ResiResi is a restaurant-reservation platform. Its backend is open — no keys, no roles — on purpose: access control is the gateway's job, not the origin's. It has two tenants, Nino's Pizza and Gino's Pizza, whose software engineers self-serve API keys and manage users & groups from ResiResi's Developers page.
What you'll set up, in plain terms:
- Start ResiResi's local backend on your computer.
- Create an APIblaze proxy from its OpenAPI spec — a checkpoint every request passes through — and tunnel it back to your machine.
- Add logins, API keys, and staff groups to ResiResi's app using APIblaze's ready-made widgets.
- Prove it works: John could open Maria's reservation before — after one rule, he can't (but his own still works, and staff see everything).
Everything runs on your laptop. You'll need Node 20+ and a free APIblaze account (one browser login, for the step that gives your backend a public address).
1 · Clone the repo and run the lab
One script explains each step, prints the exact command it's about to run, waits for Enter, runs it, and shows the result.
git clone https://github.com/jayjaychicago/rr
cd rr
./start.shWindows: .\start.ps1 (if blocked: powershell -ExecutionPolicy Bypass -File .\start.ps1).
- Every step shows its command before you press Enter — nothing runs blind.
- Crash or Ctrl-C? Re-run
./start.shand choose [r]esume — finished steps are skipped, and the tunnel self-heals if it drops. - Names are minted per run (
resiresi0000for the proxy,nino0000for the tenant), so two people doing the lab never collide.
That's the whole demo. The sections below are the same sequence the script drives, written out — for reading along, or doing it by hand.
Create the APIblaze proxy
The same sequence the script drives, by hand. All calls use the dev environment — the gateway captures dev traffic as samples the AI agents learn from.
Prereqs (the lab's first steps): the local backend running — cd rr/resiresi-backend-lightweight && node server.js — and npx apiblaze login.
Create the APIblaze proxy from the resiresi OpenAPI spec. This creates resiresi0000 — the public address that will tunnel into your local backend. It's created from the spec, so the gateway knows the routes from day one (the AI agents use them later). --identified makes each request say which person it's for; --iam turns on users & groups. Tenant names are globally unique, so mint a per-run tenant:
npx apiblaze create --name resiresi0000 --openapi resiresi-backend-lightweight/openapi.yaml --tenant nino0000 --auth api_key --identified --iamNote from the output: the dev API key (call it DPKEY) and the tenant it created.
Connect your backend to the gateway — your backend lives on your laptop; the gateway lives in the cloud. Keep this running:
npx apiblaze dev 8080 --project resiresi0000Prove the proxy reaches your machine — the exact call a storefront makes: the API key says which app; X-End-User-Id says which person:
curl "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations" \
-H "X-API-Key: $DPKEY" \
-H "X-End-User-Id: john@nino.com"Widgets & tenant admin
ResiResi's Developers page is where Nino's and Gino's engineers self-serve API keys and manage users & groups. Its backend needs one limited key.
Mint the key that powers the Developers page — a limited key that can only do those admin actions (issue keys, manage users & groups), not a full-control key. Shown once:
npx apiblaze apikeys mint --desc "resiresi widget key"Install ResiResi's web app + drop in the key (the key stays on the server, never in the browser):
cd rr/resiresi-frontend
npm install
npm install apiblazeAPIBLAZE_CP_KEY=<the key you minted>Add the two widgets. This edits only files inside rr/resiresi-frontend/. Create file 1 of 3 — maps the signed-in restaurant + email to the identity APIblaze authorizes on (the tenant is the restaurant slug + your run's suffix):
import type { AppUser } from "apiblaze/server";
import { getTenantSlug } from "./tenant";
import { getUser } from "./user";
const TENANT_SUFFIX = "0000"; // your run's suffix — nino becomes nino0000
export function getApiblazeUser(): AppUser | null {
const slug = getTenantSlug();
const user = getUser();
if (!slug || !user) return null;
return { tenant: slug + TENANT_SUFFIX, userId: user.email, email: user.email };
}Create file 2 of 3 — the API-key route (mkdir -p app/api/apiblaze/keys from inside rr/resiresi-frontend):
import { NextResponse } from "next/server";
import { createApiblazeKeys } from "apiblaze/server";
import { getApiblazeUser } from "@/lib/apiblaze-user";
let h: ReturnType<typeof createApiblazeKeys> | null = null;
function handler(req: Request) {
const cpKey = process.env.APIBLAZE_CP_KEY;
if (!cpKey) return NextResponse.json({ error: "APIBLAZE_CP_KEY not set" }, { status: 503 });
h ??= createApiblazeKeys({ cpKey, getUser: () => getApiblazeUser() });
return h.handler(req);
}
export const GET = handler;
export const POST = handler;Create file 3 of 3 — the users-and-groups route (mkdir -p app/api/apiblaze/groups). Identical shape, different factory:
import { NextResponse } from "next/server";
import { createApiblazeGroups } from "apiblaze/server";
import { getApiblazeUser } from "@/lib/apiblaze-user";
let h: ReturnType<typeof createApiblazeGroups> | null = null;
function handler(req: Request) {
const cpKey = process.env.APIBLAZE_CP_KEY;
if (!cpKey) return NextResponse.json({ error: "APIBLAZE_CP_KEY not set" }, { status: 503 });
h ??= createApiblazeGroups({ cpKey, getUser: () => getApiblazeUser() });
return h.handler(req);
}
export const GET = handler;
export const POST = handler;Edit one file — open rr/resiresi-frontend/app/developers/page.tsx. Add this import at the top:
import { ApiKeyWidget, UsersGroupsWidget } from "apiblaze/react";Then replace the two <Placeholder>…</Placeholder> blocks (one under “API access”, one under “Users & Groups”) with:
<ApiKeyWidget title="API keys" theme={{ accent: "#4f46e5" }} /><UsersGroupsWidget theme={{ accent: "#4f46e5" }} />Make yourself the tenant admin and sign in — name the first admin before signing in, so the widgets are ready the moment you land:
npx apiblaze admins add owner@nino.com --tenant nino0000Start the app (npm run dev — serves http://localhost:3003), open http://localhost:3003/developers, and sign in with any name and the email owner@nino.com. Both widgets appear, ready to use.
Put staff in a group — a group reservationists with maria@nino.com in it (she's staff; John stays just a diner). In the widget: + New user → maria@nino.com, + New group → reservationists, add maria — or from the terminal (unknown emails are created on the spot):
npx apiblaze group create reservationists --admin owner@nino.com --tenant nino0000
npx apiblaze group add-user maria@nino.com reservationists --tenant nino0000Prove access control
BEFORE: John opens Maria's reservation. One plain-English rule later, he can't — but his own booking still works, and staff see everything.
BEFORE — find one of Maria's reservations (any id whose diner_external_id is maria's), then open it as John:
curl "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations/<MARIA_ID>" \
-H "X-API-Key: $DPKEY" -H "X-End-User-Id: john@nino.com"Write the access rules — one command, plain English. The AI designs the model and rules from your routes and traffic, saves them, and turns enforcement on:
npx apiblaze rule "Bookings belong to whoever makes them. A reservation may be opened by its owner or by members of the existing group \"reservationists\". The full reservations list is for \"reservationists\" only. Leave every other route open." resiresi0000 --enforceWant to iterate on it conversationally instead? npx apiblaze agent authz resiresi0000 opens the same designer as a chat.
AFTER — same app key everywhere; the person now decides the result:
curl -X POST "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations" \
-H "X-API-Key: $DPKEY" -H "X-End-User-Id: john@nino.com" -H "Content-Type: application/json" \
-d '{"diner_name":"John Diner","diner_external_id":"john@nino.com","party_size":2,"starts_at":"2026-08-01T19:00:00Z"}'curl "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations/<JOHN_ID>" \
-H "X-API-Key: $DPKEY" -H "X-End-User-Id: john@nino.com"curl "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations/<MARIA_ID>" \
-H "X-API-Key: $DPKEY" -H "X-End-User-Id: john@nino.com"curl "https://resiresi0000.abz.run/1.0.0/dev/v1/restaurants/nino/reservations/<JOHN_ID>" \
-H "X-API-Key: $DPKEY" -H "X-End-User-Id: maria@nino.com"One key, many people: owners see their own, staff see all.