# Add login, teams and paid plans to a Replit Agent app, and keep Replit's database for your data

> A Replit project is a real Linux workspace with a shell, so the SDK installs like anywhere else. The three sign-in routes go on the Express server Agent already wrote; we ran them on 27 October 2026.

- Source: https://www.buildbase.app/guides/add-login-teams-and-paid-plans-to-a-replit-app
- Section: Guides
- Author: Dharmendra Jagodana
- Published: 2026-10-28

## In short

To add login, teams and paid plans to a Replit Agent app, run npx buildbase init, mount the three sign-in handlers on the Express server through a 26-line adapter tested on 27 October 2026, put the secret in Secrets and in the published app, and keep your data in Replit Database. Teams and plans are SDK calls.

You are adding real sign-in, a workspace per customer, paid plans and the
email that goes out when a card fails to an app Replit's Agent built for
you, without moving your data out of Replit Database. When it works, a
stranger opens your published address in a private window, signs up with
Google or a magic link, lands in their own workspace, invites a colleague
who gets an email, picks a plan on a test card, and the feature they paid
for appears without a reload. The client secret lives in Secrets and never
reaches the browser.

Replit is the easiest of the builders to do this on, for one reason: a
Replit project is a real Linux workspace with a shell. `npm install`
installs, `npx` runs, and the server Agent wrote is a Node process you can
read. There is no "where does the server live" question, which is most of
the Bolt and Lovable guides. The question here is different: Replit does
not document which stack Agent generates, so step 1 is finding out, and
the rest of the guide is written for the layout Replit's own pages
describe indirectly, a React front end on an Express server with
PostgreSQL behind it.

What we ran on 27 October 2026: the three sign-in handlers from
[BuildBase on Vite](/guides/buildbase-on-vite), unchanged, mounted on
Express 4.22.3 through a 26-line adapter, with `@buildbase/sdk` 0.0.90
installed from npm, Node 22, the API routes ahead of a stand-in for the
Vite middleware, on port 5000 as Replit's template expects. Each route
answered as the guide says it should: the session route returned
`{"sessionId":null}` with no cookie and `{"sessionId":"abc123"}` with one,
the verify route returned 400 with no code and 401 with a bogus one (our
platform rejected it, as it should), and sign-out set the cookie with
`Max-Age=0`. The same results held with `express.json()` applied globally
before the routes, which is how Agent's generated server does it, and
`tsc` reported no errors. What we have not run: anything inside Replit
itself (the Shell, Secrets, Publishing), `npx buildbase init` in a Replit
project, the ESM layout Replit's template uses, or the hosted sign-in
round trip, which needs a registered address. Where a Replit behaviour
decides whether a step works, this guide quotes Replit's page and says so.

