Skip to main content
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 under Settings.
  2. Declare an element and serve a decision, as in the quickstart. 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 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.

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 run 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 has the rest.