> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbased.health/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-host getbased

> Technical guide for running your own getbased instance, wearable OAuth apps, sync relay, and local AI.

getbased is authored in TypeScript and compiled with TypeScript 7 to native JavaScript ES modules for local development. A source checkout requires Node.js 24 and npm dependencies. Production deployments run a Rolldown build that creates a hashed startup bundle and keeps feature-level lazy chunks separate. The resulting runtime is still mostly static HTML, CSS, and JavaScript, plus optional API routes for hosted features.

## Quick start

<Steps>
  <Step title="Clone the repository">
    ```bash theme={null}
    git clone https://github.com/elkimek/get-based
    cd get-based
    npm ci
    ```
  </Step>

  <Step title="Start a local server">
    ```bash theme={null}
    npm run dev-server -- 8000
    ```

    Open `http://localhost:8000/app` in your browser. The dashboard runs immediately.

    <Note>
      Use the development server for wearable OAuth and other features that depend on `/api/proxy`. After compiling with `npm run typescript:build`, a static server such as `python3 -m http.server 8000` is sufficient only for browser-local features; it cannot load deployment OAuth settings or exchange confidential OAuth tokens, so Google Health remains unavailable there.
    </Note>
  </Step>
</Steps>

`npm ci` compiles the sources through its `prepare` step; `npm run dev-server` recompiles and builds the Companion download before serving. If you edit sources while the server is running, run `npm run typescript:build` before reloading. Production builds also require compiled runtime modules: use `npm run production:build` locally; Vercel compiles during dependency installation before its configured build command. The app is installable as a **PWA** and works fully offline for browser-local features — labs, charts, custom markers, already-imported wearable data, and local encryption are available without a network connection after the first load. OAuth connection, token refresh, and live wearable sync still require the provider network and the deployment's server routes.

## Optional CLI Companion

The hosted app and self-hosted app both support [CLI agents](/guides/cli-agents). The Companion must run on the **browser user's computer**, not merely on a remote server hosting getbased. A static website cannot launch it.

For development with the generated Companion download and normal lifecycle setup, use an application checkout with dependencies:

```sh theme={null}
npm ci
npm run dev-server -- 8000
```

This npm command compiles TypeScript and builds the Companion bundle before starting the development server. Keep the same browser origin (including `localhost` versus `127.0.0.1` and port) to use existing local data. If port 8000 is occupied, stop your previous server normally; starting on another origin does not migrate its browser storage.

For an independently running source Companion, compile after source edits and use `npm run companion`. For login startup, use `npm run companion:install`. The public download runs with Node.js 20+ without a source checkout. A self-hosted HTTPS page outside the official allowlist needs its exact origin in the Companion's `GETBASED_AGENT_HOST_ALLOWED_ORIGINS`; never expose the loopback listener publicly to solve connection failures. See [Companion operation and security](/developers/cli-agent-providers#build-and-operate).

## Wearable OAuth apps

The OAuth `client_id` values baked into the app belong to the maintainer's registered apps, which are authorized for `*.getbased.health` redirect URIs only. If you self-host under a different domain, the provider returns `invalid_client` when the bundled credentials are paired with your redirect URI.

To run wearable OAuth on your own host, register your own OAuth app with each provider you want to support and supply the credentials via environment variables.

### Register your OAuth apps