> **Before you start:** A Replit project built by Agent, on any plan (Starter publishes one app, which
> Replit's page says goes down after 30 days). A BuildBase organization, and an
> auth client created in the console under **User Management → Authentication**,
> with its client ID and secret. Your published address, registered on that
> client as a redirect URL ending in `/sign-in`. A Stripe account, only when you
> reach plans.

> **Tip:** The handlers in this guide are the Vite guide's, byte for byte, and the
> server-side pattern is the [Express guide](/guides/buildbase-on-express)'s.
> This page is the Replit wrapping around them: the Shell, Secrets, the adapter,
> and the prompts that keep Agent on the rails.

## 1. Find out what Agent built

Open `package.json` from the file tree and read the dependencies. Then
open `replit.md`, which Agent writes at the root of every project and
reads before every request; its first lines usually name the stack.

Replit's docs do not state what Agent generates, but three of its pages
say it sideways. The deployment types page says "Agent builds full-stack
apps that need a backend server, so use Autoscale or Reserved VM for
those". The Clerk Auth page says Agent protects "your API routes" with
"Clerk's Express middleware". And the `replit.md` examples describe "React
frontend, Node.js backend, PostgreSQL database" and an app "built with
React and Express.js". So the layout to expect is one `package.json` with
`react`, `vite`, `express` and a Postgres client, a `client/` folder the
Vite dev server serves, a `server/` folder Express runs, and a `shared/`
folder between them.

Three cases, and what each means for the rest of this page:

- **`express` and `vite` both listed.** This guide applies as written. The
  three routes go on the Express server, and the provider goes in the React
  app.
- **`next` listed.** You have a Next.js app, and `npx buildbase init` in
  the Shell writes all ten files, including the three route handlers and
  the webhook. Follow the [v0 guide](/guides/add-sign-in-teams-and-paid-plans-to-a-v0-app)
  from its step 2 and come back here for Secrets and Publishing.
- **`express` with no `vite`, or a server with HTML templates.** The
  [Express guide](/guides/buildbase-on-express) is the right one: the
  server drives sign-in itself and `req.user` keeps working.

What you should see: a sentence you can say out loud, "my app is React on
Express with Postgres", before you prompt anything.

## 2. Register where people come back to

The hosted sign-in page sends people back to one exact address per
environment, so register them before you prompt: your published
`.replit.app` address with `/sign-in` on the end, your custom domain with
`/sign-in` if you have one, and the preview's `replit.dev` address if you
want to test sign-in before publishing. An address it was not told about
is refused.

The preview address is the one to watch. It is a `replit.dev` address
Replit assigns to the running project, and it is not the published one.
Register it if you want to sign in during building; otherwise register the
published address and test sign-in there. Replit's own sign-in keeps a
list of your app's domains in the `REPLIT_DOMAINS` environment variable
and asks you to republish to refresh it; that list governs Replit Auth,
not this one, and the addresses on the BuildBase client are the only ones
that matter here. [Login that works after you
publish](/guides/login-that-works-after-you-publish) is the troubleshooting
page for either.

What you should see: the auth client in the console lists each address,
and the client secret has been shown once. Do not paste it into the Agent
chat or into `replit.md`.

## 3. Install the skill and run `init` in the Shell

Open the Shell (Tools, then Shell) and run three commands. The first
installs the BuildBase skill, which is the set of rules that keeps Agent
from building its own sign-in next to this one; the second installs the
SDK; the third writes the provider and the environment file.

```bash
npx skills add buildbase-app/claude-skill
npm i @buildbase/sdk
npx buildbase init --org-id <your 24-character org id>
```

Replit's Agent Skills page says installed skills live under
`/.agents/skills/`, that "only a brief description loads until the skill
is invoked", and that skills "installed via CLI or copied from the internet
have not" been audited, so read the skill's `SKILL.md` once yourself; it
is short. If the first command put the skill somewhere else, move the
folder under `.agents/skills/`.

`init` reads `package.json` to tell Next.js, Vite and Express apart. When
it finds both `vite` and `express`, it treats the project as Vite, because
that is the order it checks in, so on this layout it writes the provider
under `src/` (or `client/src/`) and `.env.example` with the variable
names, and prints the Vite guide's address for the three handlers, which
step 4 covers. It never overwrites a file. It leaves every secret empty
and refuses a secret passed on the command line, which is the behaviour
you want in a shared workspace. We have not run it inside a Replit
project; if it reports a path you did not expect, the provider is one file
and step 4's prompt names where it goes.

Then two lines in `replit.md`, so every future request starts from the
same rules:

```text
## Sign-in, teams and billing
BuildBase (@buildbase/sdk) is the only sign-in in this app. Do not add Replit Auth, Clerk or a users table. Workspaces, roles, plans and credits live in BuildBase; app data stays in Replit Database and every query scopes by the workspace id from the BuildBase session. Secrets stay in the Secrets tool, never in code, never with a VITE_ prefix.
```

What you should see: `@buildbase/sdk` in `package.json`, a provider file,
`.env.example`, and the two lines in `replit.md`.

## 4. Mount the three routes on Express

The handlers are the Vite guide's `server/auth.ts`, plain `Request` to
`Response` functions. Express 4 speaks its own request and response
objects, so a small adapter turns one into the other. This is the file we
ran today, as written:

```ts
// server/buildbase-routes.ts
import express, {
  Router,
  type Request as Req,
  type Response as Res,
} from 'express';

import { session, signout, verify } from './auth.js';

type WebHandler = (request: Request) => Response | Promise<Response>;

/** Run a web-standard `Request -> Response` handler as an Express route. */
const adapt = (handler: WebHandler) => async (req: Req, res: Res) => {
  const headers = new Headers();
  for (const [name, value] of Object.entries(req.headers))
    if (value !== undefined)
      headers.set(name, Array.isArray(value) ? value.join(', ') : value);
  const hasBody =
    req.method !== 'GET' && req.method !== 'HEAD' && req.body !== undefined;
  const request = new Request(
    `${req.protocol}://${req.get('host')}${req.originalUrl}`,
    {
      method: req.method,
      headers,
      body: hasBody ? JSON.stringify(req.body) : undefined,
    }
  );
  const response = await handler(request);
  res.status(response.status);
  response.headers.forEach((value, name) => res.append(name, value));
  res.send(Buffer.from(await response.arrayBuffer()));
};

