Self-hostable, multi-tenant auth and billing for the apps you run. User auth and provider-agnostic billing share one tenant model, behind one API, in one docker compose --profile full up.
Status: 2.1.0 is the latest stable release. 2.2.0 is in release candidates, with its stable release to follow. MIT licensed.
ReliPay is now Rekey. Packages moved to
@rekey.dev/*(the old@relipay/*packages are deprecated), environment variables renamedRELIPAY_*→REKEY_*(as of 2.0.0 the old names are no longer read, so setREKEY_*), and relipay.dev (the old domain) will redirect to rekey.dev once the domain migration completes.
If you're an AI coding agent reading this repo, start at AGENTS.md.
Building an app on Rekey Cloud? You need none of the self-host setup below.
rekey.dev/docs/quickstart goes from a free
workspace (no card) to a signed-in user in a Next.js app, with the drop-in
<SignIn> form and two environment variables. Or clone a starter:
Next.js,
Astro,
digital shop.
The whole stack boots with one command — the API auto-migrates on start:
cp .env.example .env # fill POSTGRES_PASSWORD, REDIS_PASSWORD,
# JWT_SECRET, SUPER_ADMIN_KEY, ENCRYPTION_KEY
# then paste the datastore passwords into
# DATABASE_URL/REDIS_URL — compose does not do it for you
docker compose --profile full up # Postgres + Redis + API (:3030) + panel (:3031)
# + portal (:3050)Every published port binds to 127.0.0.1, so this is a local stack — bringing
it up on a VPS does not put it on the internet. BIND_ADDRESS=0.0.0.0 overrides
that, and if you set it, set OPERATOR_SIGNUP_MODE=invite in the same edit or the
first stranger to find the host can create an operator account. The
internet-facing file is docker-compose.prod.yml
(Traefik, no published ports) — see DEPLOY.md.
Prefer to run from source (watch mode)? Start just the datastores in Docker:
pnpm install
cp .env.example .env # same five values as above
docker compose up -d postgres redis
pnpm db:migrate:deploy
pnpm devBoth paths read that one root .env. pnpm dev generates the Prisma client
and builds the workspace packages the apps import before starting anything, so
a fresh clone needs no separate build step.
API at http://localhost:3030, interactive docs at /docs, operator panel at
http://localhost:3031.
Then bootstrap the first Tenant, Application and API key in one command. The
CLI is self-host only: it authenticates with SUPER_ADMIN_KEY, which a Rekey
Cloud workspace does not have.
export REKEY_URL=http://localhost:3030
export SUPER_ADMIN_KEY=$(grep ^SUPER_ADMIN_KEY .env | cut -d= -f2)
npx @rekey.dev/cli init --tenant-name "Acme Co" --owner-email ops@acme.example \
--app-name "Acme Prod" --app-slug acme-prodSee docs/quickstart.md, the self-host quickstart, for the full walkthrough (boot → bootstrap → call from your app → sign up an end-user), and DEPLOY.md to run it in production (Traefik + TLS).
Apps (each runs on a fixed dev port):
| App | Package | Port | What |
|---|---|---|---|
apps/api |
@rekey.dev/api |
3030 | Fastify monolith — auth + billing + admin API |
apps/panel |
@rekey.dev/panel |
3031 | Next.js admin panel (panel.rekey.dev) |
apps/portal |
@rekey.dev/portal |
3050 | Hosted customer portal V2 (portal.rekey.dev) |
There is no examples/ directory: the demo apps that lived there were removed
in #261 because they had drifted from the API they demonstrated. Working
integrations live in their own repositories instead, the three starters linked
under Quick start, alongside
docs/quickstart.md and
docs/react-components.md.
Packages:
packages/shared-types— Zod schemas shared between API and SDKspackages/sdk-node—@rekey.dev/node, the server SDKpackages/sdk-react,packages/sdk-nextjs— client SDKspackages/cli— therekeyCLIpackages/mcp— MCP serverprisma/schema.prisma— owned byapps/api
Docs (docs/):
| Doc | What |
|---|---|
| quickstart.md | Self-host quickstart: fresh clone → running API → first Application → first end-user. The Cloud quickstart is rekey.dev/docs/quickstart |
| api-url.md | Which URL to point the SDK at — Rekey Cloud vs self-hosted |
| concepts.md | Tenant / Application / EndUser data model |
| api-keys.md | The three credential types, and environments |
| api-key-rotation.md | Leaked-key drill + rotation cadence |
| auth.md | End-user auth: tokens, MFA, passkeys, OAuth |
| react-components.md | The drop-in React component library |
| billing.md · billing-providers.md · coupons.md | Plans, checkout, providers, discounts |
| webhooks.md | Outbound events, signature verification, retries |
| email-templates.md | Custom transactional email: register, publish, preview, send by key from your backend |
| devices.md | Device-bound sessions, the max_devices entitlement, licence seats |
| external-billing.md | Bring your own billing: an inbound-only provider fed by your own system's events |
| portal.md | Hosted customer self-service billing portal |
| errors.md | Error envelope + the complete code reference |
| jwks.md · oidc-provider.md · tenant-auth.md · mcp.md · data-erasure.md | Everything else |
Stripe, PayPal, and Razorpay ship in the box, all behind one BillingProvider
interface with geographic routing (Razorpay for India, Stripe elsewhere, by
default — configurable per Application). Need a different processor? Providers
are self-describing modules — one directory describes checkout, webhook
verification, and event mapping, and the registry wires up the rest. See
docs/billing-providers.md for the how-to.
Billing is off on a new Application (billingConfig.enabled defaults to
false) — every billing endpoint answers 403 BILLING_DISABLED until an
operator turns it on in Panel → Application → Billing.
The core modules under apps/api/src/modules/ (including auth, billing, plans, api-keys and applications) ship their own AGENTS.md describing what the module is for and what an agent should not do there. If a module has none, start from its *.routes.ts file.
Things that are deliberately unsafe outside local development, documented here rather than left for you to discover. None are enabled by default.
REKEY_DEV_ECHO_AUTH_TOKENS=true makes the operator password-reset and
magic-link endpoints return the raw token in the API response, and the panel
then puts that token in a URL query string (?demoToken=…) to render a working
link. Query strings land in browser history, Referer headers, and access logs,
so a token that reaches one is best treated as disclosed.
The flag exists because those endpoints are unauthenticated by necessity — you cannot require a session to recover a forgotten password — so with no mail transport configured there is otherwise no way to complete the flow locally.
Guards: the API refuses to boot if the flag is set with
NODE_ENV=production, and the token is withheld unless NODE_ENV is exactly
development. Unset or unknown values withhold it. We chose to leave the
query-string hand-off in place rather than re-engineer a dev convenience, and to
document it instead — if this turns out to bite someone, the fix is a one-shot
httpOnly cookie and we'll take it.
Setup, the dev loop, and PR conventions are in CONTRIBUTING.md. AI coding agents should read AGENTS.md.
MIT.