Authentication
The three ways a request reaches uidu — public token, account Bearer, custom-app session — and where each may run.
What an app looks like — a landing page, a blog, a dashboard, a booking tool — doesn't decide how it authenticates. Who the request acts for does:
| Acting for | Token | createClient option | Runs |
|---|---|---|---|
| Anyone — public reads | public token | publicToken | anywhere, including the browser |
| The workspace — every write, and private data | account Bearer | apiKey | your server or the CLI only |
| The signed-in member — an app inside uidu | session from the host page | fromBridge(bridge) | the browser, inside uidu only |
One app can mix the rows: public pages rendered on the server with the public token, a contact form submitted through a server action with the Bearer, and a members' area that only works when uidu frames it.
Every domain page says at the top which token its reads and writes need, and the client reference says it per function.
Public token — reads for anyone
Read-only access to what a workspace publishes: CMS pages, events, stories, forms, courses, donation campaigns, the help center. It is safe to ship in browser code.
Get it from Workspace → Settings → Public tokens.
import { createClient } from '@uidu/client';
export const uidu = createClient({
workspace: process.env.UIDU_WORKSPACE!,
publicToken: process.env.UIDU_PUBLIC_TOKEN,
});Private data — contacts and deals, people, goals, bookings, campaigns — isn't readable with it: the
query succeeds and returns nothing (an empty list, or null). If a list is unexpectedly empty, check
the domain's header before debugging the query.
Bearer — the workspace
An account access token, sent as Authorization: Bearer …. It acts as the account it belongs to, so
it can do whatever that account's role in the workspace allows: read private data, and run every
write — including the visitor-facing ones (form responses, donations, RSVPs, job applications).
// lib/uidu.ts — imported only from server code
import { createClient } from '@uidu/client';
export const uidu = createClient({
workspace: process.env.UIDU_WORKSPACE!,
publicToken: process.env.UIDU_PUBLIC_TOKEN,
apiKey: process.env.UIDU_API_KEY, // server-only
});Never in the browser
Keep apiKey on the server: a Server Component, a server action, a route handler, a script, or the
CLI. Don't prefix it NEXT_PUBLIC_, and don't pass it to <UiduProvider> (which has no apiKey
prop for this reason). A visitor submitting a form goes through a server action that calls
createFormResponse with this client — see Forms.
Get one from Workspace → Settings → API keys, or run uidu login, which
signs you in through the browser and stores a token in ~/.uidu/config.json. Set it as
UIDU_API_KEY where your server runs. Writes and their
errors are covered in Writes.
Session — the signed-in member, inside uidu
A custom app runs in an iframe inside uidu. The host page hands it a
short-lived session (five minutes, refreshed for you) over postMessage: the app can do what that
member can do, and no more. There is no token to configure.
'use client';
import { UiduAppProvider } from '@uidu/react';
// Inside, useUiduClient() / useUiduApp() give you a session-backed client.
<UiduAppProvider>{children}</UiduAppProvider>;Outside React: connect() from @uidu/app-bridge, then createClient(fromBridge(bridge)).
Three rules follow from where the session lives:
- It only exists in the browser. It arrives after the page loads, so Server Components and
server actions never have it — pages that show member data are
'use client'. - It only works from the app's own origin. Don't forward it to a backend of yours to call uidu from there.
- It reaches less than a Bearer. The app's own Models, and read-only the workspace's goals. Everything else comes back as an error for now.
Choosing, in one line each
- Rendering public content (site, blog, events, careers): public token, on the server or in the browser.
- Anything that writes, or reads private data: Bearer, on the server.
- An app members open inside uidu: session, in the browser.