export const authRouter = Router();
authRouter.post('/api/auth/verify', express.json(), adapt(verify));
authRouter.get('/api/auth/session', adapt(session));
authRouter.post('/api/auth/signout', adapt(signout));
```

Two choices in it are deliberate. The verify route parses JSON with
`express.json()` and re-serialises the body, rather than streaming the raw
request, because Agent's generated `server/index.ts` applies
`express.json()` to every route before any of yours, which consumes the
stream; this adapter works either way, and we checked both. And
`Set-Cookie` is copied with `res.append`, so the session cookie arrives as
its own header rather than folded into another.

Then the router goes on the app before the Vite middleware, so the API is
never swallowed by the front-end fallback. Paste this prompt:

```text
Use the @buildbase/sdk npm package, already installed, to add BuildBase sign-in to this app. Read https://www.buildbase.app/llms.txt first and do not invent endpoints.
- Create server/auth.ts with the verify, session and signout handlers from https://www.buildbase.app/guides/buildbase-on-vite, byte for byte.
- Create server/buildbase-routes.ts with the adapter from the BuildBase Replit guide and mount authRouter in server/index.ts before the Vite middleware and before any catch-all route.
- Keep the BuildBaseProvider that npx buildbase init wrote and wrap the React app in it in client/src/main.tsx, with import '@buildbase/sdk/css'.
- Read VITE_BUILDBASE_ORG_ID, VITE_BUILDBASE_CLIENT_ID and VITE_BUILDBASE_REDIRECT_URL from the environment; I will add BUILDBASE_CLIENT_SECRET in Secrets myself. Never read it in client/ and never give it a VITE_ prefix.
- Do not add Replit Auth, Clerk, Passport or a users table. If sign-in already exists in this app, stop and ask me before changing it.
```

What you should see: three new files and one changed one, and nothing
under `client/` that mentions the secret. The provider `init` wrote is the
Vite guide's: `getSession` fetches `/api/auth/session` once per page load,
`handleAuthentication` posts the one-time code to `/api/auth/verify`, and
`onSignOut` posts to `/api/auth/signout`, so the three routes are the whole
contract between the two halves.

**Correcting potential issues.** Read the change before you accept it. The
ways Agent tends to drift, and the line to paste back:

- It mounted the router after the catch-all, so `/api/auth/session`
  returns the app's HTML. Paste: "Mount authRouter in server/index.ts
  before the Vite middleware and before any route that serves index.html."
- It rewrote the handlers as Express middleware with `req.body` and
  `res.cookie`. Paste: "Keep server/auth.ts byte for byte as the guide has
  it and keep the adapter; do not rewrite the handlers."
- It added Replit Auth "as a fallback", or kept a `users` table for its
  own login. Paste: "BuildBase is the only sign-in in this app. Remove the
  other sign-in and its users table, and keep every other table."
- It read the secret in a client file, or prefixed it `VITE_`. Paste:
  "BUILDBASE*CLIENT_SECRET is read only inside server/auth.ts on the
  server. Remove every other reference and the VITE* prefix."

## 5. Secrets, twice

Open Tools, then Secrets under Setup, and add `BUILDBASE_CLIENT_SECRET`
with the value the console showed you once. Replit's secrets page says the
tool "automatically encrypts the data and makes it available to your
Replit project as an environment variable", which is exactly how
`server/auth.ts` reads it. The three public values can go in the same tool
as configurations (its "separate sections for secrets and
configurations") or in `.env`; either way they reach Vite as `VITE_`
variables, which Vite inlines into the browser bundle, which is fine for
an organization id and a client id and fatal for a secret.

```bash
# Secrets tool (the one bare value) and configurations (the three public ones)
BUILDBASE_CLIENT_SECRET=<the secret, server only>
VITE_BUILDBASE_ORG_ID=<your 24-character org id>
VITE_BUILDBASE_CLIENT_ID=<the client id>
VITE_BUILDBASE_REDIRECT_URL=https://your-app.replit.app/sign-in
# VITE_BUILDBASE_SERVER_URL defaults to https://api.console.buildbase.app
```

Then the second time, and this is the step people miss. Replit's
troubleshooting page says "Secrets you set in the Project Editor do not
automatically carry over to your published app. Add all production Secrets
and environment variables in the Publishing pane", under Adjust settings,
Production app secrets. One value differs between the two: the redirect,
which carries the `replit.dev` address in development and the published
one in production, and Replit's secrets page adds that the tool has a
"New published app configuration" option for exactly this.

What you should see: `printenv | grep BUILDBASE` in the Shell lists the
four names with the secret present (Replit's page says `printenv` is how
to see every variable; organization members without the Owner role cannot
read secret values in the tool but can print them, which is worth knowing
before you share the workspace).

## 6. Run, then publish, then sign in from a private window

Press Run and open the preview. Before any button exists, open the
preview's address with `/api/auth/session` on the end. You should see
`{"sessionId":null}`: the server half is alive. That is the response we
got today from the same router on the same handlers, and it is the first
thing to check whenever sign-in misbehaves later, because the app's HTML
there means the route is not mounted ahead of the fallback.

Publish with the default deployment type. Replit's page says Autoscale "is
the default and recommended choice" for web apps and APIs, that Static
"is not compatible with Replit Apps created using Agent", and that Secrets
"are available for all deployment types except Static Deployments".
`REPLIT_DEPLOYMENT` is set to `1` in the published app if you ever need to
tell the two apart in code.

Then sign in. Open the published address in a private window, click the
button that calls `signIn()` from `useSaaSAuth()`, and you are sent to the
hosted sign-in page, which offers whichever methods you switched on in the
console. Sign up with a second email. You come back to `/sign-in` on your
app, signed in.

Then the two checks a demo skips. Close the browser, reopen the site: still
signed in, because the cookie is good for 30 days, which is the
[default session length](/guides/token-expired-mid-request). Sign out, then
reload: asked to sign in again, because sign-out ended the session on the
server rather than clearing a value in the browser; today's test showed
the sign-out response carrying `bb-session=; Path=/; HttpOnly;
SameSite=Lax; Max-Age=0`.

The methods are switches, not code: email and password, a magic link, an
email code, a phone sign-in code sent through your own Twilio account,
passkeys, Google, LinkedIn, GitHub, Microsoft, Apple, your own OIDC
provider, and Ethereum or Solana wallets. Turning on Google means creating
the client in Google's console and pasting two values; nothing in the
Replit project changes.

**Correcting potential issues.** "The redirect address is not allowed" on
the hosted page: the address in `VITE_BUILDBASE_REDIRECT_URL` and the one
registered on the client differ, usually by a trailing slash or the
`/sign-in` path, or production is still carrying the `replit.dev` value.
"Sign-in failed" after a correct code: the secret did not reach the
published app; Production app secrets, not the editor's Secrets, is where
it has to be. The preview works and the published app does not: Replit's
troubleshooting page puts production Secrets and the port configuration
first, and so do we.

## 7. A workspace per customer, and the colleague they invite

Every new user lands in their own workspace on first sign-in, with no setup
step. That is on by default for new organizations. In your app it is one
component for the switcher plus the hooks the SDK already has, so prompt
Agent for the screens; the data model is already there.

```text
Add a workspace switcher to the app header using useSaaSWorkspaces() from @buildbase/sdk/react, and a /settings page that opens the SDK's built-in workspace, members and billing screens.
Members with the admin role may invite teammates by email; everyone else sees the member list read-only.
Do not create workspace, membership or role tables in Replit Database; BuildBase holds them. Add a workspace_id column to every table that holds customer data, filled from the BuildBase session on the server, and filter every query by it.
```

What you should see: a switcher listing the workspaces the signed-in person
belongs to, with **Add team** creating another if you allow it. On the
members screen, inviting an address that has no account yet sends an email
with a link; the person signs up and lands in the workspace with the role
you chose. That flow has been in the SDK since version 0.0.71, and the
reasons an invite can be refused (a seat limit, an outsider policy) come
back as named errors rather than silence.

Why not let Agent write the tables? Because Replit's own options do not
have this layer either, and the page says so. Replit Auth means "users
sign in with their existing Replit accounts" on a Replit-branded page.
Clerk Auth gives your app its own tenant and branded sign-in, and under
"What's not supported" the same page lists "Organization tenants, Clerk's
organization and team-membership features". So whatever Agent builds for
teams, it builds from scratch. Every audit of a generated app finds the
same thing: a role column checked on one page and nowhere else. The SDK's
roles are enforced by the API, so a member who types the address of an
admin page is refused by the server, not just by a missing menu item.

**Correcting potential issues.** If a member can see another workspace's
rows by changing an id in the address bar, Agent wrote a query that
filters by the row id and not by the workspace. That is your data and your
query, and the next section is about it.

## 8. Your data stays in Replit Database, and it is simpler here

Your tables stay in Replit Database. The rule we give every agent is "do
not use BuildBase as the primary database", and it applies here. On
Supabase-shaped backends there is a wrinkle, because row-level security
policies keyed to the old sign-in cannot read a BuildBase session; on
Replit there is not, because the database is PostgreSQL that your Express
server reads with a server-side connection, and the permission check lives
in the route, where it cannot be forged.

So the pattern is one helper and one habit. The helper binds the server
SDK to the request's cookie:

```ts
// server/buildbase.ts
import BuildBase from '@buildbase/sdk';
import type { Request } from 'express';

