---
title: "Cloud and regions"
description: "What MainSDK Cloud stores, in which of three regions, how a project joins it, and why a Cloud limit never breaks your app."
section: "architecture"
status: "Planned for 0.6"
spec: "cloud-platform"
canonical: "https://mainsdk.dev/docs/architecture/cloud-and-regions"
updated: "2026-10-28"
---

# Cloud and regions

> What MainSDK Cloud stores, in which of three regions, how a project joins it, and why a Cloud limit never breaks your app.

Status: Planned for 0.6 · Canonical: https://mainsdk.dev/docs/architecture/cloud-and-regions · Updated: 2026-10-28

MainSDK Cloud is optional. Your app and every check work without it. When you do log in, your project's data stays in the region you chose: US, EU or India. Cloud arrives in phase 2 (milestone 0.6).

## Three regions

```mermaid
flowchart TB
  app["Your app<br/>project: { id, region: eu }"] -- "events, traces, decisions" --> eui
  subgraph usr["US region"]
    usi["Ingest"] --> uss[("Store")]
  end
  subgraph eur["EU region"]
    eui["Ingest"] --> eus[("Store")]
  end
  subgraph india["India region"]
    ini["Ingest"] --> ins[("Store")]
  end
  wrong["Call reaches the wrong region"] --> usi
  usi -. "forward or refuse with RFC 9457,<br/>store nothing" .-> eui
```

- Each project's events, traces and decisions are kept in its chosen region. A call that reaches another region's endpoint is forwarded or refused, and nothing is stored there.
- Every region is included on every plan at no extra cost. There is no data region add-on.
- Still open: the default region when you don't choose one (taken from `main.yaml` or asked at login).

Your app's own data follows the same three regions (ADR-0006): `db` keeps one store per region and writes each record to its owner's region, `check` refuses queries that join across regions, analytics combine regions only as totals, and moving a user between regions is an audited job.

## What Cloud stores

| Stored | Notes |
| --- | --- |
| Projects, environments and keys | Keys shown masked in the console |
| Events: trace spans, log lines, errors, policy decisions, gate runs, action calls | Redacted in your app before they are sent |
| History | 7 days on Free, 30 days on Teams |

What Cloud never needs: your request path. See [request lifecycle](https://mainsdk.dev/docs/architecture/request-lifecycle).

## Joining Cloud

```mermaid
sequenceDiagram
  participant D as You
  participant C as mainsdk CLI
  participant B as Browser
  participant M as MainSDK Cloud
  D->>C: mainsdk init (no account, no network)
  C->>C: write project id (UUIDv7) to main.yaml
  D->>C: mainsdk login --region eu
  C->>M: device authorization (RFC 8628)
  M-->>C: short code
  D->>B: approve the code
  M-->>C: project claimed, same prj_ id
  C->>M: optional: upload the last 7 days of local events
```

- **The project id is not a credential.** It is a UUIDv7 in `main.yaml`, shown as `prj_01JC8Q…`. A public repo shows it, so only an ingest key or a platform OIDC token can send data.
- **No copying keys.** Login uses the device flow; you approve a code in the browser.
- **Ingest keys:** one per environment. `msk_live_…` for prod, `msk_test_…` for dev and stage, so secret scanners catch leaks. In CI and on Vercel, the platform's OIDC token replaces the key.
- **Forks get a new id.** If an id is already claimed under a different git remote, login offers a new one; `mainsdk init --new-id` does it by hand.
- **Logout stops sending at once.** The app and every check keep working locally.

Every call carries the wire contract version in a `mainsdk-protocol` header. The Cloud supports the current and two previous SDK majors.

## The 402 rule

Only what reaches our Cloud is metered. Your machine is never metered, and security is never metered.

```mermaid
flowchart LR
  a["0–80%<br/>free allowance"] --> w["80%<br/>warning in CLI and email"]
  w --> g["100% to 120%, or 7 days<br/>grace"]
  g --> c{"Card on file?"}
  c -- yes --> p["Pay per 100,000 events<br/>up to your monthly cap"]
  c -- no --> e["HTTP 402 from Cloud only"]
  p -- cap reached --> e
```

```json
{ "type": "https://mainsdk.dev/errors/MAIN-CLOUD-0402", "title": "Free allowance used",
  "status": 402, "detail": "50,000 events used this month. Add a card or move to Teams." }
```

After a 402, events stop going online and hosted reviewers stop. Your app keeps running, every check, gate, guarantee and runtime enforcement keeps working, and logs stay in your terminal (ADR-0003, ADR-0007).

The allowance of 50,000 events a month is a proposal. The price per 100,000 events and per hosted reviewer run is not set yet.

## Production access

Any access to production data in Cloud needs SSO or a passkey with phishing-resistant MFA (ADR-0008). Production live tail and debug sessions ask for a fresh passkey no older than 5 minutes. MainSDK staff follow the same rules and appear in the same project audit view. There is no remote shell into production.

## Later

| What | Phase |
| --- | --- |
| Organisations, project transfer, SSO and SCIM for the console (Teams) | 2 |
| Self-hosted and air-gapped control planes with PKI-signed licences | 4 (Fleet) |

## Next

- [Telemetry and signals](https://mainsdk.dev/docs/architecture/telemetry-and-signals): what is redacted before it is sent.
- [Free in the app, paid in the Cloud](https://mainsdk.dev/docs/concepts/free-and-cloud): the plans and their limits.
- [Data and regions](https://mainsdk.dev/docs/concepts/data-and-regions): how your app's own data stays in its region.