| Provider | Developer portal |
| - | - |
| Oura | [cloud.ouraring.com/oauth/applications](https://cloud.ouraring.com/oauth/applications) |
| Withings | [developer.withings.com/dashboard](https://developer.withings.com/dashboard/) |
| Polar | [admin.polaraccesslink.com](https://admin.polaraccesslink.com/) |
| Google Health | [Google Health API setup](https://developers.google.com/health/setup) |
| Fitbit | Legacy connections only; use Google Health for new Fitbit access. |
| WHOOP | [developer.whoop.com](https://developer.whoop.com/) — an active WHOOP membership is required to create an app. |
| Ultrahuman | [vision.ultrahuman.com/developer](https://vision.ultrahuman.com/developer) |

When registering, add both your local dev redirect URI (`http://localhost:8000/app`) and your production hostname (e.g. `https://your-host.example/app`) to the allowed redirect URI list in each provider's portal — the URIs must match character-for-character. For a custom hostname that is not already listed, add the same URI to that provider's `oauth.redirectUris` entry in `js/wearable-adapters.ts` in your fork.

### Environment variables

Create a `.env.local` file in the repo root (copy `.env.local.example` as a starting point):

```bash theme={null}
# Confidential clients — client_id + secret required
OURA_CLIENT_ID=
OURA_CLIENT_SECRET=

WITHINGS_CLIENT_ID=
WITHINGS_CLIENT_SECRET=

POLAR_CLIENT_ID=
POLAR_CLIENT_SECRET=

# Self-host-only integrations — flag + client_id + secret required
ULTRAHUMAN_ENABLED=true
ULTRAHUMAN_CLIENT_ID=
ULTRAHUMAN_CLIENT_SECRET=

WHOOP_ENABLED=true
WHOOP_CLIENT_ID=
WHOOP_CLIENT_SECRET=

# Google Health is disabled unless all three values are present
GOOGLE_HEALTH_ENABLED=true
GOOGLE_HEALTH_CLIENT_ID=
GOOGLE_HEALTH_CLIENT_SECRET=

# Legacy Fitbit uses PKCE; new Fitbit connections should use Google Health
FITBIT_CLIENT_ID=
```

<Info>
  Apple Health is file-import only — no OAuth, no credentials, no portal registration. It works identically on every self-hosted install.
</Info>

At startup, the browser fetches active public `client_id` values and provider availability flags from `/api/proxy`. Google Health, WHOOP, and Ultrahuman are fail-closed: each is available only when its `*_ENABLED` value is exactly `true` and its client ID and secret are all present. Only the public client ID and availability flag reach the browser; each client secret stays on the server and is injected during token exchange and refresh.

WHOOP is a supported self-host-only integration; Ultrahuman remains experimental. They are hidden on hosted deployments that have not enabled them. On `localhost`, disabled setup rows remain visible so an operator can find the required configuration; **Connect** appears only after the server reports a complete, enabled setup.

### WHOOP developer program and operator responsibility

WHOOP is not supported on the official hosted getbased app. Each independent deployment uses its own WHOOP membership and developer application. Start with WHOOP's [Developer Platform overview](https://developer.whoop.com/docs/developing/overview/), register the application in the WHOOP Developer Dashboard, configure the exact redirect URI, and complete any [app approval](https://developer.whoop.com/docs/developing/app-approval/) WHOOP requires before offering it to users.

The application requests `read:profile`, `read:cycles`, `read:recovery`, `read:sleep`, `read:workout`, and `offline`. getbased uses WHOOP's current v2 profile, cycle, recovery, and sleep endpoints. WHOOP's v1 data API is no longer supported; keep deployments aligned with the [v1-to-v2 migration guide](https://developer.whoop.com/docs/developing/v1-v2-migration/) and [current API reference](https://developer.whoop.com/api/).

WHOOP uses a confidential authorization-code flow. Keep `WHOOP_CLIENT_SECRET` server-side. Supply `WHOOP_CLIENT_ID` through environment configuration rather than committing it to the open-source fork; the active client ID must reach the browser for the authorization redirect, while the secret is injected only by `/api/proxy` for exchange and refresh.

<Warning>
  The self-host operator is the WHOOP application owner. Before enabling the integration, review the current [WHOOP API Terms of Use](https://developer.whoop.com/api-terms-of-use/) and ensure the deployment's consent, accessible support contact, privacy policy, security, retention/cache behavior, user access, deletion, and provider-side revocation process satisfy WHOOP and applicable law. Do not reuse or distribute another operator's developer credentials.
</Warning>

getbased presents a WHOOP-specific disclosure before redirecting to authorization. It explains the read scopes, self-host data path, device encryption, optional encrypted Sync/AI context, and deletion/revocation path. Do not remove or bypass that disclosure. A custom deployment must also publish its own accurate privacy policy and support contact.

At runtime, WHOOP token exchange, refresh, and data requests pass through the deployment's same-origin `/api/proxy`; they never fall back to getbased-operated infrastructure. Tokens, raw daily rows, and WHOOP-specific local profile data are device-key encrypted. Tokens, raw rows, and the connection record stay device-local and out of backups and Sync; only allowlisted compact derived surfaces can participate in optional encrypted profile Sync or enabled AI/Agent context.

<Warning>
  Google Health uses restricted, read-only OAuth scopes. The owner of each self-hosted Google Cloud project is responsible for the OAuth consent screen, verification, testing-user limits, and any Cloud App Security Assessment that Google requires. Do not reuse the official getbased OAuth project. See [Google Health setup](https://developers.google.com/health/setup) for current requirements.
</Warning>

The integration requests only these scopes:

* `https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly`
* `https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly`
* `https://www.googleapis.com/auth/googlehealth.sleep.readonly`

A copy-ready explanation for Google's **How will the scopes be used?** field is kept under 1,000 characters in [`google-health-scope-justification.md`](https://github.com/elkimek/get-based/blob/main/engineering/integrations/google-health-scope-justification.md).

<Note>
  Google currently caps a new unverified OAuth client at 100 users. Testing mode requires each tester to be added explicitly, and its refresh tokens expire after seven days. Supporting more than 100 users requires a third-party security review. Check Google's setup page before launch because these requirements can change.
</Note>

## Sun atmosphere source

An independent deployment does not inherit getbased's operated CAMS upstream or bearer. You have two supported choices:

* Select **Open-Meteo** in the Light lens for a browser-direct source with no server configuration.
* Run a compatible `getbased-uvdata` service, set `UVDATA_UPSTREAM` to its explicit base URL, and set `UVDATA_BEARER` only when that service requires one.

```bash theme={null}
UVDATA_UPSTREAM=https://uvdata.your-host.example
UVDATA_BEARER=
```

The self-hosted `/api/proxy` fails closed for CAMS when `UVDATA_UPSTREAM` is empty. It does not silently use the getbased-operated service. The app then falls back to browser-direct Open-Meteo in **Auto** mode.

For a multi-instance Vercel deployment, also configure a suitable proxy limiter with `PROXY_RATE_LIMIT_BLOB_TOKEN`, or explicitly accept the weaker per-instance fallback with `PROXY_ALLOW_INSTANCE_RATE_LIMIT=1`. `PROXY_RATE_LIMIT_MAX`, `PROXY_RATE_LIMIT_WINDOW_MS`, and `PROXY_UPSTREAM_TIMEOUT_MS` tune the shared proxy bounds. `PROXY_POSTAL_QUEUE_MAX` optionally caps queued Nominatim postal lookups per instance from 1 to 64; the default is 8.

## App updates

Deploy the production build so its embedded build identity and service-worker assets stay aligned. Updates download quietly; **Reload / Later** appears after installation succeeds. A failed update keeps the current app usable. Build identity, not a semver bump for every fix, distinguishes cache generations. See [Deployment](/developers/deployment#service-worker).

## Deploying to Vercel

A `vercel.json` is already present in the repo root — no extra configuration is needed.

<Steps>
  <Step title="Import the repository on Vercel">
    In the Vercel dashboard, click **Add New → Project** and import your fork of `get-based`. Vercel detects `vercel.json` automatically.
  </Step>

  <Step title="Set environment variables">
    Go to **Project → Settings → Environment Variables** and add your wearable credentials. Mark each `*_CLIENT_SECRET` value as **Sensitive**. Google Health, WHOOP, and Ultrahuman each require their enable flag, client ID, and secret. For WHOOP, finish the developer-program, redirect, disclosure, and terms review above before enabling production access.
  </Step>

  <Step title="Deploy">
    Vercel builds and deploys on every push to your default branch. The app runs at your Vercel project URL immediately after the first deploy.
  </Step>
</Steps>

<Tip>
  For a basic self-hosted install, no additional build configuration is needed — the app runs out of the box with the example supplement catalog included in the repo.
</Tip>

## Self-hosted profile sharing

The local development server provides only a process-memory `/api/share` store, so its links disappear when the process restarts. For durable temporary links, run `server/profile-share-server.ts` behind the same HTTPS origin at `/api/share`, using the container assets under `deploy/profile-share/`.

The standalone service requires an absolute `PROFILE_SHARE_SQLITE_PATH` and a unique `PROFILE_SHARE_RATE_LIMIT_KEY` of at least 32 characters. Optional bounds include `PROFILE_SHARE_BIND`, `PROFILE_SHARE_PORT`, `PROFILE_SHARE_DATABASE_MAX_BYTES`, and `PROFILE_SHARE_REQUEST_TIMEOUT_MS`.

The browser encrypts before upload, but the service still observes ordinary connection metadata and stores keyed abuse-control, expiry, and deletion metadata beside the opaque envelope. Decide and document your own backup policy. The official operated service intentionally has no retained database backup so deleted or expired temporary links are not resurrected; that also means infrastructure loss can invalidate live links.

## Running your own sync relay

Cross-device sync uses a **relay** — a blind store-and-forward server that holds encrypted blobs. The relay never sees your plaintext data. By default, getbased connects to `wss://sync.getbased.health`. You can run your own.

These examples pin the release used by the app's compatibility CI: [getbased-relay v2.0.0](https://github.com/elkimek/getbased-relay/releases/tag/v2.0.0). The v2 line uses Evolu 8 and adds replay-protected compaction plus an owner-proof verifier for the separate Agent Access context gateway.

<Steps>
  <Step title="Clone a released relay">
    Clone the repository and check out the immutable release tag:

    ```bash theme={null}
    git clone https://github.com/elkimek/getbased-relay
    cd getbased-relay
    git checkout v2.0.0
    ```

    Review `docker-compose.yml` before starting it. In particular, set `QUOTA_PER_OWNER_MB` to the encrypted-history allowance you want each Sync identity to have.
  </Step>

  <Step title="Create the private verifier configuration">
    The supplied Compose stack starts both the Evolu relay and the Agent Access context gateway. They share a private verifier socket and require a random internal credential.

    ```bash theme={null}
    cp .env.example .env
    openssl rand -hex 32
    ```

    Paste the generated value into `.env` as `CONTEXT_VERIFIER_TOKEN`. Keep it private: it authenticates only the gateway-to-relay verifier call and is not an Evolu mnemonic, owner key, or Agent Access read token.
  </Step>

  <Step title="Start and verify the stack">
    ```bash theme={null}
    docker compose up -d --build
    docker compose ps
    docker compose exec relay node -e "fetch('http://127.0.0.1:4002/health').then(async r => console.log(await r.text()))"
    ```

    Both `relay` and `context-gateway` should become healthy. The default Compose file binds the WebSocket relay to host loopback port `4000`, the context gateway to loopback port `4001`, and owner self-service to loopback port `4003`. Relay administration stays container-only on port `4002`.

    Persistent state lives in separate relay-data and context-data volumes. The verifier uses a private socket volume; it does not expose owner keys or a public TCP listener.
  </Step>

  <Step title="Add TLS with Caddy">
    Install [Caddy](https://caddyserver.com/) and create `/etc/caddy/Caddyfile`:

    ```caddyfile theme={null}
    sync.yourdomain.com {
        handle /api/* {
            reverse_proxy 127.0.0.1:4001 {
                header_up X-Getbased-Client-IP {http.request.remote.host}
            }
        }

        handle /self/* {
            reverse_proxy 127.0.0.1:4003
        }

        handle {
            reverse_proxy 127.0.0.1:4000
        }
    }
    ```

    The `/api/*` route serves Agent Access and must overwrite `X-Getbased-Client-IP` from the actual connection peer so callers cannot choose their own rate-limit identity. `/self/*` serves owner-signed storage and compaction requests. The remaining path carries the Evolu WebSocket connection; Caddy handles the upgrade automatically.

    Restart Caddy:

    ```bash theme={null}
    systemctl restart caddy
    ```

    Caddy provisions a TLS certificate automatically.
  </Step>

  <Step title="Point DNS">
    Add an A or AAAA record for `sync.yourdomain.com` pointing to your server.
  </Step>

  <Step title="Point getbased at your relay">
    In getbased, open **Settings → Data → Cross-device sync → Advanced**, enter `wss://sync.yourdomain.com`, and click **Save**. Use **Sync status & storage** to verify both relay connectivity and the configured quota.
  </Step>
</Steps>

<Info>
  The v2.0.0 Docker image pins Node.js 24.20.0 and installs `@evolu/common` 8.9.0 with `@evolu/nodejs` 3.2.0. A native, non-Docker deployment requires Node.js 24.20.0 or newer. Browser and relay adapters use independent package version streams; exact package numbers do not need to match, but they must share a supported Evolu protocol generation.
</Info>

<Info>
  A small Linux VPS with 1 CPU, 1 GB RAM, Docker, persistent storage, and a domain with TLS is sufficient for a personal relay. Monitor disk usage and keep regular backups of both persistent data volumes.
</Info>

<Warning>
  Before a major relay upgrade, record the current release and image, stop the stack, and back up the relay-data volume, context-data volume, and deployment configuration. Verify the archives before starting the new image. A safe rollback can require restoring the matching data backup; changing only the container image is not guaranteed to reverse an on-disk migration.
</Warning>

<Warning>
  Do not expose the admin port, verifier socket, or database volumes publicly. Route `/api/*` and `/self/*` through the same TLS hostname with the boundaries shown above.
</Warning>

## Local AI with a self-hosted instance

getbased supports Ollama, LM Studio, Unsloth Studio, Jan, llama.cpp, and other OpenAI-compatible servers through **Settings → AI → Local AI**. Enter the server's base URL and getbased sends AI calls directly from the browser to that endpoint.

Privacy depends on the address and model you choose. A loopback endpoint with a downloaded model keeps inference on the browser device. A LAN server moves requests to another machine, while a remote endpoint or Ollama `:cloud` model sends them off your network. getbased shows the detected execution location in the provider panel.

<Warning>
  The page and model-server schemes must satisfy browser mixed-content rules. An HTTPS getbased page can use an HTTP loopback endpoint, but the browser blocks plain HTTP LAN and remote endpoints. Put the model server behind HTTPS or an encrypted local tunnel. A getbased instance served over plain HTTP can call an HTTP LAN server when CORS allows the page origin, but the traffic itself is unencrypted unless a VPN or tunnel protects it.
</Warning>

Allow the exact getbased origin in your model server's CORS configuration. For example, a self-hosted page at `http://192.168.1.20:8000` should use that origin instead of `*` in `OLLAMA_ORIGINS`. LM Studio users should enable CORS and API authentication.

See [Connect an AI provider](/ai-providers) for backend detection, context and VRAM behavior, and model-testing links.

## Tor access

A `.onion` address is available for getbased. When you access the app via Tor Browser, the sync relay automatically switches to the `.onion` address (`ws://` — no TLS needed over Tor's end-to-end encryption). No additional configuration is required.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.