export function bbFor(req: Request) {
  const match = (req.headers.cookie ?? '').match(
    /(?:^|;\s*)bb-session=([^;]+)/
  );
  return BuildBase({
    serverUrl:
      process.env.VITE_BUILDBASE_SERVER_URL ||
      'https://api.console.buildbase.app',
    orgId: process.env.VITE_BUILDBASE_ORG_ID!,
    getSessionId: async () => (match ? decodeURIComponent(match[1]) : null),
  });
}
```

The habit is that every route that touches customer data asks the SDK who
is asking and which workspace they are in, and passes that workspace id
into the query. A route with no session gets an error from the SDK rather
than a guess, and a query without a `workspace_id` filter is the bug to
look for in every change Agent makes from now on. The pattern is spelled
out, with the audit that found "any logged-in user could grant themselves
admin", in [teams, invites and admin versus
member](/guides/teams-invites-and-roles-for-a-vibe-coded-app).

One Replit-specific comfort. After the July 2025 incident in which the
agent deleted a customer's production database during a code freeze,
Replit rolled out automatic separation of development and production
databases, so the test data you create in the preview does not sit next to
real customers' rows. Jason Lemkin's line from that week, "there is no way
to enforce a code freeze in vibe coding apps like Replit", is the reason
to keep the permission check in a route you can read rather than in a
table rule you cannot.

## 9. Plans people can pay for, and the email when a card fails

Plans, prices and trials live in the BuildBase console and sync to your own
Stripe account at Stripe's rates, with a 0% platform fee, and no card data
touches your app. The part that generated apps get wrong is what happens
after the payment, so this step is mostly about the webhook route and the
feature flag.

In the console, create two plans, Launch and Pro, say, each with a 7-day
trial and a monthly and a yearly price. Then the webhook. On Express it
needs the raw body, because the signature is computed over the bytes, and
Agent's global `express.json()` would parse them first:

```text
Add server/buildbase-webhook.ts and mount it in server/index.ts as app.post('/api/webhooks/buildbase', express.raw({ type: 'application/json' }), buildbaseWebhook), before express.json(). Verify the signature with parseWebhookEvent from @buildbase/sdk on the raw body, using BUILDBASE_WEBHOOK_SECRET from Secrets. Handle payment.failed with our own side effects only (flag the workspace, post to Slack); the platform sends the customer's email itself. Ignore other events.
Gate the export feature behind a BuildBase feature flag tied to the Pro plan, checked in a server route. Do not store the plan in Replit Database.
```

Point an endpoint in the console at
`https://your-app.replit.app/api/webhooks/buildbase`, add its signing
secret as `BUILDBASE_WEBHOOK_SECRET` in Secrets and again in Production app
secrets, and republish.

