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_123is a different user from a live key’suser_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.
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
- Create a secret test key in the dashboard under Settings.
- Declare an element and serve a decision, as in the quickstart. The response is real;
experiment_statusstarts atinitializing. - Report an event for the same user. Send it twice with the same
idand seeduplicate: trueon the second. - Watch the dashboard: the element, the experiment, the decision, the event. When the first variants arrive, approve them (the default approval is manual) and serve more decisions to see variants come back.
- Exercise the failure paths: a wrong key for a
401, a declaration field under a publishable test key for a400, an undeclared element under a publishable key for a409. The messages are the ones quoted on Errors.
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.
In CI
A deploy step can rundeclare 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.