---
title: Environments & the Shared Catalog
description: What sandbox and live keep apart, what they share, and why editing a plan in sandbox can change a live customer's bill.
order: 16
---

# Environments & the Shared Catalog

QuotaStack gives every tenant two environments: **live** and **sandbox**. Your customer data is kept separate between them. Your catalog is not. The second half of that sentence surprises people, so this page spells out both halves.

> **Mental Model:** Think of it as **two warehouses that share one product catalogue**. Sandbox and live each hold their own stock, customers, and paperwork — but there is a single price list on the wall, and both warehouses read it. Reprice an item while playing in sandbox and the live warehouse charges the new price.

## Quick Take

- Two environments: **live** and **sandbox**
- Customer data is separated — customers, balances, subscriptions, ledger, reservations, topups, webhook events
- **The catalog is NOT separated** — plans, variants, billable metrics, metering rules, topup packages are shared
- Editing a plan or a price in sandbox changes it for **live customers too**
- The sandbox API key prefix is `qs_test_`; the environment is named `sandbox`

## Diagram

Sandbox and live each own their customer data, but both read a single shared catalog of plans, metrics, metering rules, and topup packages.

```mermaid
flowchart TB
    C[Shared Catalog<br/>plans · variants · metrics<br/>metering rules · topup packages]
    S[Sandbox<br/>customers · balances · subscriptions<br/>ledger · reservations · topups]
    L[Live<br/>customers · balances · subscriptions<br/>ledger · reservations · topups]
    C --> S
    C --> L
```

## Choosing an environment

There are three ways to pick an environment. They all point at the same two places.

**With an API key.** The product API decides from the key prefix.

| Prefix | Environment |
|---|---|
| `qs_live_...` | `live` |
| `qs_test_...` | `sandbox` |

**In the dashboard.** The header has a Sandbox / Live toggle. It changes which customer data you see.

**With a header.** Dashboard-session calls send `X-Environment: sandbox` or `X-Environment: live`.

One naming note. The sandbox API key prefix reads `qs_test_`, but the environment is named `sandbox`. They are the same place. The API, the webhook payloads, and the dashboard all use the word `sandbox`.

## What is separate

Each of these rows is stored with the environment it belongs to. A row in one environment is invisible from the other.

| Data | What separation means |
|---|---|
| Customers | A customer created in sandbox does not exist in live. |
| Credit accounts and balances | Sandbox credits are not spendable in live. |
| Credit blocks | A block granted in sandbox never appears on a live balance. |
| Ledger entries | Each environment has its own transaction history. |
| Reservations | A sandbox hold never touches a live balance. |
| Topup purchases | A topup recorded in sandbox is a sandbox record only. |
| Subscriptions | A sandbox subscription bills nobody in live. |
| Overage events | Arrears are tracked per environment. |
| Gauge states | Seat counts and other gauges are counted per environment. |
| Webhook events | The delivery log is per environment. |
| Audit log | Each environment has its own audit trail. |

## What is shared

These rows have no environment at all. There is one copy, and both environments read it.

| Catalog | Shared |
|---|---|
| Plans | Yes |
| Plan variants | Yes |
| Plan credit grants | Yes |
| Variant entitlements | Yes |
| Billable metrics | Yes |
| Metering rules | Yes |
| Topup packages | Yes |

There is no sandbox copy of a plan. There is no live copy of a metering rule. There is one of each, and a change to it is a change everywhere.

## What this means in practice

These are things that will happen, not things that might.

**Retire a plan in sandbox and it is retired in live.** Live customers subscribed to that plan are affected right away.

**Change a metering rule's price in sandbox and live customers are charged the new price.** The rule is the same row. Saving it in sandbox saves it for everyone.

**Create a billable metric in sandbox and it appears in live.** There is no promotion step, because there is nothing to promote.

Here is a real example. A metric named `tarot_reading` was created in the live environment, with a metering rule of one credit per unit. A brand-new **sandbox** customer was then able to reserve seven units against `tarot_reading` immediately, and the hold cost seven credits from the sandbox balance. The customer was sandbox. The balance was sandbox. The metric and its price came from live, because there is only one of each.

## Working safely

**Treat every catalog write as a production change.** It does not matter which environment you are in. If you are editing a plan, a variant, a metric, a metering rule, or a topup package, you are editing live.

**Give experiments their own names.** Use a metric key like `test_tarot_reading` rather than reusing a real one. A test plan should be obviously a test plan by its name, because both environments will show it.

**To try a new price, add a new metering rule.** Do not edit the live one. Rules have effective dates, so a new rule can take over without touching the old one.

**Check the environment badge before you edit.** It tells you which customer data you are looking at. It does not protect the catalog, because there is only one catalog.

## One exception worth knowing

Subscription entitlement overrides do not carry an environment of their own. They still cannot leak between environments, because an override belongs to a subscription, and subscriptions are separated. An override created against a sandbox subscription affects that sandbox subscription only.

The rule is this. Catalog rows have no environment, and they are shared. Rows that hang off a customer or a subscription follow their parent, whether or not they store the environment themselves.

## Common Mistakes

**✗ Don't test a price change by editing a metering rule in sandbox**

Metering rules are shared. The new price applies to live customers the moment you save it. Create a new rule instead.

**✗ Don't retire or rename a plan "just to see what happens" in sandbox**

Plans are shared. Retiring one in sandbox retires it in live, where real subscribers are attached to it.

**✗ Don't assume the dashboard's environment toggle protects everything**

It switches which customer data you see. It does not give you a second copy of the catalog — there is only one.

## Related

- [API Conventions](/docs/concepts/conventions) — Authentication, rate limits, pagination, and the error format.
- [Metering](/docs/concepts/metering) — Billable metrics and metering rules — both shared across environments.
- [Subscriptions](/docs/concepts/subscriptions) — Plans, variants, and the subscription lifecycle.
- [Customer Identification](/docs/concepts/customer-identification) — How customers are addressed, and why a sandbox customer is not a live one.