What you should see: subscribe on a test card and the Pro feature appears
without a reload, because the flag follows the workspace's plan. Let the
test card fail on renewal and the email goes out from your sending domain,
because `payment.failed` is one of the 129 events in the catalog and the
console's notification or a workflow sends it; the route in your app is
for whatever you want to happen besides. The console's delivery log shows
each attempt, and a delivery your server was down for is retried up to
five times over about two and a half minutes. Autoscale scales to zero
when idle, which is fine: the first retry wakes it.

Compare that with Replit's own routes to payments, read today: the Stripe
connector, on paid plans, where Agent sets up a Stripe sandbox and you
connect the live account from the Publish pane, or your own keys in
Secrets with the Checkout page and webhook endpoint Agent writes. Its
Stripe page walks through deposits, balance charges and refunds for a
booking app and mentions a monthly subscription with Stripe Billing as a
customisation. It says nothing about a plan that belongs to a team, seats,
limits, trials that end without a card, or credits, so if a plan has to
change what the app does for a whole workspace, [entitlements rather than
flags](/blog/entitlements-are-not-feature-flags) is the ten-minute read
before you prompt.

## Where Replit's built-in wins

Keep Replit Auth when your app is a prototype or an internal tool and
Replit branding is acceptable, which is Replit's own description of where
it fits; it is one sentence to Agent, Replit sends the password reset
emails for you, and there is nothing to configure. Keep Clerk Auth when
you want a branded sign-in with separate development and production user
stores and you need nothing after login; Replit provisions the tenant,
stores the keys and switches them at publish time, and its migration from
Replit Auth to Clerk moves the accounts for you. Keep Replit's Stripe
connector when you sell one thing to one person and want the sandbox and
the live switch handled for you.

