# Connect your app to Rindle Cloud

Point your API server and browser client at a managed Rindle Cloud app using the fleet, control, and write endpoints from the dashboard.

You provisioned an app on [Rindle Cloud](https://cloud.rindle.sh) (if not, start
with the [Cloud quickstart](/docs/cloud-quickstart)). Now you point your two tiers
at it: the [API server](/docs/api-server) that holds your app's authority, and the
[browser client](/docs/client) your users run. **Nothing about your app code
changes** — the dashboard supplies connection coordinates and server-side credentials
for the same SDK you'd use [self-hosted](/docs/architecture).

## The values on your dashboard

Open your app and find the **Connect** panel. It surfaces exactly what the two
tiers need:

| Value | Who uses it | Goes into |
| --- | --- | --- |
| **Live queries** — `wss://…` | the **browser** | `createRindleClient({ daemon: { wsUrl, affinity: true } })` |
| **Control plane** — `https://…:8443` | the **API server** (reads) | the reads leg — `new HttpRindleDaemonClient({ baseUrl })` |
| **Write-master** — `https://…` | the **API server** (writes) | the writes leg of the `SplitDaemonClient` |
| **Daemon token** | the **API server** | the `Bearer` header on the reads leg |
| **Replicator token** | the **API server** | the `Bearer` header on the writes leg |

Every app is a `rindle-replicator` write-master plus one or more `rindled`
read-followers, so the API server has two legs: reads to a follower's Control plane,
writes to the write-master. The split is the whole security model: the **token never
reaches the browser**. Only your API server holds it, and it dials the data tier's
bearer-gated [private control plane](/docs/daemon#two-planes). The browser only ever gets the
public, **lease-gated** WebSocket — it can subscribe, but every query is authorized
by your API server first.

> The tokens are per-app secrets. The dashboard masks them until you click **reveal**,
> and the snippets read it from an environment variable rather than inlining it —
> put it in your API server's secret store (`RINDLE_DAEMON_TOKEN` below), not in
> source control.

## Your API server → the daemon

This is the [API server](/docs/api-server) page's wiring with the `baseUrl` and the
token swapped for your dashboard's **Control plane** endpoint and your
`RINDLE_DAEMON_TOKEN`. Everything else — your queries, your shared mutators,
your authorizers — is unchanged:

```ts
import { HttpRindleDaemonClient } from "@rindle/daemon-client";
import { createRindleApiServer, SplitDaemonClient } from "@rindle/api-server";
import { schema, queries, mutators } from "./app-api.ts";   // your registerQueries + sharedApiMutators (server/api.ts in the quickstart)

// The API server is the only tier that holds the token — it never reaches the browser.
// One topology: reads → the follower (Control plane), writes → the write-master.
const reads = new HttpRindleDaemonClient({
  baseUrl: "https://rindle-ab12cd34.fly.dev:8443",          // ← Control plane (reads), from your dashboard
  headers: { authorization: `Bearer ${process.env.RINDLE_REPLICATOR_TOKEN}` },
});
const daemon = new SplitDaemonClient(
  new HttpRindleDaemonClient({
    baseUrl: process.env.RINDLE_REPLICATOR_URL!,            // ← Write-master (writes), from your dashboard
    headers: { authorization: `Bearer ${process.env.RINDLE_DAEMON_TOKEN}` },
  }),
  reads,
);

export const api = createRindleApiServer({
  daemon, schema, queries, mutators,
  // …plus the same authorizeQuery / authorizeMutation you already had — unchanged.
});
// mount api.routes on your HTTP host (node:http, a Worker, Lambda, Hono, …)
```

Because `@rindle/api-server` is
[transport-agnostic](/docs/api-server#bring-your-own-http), the API server can run
**anywhere that speaks HTTPS** — a Cloudflare Worker, a Node process, a Lambda. It
reaches the follower's Control plane (reads) and the write-master (writes) over TLS
with the bearer token; you don't run anything inside Rindle Cloud to make those calls.

## Your browser client → your API server

The [client](/docs/client) connects to **your** API server for query leases and
mutations, and opens its live subscription against the dashboard's **Live queries**
endpoint:

```ts
import { createRindleClient } from "@rindle/optimistic";
import { schema, mutators } from "./shared/rindle";

const app = await createRindleClient({
  schema,
  mutators,
  user: () => currentUser(),                          // a mutator's ctx.user
  api: { url: "" },                                   // your own API server (above), same-origin
  daemon: {
    wsUrl: "wss://rindle-ab12cd34.fly.dev",           // ← stable fleet endpoint
    affinity: true,                                    // keep ws + lease on one follower
  },
});
```

That's the entire change from local development: the follower's `wsUrl` and the API
server's read/write endpoints move from `127.0.0.1` to the endpoints on your dashboard.

## What changes from self-hosted

If you followed the self-host docs first, only the connection coordinates differ —
your queries, mutators, schema, and React code are identical:

| | Self-hosted (local) | Rindle Cloud |
| --- | --- | --- |
| API server reads (`RINDLE_DAEMON_URL`) | `http://127.0.0.1:7600` | the **Control plane** endpoint (`:8443`, TLS) |
| API server writes (`RINDLE_REPLICATOR_URL`) | `http://127.0.0.1:7611` | the **write-master** endpoint |
| Client `daemon.wsUrl` | `ws://127.0.0.1:7601` | the **Live queries** endpoint (`wss://`) |
| Daemon token | one you set on the pair | minted per app, on your dashboard |
| Replicator token | one you set on the master | minted per app, on your dashboard |
| Running the data tier | you operate the pair | operated for you |

## Multi-node apps

Every managed app is a `rindle-replicator` write-master plus one or more separately
placed `rindled` read-followers: **reads** go to the Control plane / Live queries
endpoints, **writes** to the write-master. Scaling from one follower to a regional fleet
keeps the same connection shape:

- **Reads** keep using one stable **Live queries** endpoint. With `affinity: true`, a
  follower mints a signed placement ticket; the WebSocket and lease request are then
  replayed to that same follower. A sustained outage clears the ticket and re-pins the
  client to a live follower.
- **Writes** keep using the separately bearer-gated **Write-master** endpoint from the
  dashboard. They still pass through your API server and never expose the replicator
  token to the browser.

The reason there's one write master at all is the
[correctness contract](/docs/architecture#the-correctness-contract): one total write
order is what lets every follower and every browser converge without a
conflict-resolution protocol. The master accepts concurrent transactions and commits
them into that one order; "one master" does not mean one request at a time.

## Next steps

- [The API server](/docs/api-server) — resolving named queries and running
  authoritative mutators, in full.
- [The browser client](/docs/client) — optimistic writes, live views, and rebase.
- [Scale & operate](/docs/cloud-scaling) — the dashboard, resizing, and what each
  profile costs.
- [Full app: the issue tracker](/docs/example-issue-tracker) — the same two tiers in
  a complete React app.

---

[View this page on Rindle](https://rindle.sh/docs/cloud-connect)
