# Test and go live

> Build against test mode end to end, then switch to live keys with a checklist covering origins, agents, webhooks, replacing keys and rate limits.

Everything in Proof works in test mode, from the editor to agents and seals, so you can build and try your whole flow before any real user writes a word. This guide covers what's different in test mode, then what to check before you switch to live keys.

## Test mode

Your platform has two sets of keys in the console: test (`sk_test_…`) and live (`sk_live_…`). Each sees only its own data. Users, documents, agents, seals and webhook endpoints made with a test key don't exist for a live key, and the other way round.

In test mode:

- **The editor loads on `http://localhost`** and `http://127.0.0.1` on any port, as well as your embed origins.
- **Seals are marked.** Codes start with `T`, such as `TQ83KD0PA`, and the seal page says **Test seal** at the top. Test seals don't count toward billing.
- **Agents connect to a test address**, `https://mcp.proof.exalto.ai/acme/test`, which uses the test consent URL you set in the console. It may be on `localhost`, since the person's own browser opens it.
- **Webhooks go to test endpoints only.** A test endpoint may be any URL, `http://localhost:3000/proof/events` included when Proof runs on your machine; otherwise point it at a tunnel such as `cloudflared tunnel --url http://localhost:3000`.

## Before you go live

Live keys come once we approve your platform: choose **Request live mode** on its **Live** tab in the console. Then work through these with your live keys, in a staging copy of your app if you have one.

### Keep the secret key on your server

Load `sk_live_…` from your secret store or an environment variable, on the server only. It never belongs in the browser, in a mobile app, or in your repository. The browser only ever needs session tokens, which your server starts for one user and one document at a time.

Anyone holding your secret key can start a session as any of your users. That's why seals name your platform as the one vouching for who wrote, and record which key was used.

### Set your embed origins

List every origin your app serves the editor from, such as `https://acme.com`, under **Embed origins**. Leave out anything you don't control. The editor won't load anywhere else, which stops other sites from framing your users' documents.

### Set your consent URL

Set the live consent URL in the console, or with [`PATCH /v1/platform`](/reference/api/platform/) and a live key, and connect a real agent to your live address, `https://mcp.proof.exalto.ai/acme`, from start to finish. See [Connect agents](/guides/connect-agents/).

### Add your webhook endpoint

Add the live endpoint, which must be `https`, and store its `whsec_…` secret apart from the test one. Check your server answers `2xx` to a real event, such as a comment in a live document. See [Send notifications](/guides/notifications/).

### Check your platform's name

Seal pages name your platform as where the text was written, which vouches for who wrote it. Make sure the platform's name in the console is the one your users know.

### Replace keys

To replace a key without downtime:

1. In the console, create a new key.
2. Deploy it.
3. Once the console shows the old key's last use was before your deploy, revoke it.

If a key may have leaked, revoke it right away. Every request with it stops at once. Seals record the ids of the keys that started their writing sessions (`attested_by.key_ids`), so you can find every seal a leaked key had a hand in.

### Stay within rate limits

The API doesn't limit requests yet. Even so, start one session per page load rather than one per action, and call `users.upsert` only when starting sessions or when a profile changes. When limits come, a request over them will get `429` with a `Retry-After` header, which the Node SDK already waits for and retries.

In the editor, people's edits always go first, and each agent request gets a short slice of work at a time per document, so one busy agent never slows anyone's typing.

### Handle errors

Every error has a `type` and a `code`, so your code can branch on them:

```ts
try {
  await proof.sessions.create({ user: "usr_9Tb3Wc1X", document: "doc_8Hq2Lm4R" });
} catch (error) {
  if (error.type === "permission" && error.code === "no_access") showNoAccess();
  else throw error;
}
```

The [API overview](/reference/api/overview/) lists every type.

## Next

- [Platforms and keys](/concepts/platforms-and-keys/): test and live modes in detail.
- [Trust model](/concepts/trust-model/): what your key vouches for.