Choose BuildBase when the question is "what may this team do now that they
paid": a workspace per customer, invites and roles enforced by the API,
plans with versions, feature flags tied to the plan, credits with a hard
stop, and the lifecycle email, from one SDK, with your data staying in
Replit Database. That is the layer Replit's pages list as not there today,
and [the launch checklist](/guides/vibe-coded-app-launch-checklist) is the
twenty-item version of why it matters before strangers arrive.

> **Key takeaway:** On Replit there is a shell and a server, so nothing here is a workaround: the
> three routes mount on Express through 26 lines we ran today. The two things to
> get right are the secret, twice, and a workspace id on every query.

## What this costs in credits

Replit prices Agent work by effort. Its AI billing page says one
checkpoint is created per request, that complex builds are "bundled into
one checkpoint that may cost more than a simple request", and that "Replit
asks for confirmation before a paid action". On 27 October 2026 the
pricing page lists Core at $20 a month ($18 billed annually) with $20
towards models, Pro at $100 ($90 annually) with $100 and rollover for two
months, and Enterprise as custom; the Starter plan has daily Agent credits
up to a monthly cap and one published app that goes down after 30 days.
Published apps, the database and storage are billed by usage from the same
credits. Replit changes these; check the page.

The prompts in steps 4, 7 and 9 are three checkpoints, with perhaps two
fix-ups. The expensive thing on Replit is not a feature, it is a loop, and
the loop here has a name: a one-star Trustpilot review from 17 October 2026
reads, in full, "Users can't log in. AI gets into a failure loop." Another,
from 20 September, warns that "especially if you set the limit 50 dollar
it turns out take 300 dollar". Naming the exact files, the exact routes
and the exact guide is how you avoid the loop, and `replit.md` is how you
avoid it next month too. A non-developer who built a SaaS on Replit in
three hours in July 2026 chose it because "I wanted authentication,
payments, and a database without configuring anything myself", and called
the lock-in "the uncomfortable trade-off for the convenience"; this guide
is the version of that trade where the accounts, the teams and the plans
are yours to take with you.

BuildBase's side: a Free plan at $0, then Launch at $19 a month, with a 7-day
trial and no card on the paid plans.

## What breaks, and how you can tell

**`/api/auth/session` returns the app's HTML.** Step 4. The router is
mounted after the Vite middleware or the catch-all. Move it up.

**"Sign-in failed" on a correct code.** The secret is not in Production
app secrets, only in the editor's Secrets. Step 5, the second half.

**Sign-in works in the preview and loops after publishing.** Production is
still carrying the `replit.dev` redirect value, or the published address
is not registered with `/sign-in` on the end. Step 2 and step 5.

**The verify route returns 400 with a stack trace in the log.** The body
was not JSON. The adapter hands malformed JSON to Express's parser, which
answers 400 on its own; the provider always sends JSON, so this is a
hand-made request, not the app.

**The webhook returns 401 on every delivery.** `express.json()` parsed the
body before the raw handler saw it. Mount the webhook route with
`express.raw` ahead of `express.json()`.

**A member can see another workspace's rows.** The query filters by the row
id and not by `workspace_id`. Section 8, the habit.

**`npx buildbase init` says "could not tell the framework".** Neither
`next`, `vite` nor `express` is in `package.json`. Pass
`--framework vite` for the provider, and mount the routes by hand as in
step 4.

## Install

In the Shell:

```bash
npm i @buildbase/sdk
npx buildbase init --org-id <your 24-character org id>
```
