Joining the waitlist does not create a usable account.
An admin approves you in Clerk (who are you), then separately grants
database access (what may you see). This hub itself is admin-only.
doka-platform
developer hub
doka-platform
developer hub
Search everything
What's in the database
Recent changes
The audit trail: who changed what, and the old → new values.
Writes from the terminal or loaders show as owner/CLI.
Currently audited: app_users; new tables opt in per migration.
Newest timestamp per table — the quick “is data
actually flowing” answer.
Change queue
CRM edits queued by app users (change_requests).
Apply the change at the source system, then mark it here.
Integration starter prompt
A generated build prompt for wiring this Clerk instance and
the Data API into a new app — paste it into Claude Code or Cursor. It embeds the live
table reference, the token discipline, the error taxonomy, and the RLS contract, so a new
app starts where this hub finished.
The Data API
Every request: a fresh Clerk token (they live ~60 s) as
Authorization: Bearer, plus
Accept-Profile: app on reads /
Content-Profile: app on writes. Paths are unprefixed.
The database decides every row via RLS — send no client-side scope filters.
Full details live in the Prompts tab's starter prompt.
Create a new app
The path from idea to scaffold, like Pocket CRM or TiplistToGo. An app is two
things here: an entitlement key in users' apps
arrays (what has_app() checks), and a client
application built on this Clerk instance + Data API. This generates a tailored
build prompt for the second and the registration email for the first.
Request a platform change
New tables and policy changes are applied by the platform owner through
migrations — this builds the request, it does not execute anything. Fill it in, copy,
and email it.
User directory
Everything here is written through the Data API and enforced by RLS —
a non-admin calling the same endpoints gets nothing.
Add a user
Create their identity in Clerk first (invite below), then grant access
here. The sub is their Clerk User ID.
Clerk — identities & invitations
Invitations, session revocation and bans run through a small server
function holding the Clerk secret key; the database never sees it, the browser never
sees it. Password resets stay self-serve: the person uses “Forgot password”
on the sign-in screen.
CLI fallback — user:add
The same grant, from the terminal — for when the hub or the Data API is
unavailable. Run from the doka-platform repo, where
DATABASE_URL lives.
Adding a user — the two gates
Gate 1 · Clerk answers who are you.
Gate 2 · this command answers what may you see.
Gate 1 alone is a working sign-in that sees zero rows everywhere — correct, not broken.
Nothing per-user ever happens in Neon or GitHub.
Invite (Clerk section above): send the invitation email; they accept,
set a password and MFA. Their identity then appears in the Clerk list with
no row.
Grant: click Grant access on their Clerk row — it
prefills the add form with their sub — choose role, extkey
or branch, apps, and add. Editing rights later happens in the directory.
Verify: click Preview access on their directory row
— the database itself counts what they will see, before they ever sign in.
Traps: flags are space-separated pairs (--flag=value
silently mis-parses) · the flag is --extkey · email is
unique — if their Clerk account was recreated, delete the old row first or the insert
dies with 23505.
What the fields decide (enforced by RLS, never by an app)
rep sees rows matching their ExtKey ·
manager sees their branch (exact string) ·
admin sees everything
apps gates entitlement: CRM needs
pocket-crm; catalog needs either
Offboarding
is_active = false on their row darkens every table instantly
(app.me() filters on it), then ban or delete in Clerk. Deleting the
row cascades away their user_state,
change_requests and tiplists.
Identity
Watch the lifetime without issuing a query.
Token
—
Decoded payload
Decoded client-side; the signature is verified by Neon, not here.
No token acquired yet.
Database access — app.app_users
Clerk says who you are; this table says what you may see.
Who am I, according to Postgres?
The role Postgres actually ran your query as, and the
sub it resolved.
Test all tables
Every table, graded against what your
app_users row should produce.
Write probes
Each probe inserts a throwaway row and deletes it. Expected outcomes
are shown per step.