Test Mode vs Live Mode
Use a separate data scope for development without polluting your live help center. Covers the four key prefixes, how the SDK and dashboard signal mode, and the common pitfalls.
Prerequisites
- A cStar workspace
- Comfort with creating API keys from Settings → API Keys
Understand the four key prefixes
cStar follows the Stripe-style key naming convention. Two key types × two environments = four prefixes: - `sk_live_*` — Secret key, live mode. Server-side only. Full read/write on production data. - `sk_test_*` — Secret key, test mode. Server-side only. Full read/write on test data. - `pk_live_*` — Publishable key, live mode. Safe in browsers. Read-only public surfaces (Library / Community / QuickHelp). - `pk_test_*` — Publishable key, test mode. Safe in browsers. Read-only on test data. No key at all is also valid for anonymous public surfaces — the client defaults to live mode for backwards compatibility with widgets that have shipped for years.
Mint a publishable test key
In the dashboard: Settings → API Keys → New Key. Pick **Publishable** and **Test**. The key starts with `pk_test_`. Copy it once — the dashboard masks it after creation.
# Or via the CLI
cstar keys create --name "Dev preview" --type publishable --environment testWire it through your client
Pass the publishable key as the `publishableKey` option to any anonymous client. The SDK derives the mode from the prefix and sends the key as `Authorization: Bearer pk_test_...`. The server validates the key against your team and scopes the response data accordingly.
import { LibraryClient } from '@cstar.help/js/library';
const lib = new LibraryClient({
teamSlug: 'acme',
publishableKey: 'pk_test_...'
});
console.log(lib.mode); // 'test'
const articles = await lib.articles({ limit: 50 });
// Returns articles where is_test_data = true.
// Live articles are NOT returned even though they exist in the same DB.// Anonymous live (default — backwards-compatible with existing widgets):
const live = new LibraryClient({ teamSlug: 'acme' });
console.log(live.mode); // 'live'
// Or explicit live key (slightly higher per-key rate limits):
const liveExplicit = new LibraryClient({
teamSlug: 'acme',
publishableKey: 'pk_live_...'
});Seed test data with the matching secret key
On the server, use a `sk_test_*` key with `CStarClient`. Anything you create — articles, categories, tickets, customers — is automatically tagged `is_test_data: true` and only visible to clients sending the matching test publishable key.
import { CStarClient } from '@cstar.help/js';
const cstar = new CStarClient({
apiKey: process.env.CSTAR_TEST_KEY, // sk_test_...
teamId: process.env.CSTAR_TEAM_ID
});
const article = await cstar.articles.create({
title: 'How to reset your password',
content: '...',
status: 'published' // immediately visible in the public library
});Verify which mode you got
Every public-route response now includes a `mode: "live" | "test"` field so you can confirm the scope server-side. The client also exposes a `mode` getter derived from the publishable-key prefix.
// Client-side: assert mode in CI smoke tests
import assert from 'node:assert';
assert.strictEqual(lib.mode, 'test');
// Server response also includes the mode:
const response = await fetch(
'https://www.cstar.help/api/library/acme/categories',
{ headers: { Authorization: 'Bearer pk_test_...' } }
);
const json = await response.json();
console.log(json.mode); // 'test'Common pitfalls
Three things developers hit on day one: 1. **Wrong prefix in the browser.** `sk_*` keys must never ship to the browser — the SDK throws on construction. Use `pk_*` for client-side code. 2. **Mixing modes accidentally.** A test publishable key returns disjoint data from a live publishable key against the same team — they will NEVER show the same articles, categories, or community posts. This is intentional. 3. **`articles.create({status: "published"})` and the article does not appear in the live library.** Per [release notes](/developers/changelog), as of `@cstar.help/js@0.13.0`, `is_public` defaults to `true` when `status === "published"`. Earlier versions required passing `isPublic: true` explicitly.