The two key kinds

Every account has two API keys side-by-side on the dashboard. They share the same routes but behave differently:

pk_live_…pk_test_…
RandomnessCryptographic, single-useDeterministic per (account, clientSeed)
Live seed stateMutates — cursor / nonce advanceUntouched — no rotation, no seed_state row
Daily quotaCountedNot counted
WebhooksFire on every outcomeSuppressed
Outcome mode field"live""test"
Per-minute rate limitAnonymous IP limit appliesAnonymous IP limit applies

Test keys still hit real routes, real validation, real error shapes — they just don't move the world forward. That's the entire point.

What "deterministic per (account, clientSeed)" means

Two test-mode calls with the same clientSeed and same parameters will always return the same numbers — regardless of when, from where, or with which of your account's test keys you call. That makes them safe to use as fixtures:

# Run twice. Same numbers every time.
curl "https://api.provable.io/api/ints?clientSeed=ci-fixture-loot&count=3&min=1&max=100" \
    -H "x-api-key: $PROVABLE_TEST_KEY"
curl "https://api.provable.io/api/ints?clientSeed=ci-fixture-loot&count=3&min=1&max=100" \
    -H "x-api-key: $PROVABLE_TEST_KEY"

Change any input — the clientSeed, count, min, max, the items array, the dice notation — and you get a different deterministic outcome. The function is pure; it just isn't random.

Pattern: snapshot tests in CI

Because the outputs are stable, you can snapshot-test code that consumes Provable.io without mocking it out. Your test does a real round-trip — same routes, same JSON shapes, same error handling — but against a frozen output set.

// jest example
test("loot drop for fixture user is consistent", async () => {
    const res = await fetch(
        "https://api.provable.io/api/pick?clientSeed=test-user-1&items=common,rare,legendary&weights=70,25,5",
        { headers: { "x-api-key": process.env.PROVABLE_TEST_KEY } },
    );
    const body = await res.json();
    expect(body.mode).toBe("test");
    expect(body.outcome).toMatchSnapshot();      // stable across runs
    expect(body.index).toMatchSnapshot();
});

Pattern: local dev that doesn't poison prod state

If you've been generating outcomes for clientSeed=order-123 in production, you don't want a sleepy npm run dev session to advance that seed's cursor twenty more times overnight. Point your dev environment at the test key and your live seed state stays frozen — every test-mode call short-circuits the seed write path entirely.

# .env.development
PROVABLE_API_KEY=pk_test_a1b2c3...
# .env.production
PROVABLE_API_KEY=pk_live_x9y8z7...

Your application code is identical. Only the env var changes.

Detecting test outcomes in your own code

Every test-mode response includes "mode": "test" in the JSON body — and so does every test-mode outcome on the streaming endpoint. Webhooks won't fire for test calls (so your downstream consumers never see test data), but your own client code can guard against accidentally treating a test outcome as live:

const body = await res.json();
if (body.mode === "test" && process.env.NODE_ENV === "production") {
    throw new Error("refusing to use a test-mode outcome in production");
}

What test mode does not change

Next steps