Editions, organizations & BYOK
Upliftr is AI debugging agents that debug your app: you chat with them in plain English, they drive a real browser to reproduce the problem, and they root-cause the failure to the exact backend line. They are read-only by design, investigating and diagnosing across your flow and your servers without ever mutating your code, data, or infrastructure. It runs in two editions from one codebase, chosen with UPLIFTR_DEPLOYMENT_MODE:
Self-hosted (selfhosted) | Cloud (cloud) | |
|---|---|---|
| Organizations | One, created at first run | Many, one per signup |
| Signup | Closed (first-run setup screen) | Open self-service |
| AI key | The org's BYOK key, or the instance ANTHROPIC_API_KEY | Each org must BYOK its own key |
| Billing | License (no metered billing) | License (no metered billing) |
Every row of data, suites, runs, issues, plans, settings, members, is scoped to an organization, and the two are fully isolated: a user in one org can never see or touch another's. A user belongs to exactly one org.
Roles
| Role | Can |
|---|---|
owner | Everything; created automatically as the first user of an org. |
admin | Manage members, invites, settings, the org name, same as owner for day-to-day. |
member | Chat with the agents, save and re-run checks, triage issues, not org administration. |
The three roles above are scoped to one organization. There is one tier above them:
Platform super-admin
A platform super-admin manages every organization and user across the whole instance (cross-tenant) — the operator's console. It's a flag on a user, not an org role, and it's provisioned only from the environment, never through signup or invites. Available in both editions.
Set all three (the org is created for the admin itself; defaults to Platform):
UPLIFTR_PLATFORM_ADMIN_EMAIL=manage@yourco.com
UPLIFTR_PLATFORM_ADMIN_PASSWORD=a-strong-password
UPLIFTR_PLATFORM_ADMIN_ORG=PlatformOn every boot this is authoritative and idempotent: it creates the account (in its own org) or updates an existing user with that email — setting the flag, verifying it, and resetting its password to the env value (which also rotates any existing sessions). So you rotate the password by changing the env var and restarting, not in the UI.
Signed in as a platform admin, a Platform item appears in the sidebar → a console to search all users and orgs, change roles, reset passwords, force-logout, grant/revoke platform access, and delete users or whole organizations. The /api/v1/platform/* API enforces the same flag. Lock-out guards prevent deleting your own account, removing your own platform access, or deleting your own org.
Self-hosted first run
A fresh self-hosted instance has no users. Open the dashboard and you get a one-time "Create your admin account" screen, that creates the single org and its owner. Or pre-seed it headlessly:
UPLIFTR_DEPLOYMENT_MODE=selfhosted
UPLIFTR_ADMIN_EMAIL=admin@yourco.com
UPLIFTR_ADMIN_PASSWORD=change-me-now
UPLIFTR_BASE_URL=https://upliftr.yourco.com # used in invite linksSignup is closed; you grow the team with invites.
Cloud signup
In cloud mode, anyone can self-serve at the signup screen: email + password + organization name creates a new, isolated org with that user as its owner. Signups are rate-limited per IP. Each org then sets its own AI key (BYOK).
Inviting your team
An admin invites by email + role from Settings → Members:
- Invite, creates a tokenised link (
/accept-invite?token=…) that usesUPLIFTR_BASE_URL. Send it to the person (the dashboard shows it; email delivery is up to your proxy/SMTP). - Accept, they open the link, see "You've been invited to {org} as {role}", set a password, and they're in, as a member of your org.
Invites expire after 7 days, are single-use, and are listed (and revocable) under Settings → Members. A person who already has a Upliftr account elsewhere can't be invited (one user → one org); use a different address.
BYOK (bring your own key)
Upliftr never pays for your AI usage, each org brings its own Anthropic key. Set it under Settings → AI (the onboarding flow prompts for it on first run). It's encrypted at rest and, critically, threaded per-run in memory, it is never written to the shared process environment, so concurrent runs of different orgs can never see each other's key. Tracker (GitHub/GitLab/Jira) and log-backend credentials work the same way: per-org, isolated.
In selfhosted mode, an org with no key falls back to the instance ANTHROPIC_API_KEY (handy for a single-tenant box). In cloud, each org must set its own.
Audit log
Every security-relevant action, logins, signups, member and invite changes, session revocations, settings edits, org renames, is recorded per org. Admins see it under Settings → Audit log (or GET /api/v1/audit). Secret values are never logged (only which keys changed).
Configuration
| Variable | Default | Purpose |
|---|---|---|
UPLIFTR_DEPLOYMENT_MODE | cloud | cloud (open signup, many orgs) or selfhosted (one org, closed signup). |
UPLIFTR_BASE_URL | dashboard origin | Public URL used to build invite links. Set to your domain in prod. |
The dashboard reads GET /api/v1/config (public) at startup to adapt the UI to the edition. See Settings & env and Authentication.
Running the cloud edition: the hardening gate
Data isolation and per-org BYOK make the data layer safe for multi-tenant use. The cloud edition also executes a tenant's instructions (an LLM driving a real browser) on shared infrastructure, two extra risks a single-tenant box doesn't have. Both have built-in controls; turn them on for cloud:
- Egress isolation (SSRF). A run fetches whatever URL the tenant points it at, including, on cloud infra, the metadata endpoint (
169.254.169.254) and internal services. SetUPLIFTR_RUN_EGRESS=restricted: outbound requests (browser navigations and server-log fetches) that resolve to loopback / link-local / metadata / private ranges are rejected, with aUPLIFTR_RUN_EGRESS_ALLOWallowlist. It's mode-aware on purpose, self-hosted staysopenso it can test internal/localhostapps. - Per-run execution isolation. Set
UPLIFTR_RUN_EXECUTOR=subprocess: each run executes in a killable child process with no database access (it gets only the suite, the org's key, and an output dir), with a hardUPLIFTR_RUN_TIMEOUT_Swall-clock kill and optional memory/CPU caps. So a tenant's run can't read another's data or run forever.
For horizontally-scaled cloud, a container / Kubernetes-Job / Fly-Machine executor (one ephemeral, network-isolated container per run) plugs in behind the same executor interface, that step is tied to your platform, but the seam and the single-host isolation above are in place. Before opening cloud to untrusted tenants, also run a load + security test on your real infrastructure.
Self-hosted (one trusted org, your own network) is unaffected and secure as shipped.