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

# Test with test keys

> Run the whole loop on a test key before live traffic: what the test environment shares with live, what it keeps separate, and its use in staging and CI.

Every key has an environment, `live` or `test`, fixed at creation. A test key (`curious_sk_test_…`, `curious_pk_test_…`) runs the full product: real declarations, real variant generation, real decisions, real attribution, visible in the dashboard. What it changes is accounting and identity, never semantics.

## What test keeps separate

* **Users.** A test key's `user_123` is a different user from a live key's `user_123`. Test traffic can never read or change live user state, and a test decision never ends a live decision's window.
* **Learning.** Test decisions and events are excluded from live learning and from the dashboard's live metrics.
* **Idempotency.** A test retry never replays a live decision; event ids are unique per environment.

What test shares: the elements, their declarations and experiments are the same objects. Declaring `pricing_hero` with a test key and later with a live key is one element with one experiment chain, so the declaration you test is the declaration you ship.

## Run the loop

1. Create a secret test key in the [dashboard](https://app.trycurious.ai) under **Settings**.
2. Declare an element and serve a decision, as in the [quickstart](https://docs.trycurious.ai/quickstart.md). The response is real; `experiment_status` starts at `initializing`.
3. Report an event for the same user. Send it twice with the same `id` and see `duplicate: true` on the second.
4. Watch the dashboard: the element, the experiment, the decision, the event. When the first variants arrive, approve them (the default [approval](https://docs.trycurious.ai/concepts/approval.md) is manual) and serve more decisions to see variants come back.
5. Exercise the failure paths: a wrong key for a `401`, a declaration field under a publishable test key for a `400`, an undeclared element under a publishable key for a `409`. The messages are the ones quoted on [Errors](https://docs.trycurious.ai/api-reference/errors.md).

## In staging

Give staging the test keys and production the live keys through the same environment variables (`CURIOUS_SECRET_KEY` on servers), so the code is identical and only the key changes. A test publishable key has its own origin allowlist; add your staging origin to it.

Staging is not a configuration sandbox. Its declarations change the same experiment production serves from, so deploy the same declaration to both: one that changes a declaration-class field in staging supersedes the live experiment. See [The experiment restarted](https://docs.trycurious.ai/troubleshooting/experiment-restarted.md).

## In CI

A deploy step can run [`declare`](https://docs.trycurious.ai/guides/declare) with the test key on every build: an identical declaration is a no-op, and a malformed one fails the build with a `400` that names the field. Keep integration tests that call `decide` to a few, since each one is a real decision that generates real variants for the element.

## Going live

Switching is a key swap, nothing else. The [go-live checklist](https://docs.trycurious.ai/guides/go-live.md) has the rest.


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