Skip to content
Gauge

ax-check.com

Statsig

statsig.com

ax-check.com

Checked 6 October 2026

B

65/100

Agents need your login

Statsig quickstart and pricing are clear, but llms.txt is missing

16 of 23 checks passed: install steps, code samples, and pricing are all stated plainly. No llms.txt index exists (404), so doc discovery relies on normal navigation.

3 fixes to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html for a text/markdown Accept header; no Markdown representation served.

Docs

Add links to your product documentation and quickstart in /llms.txt.

https://statsig.com/llms.txt returns 404, so no documentation index exists.

Recorded coding sessions

Same task, 3 models, no statsig.com account. Recorded 6 October 2026 on Pi.

3 recorded · 2m 54s

2m 54s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelQwen 3.8 Maxcwd/sandbox/repo
Help me build a simple example using Statsig. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

The user wants a simple Statsig example. Let me first check the repo, then think about Statsig. Statsig requires an API key from a hosted account — I can't sign up. So it's likely blocked on credentials. Let me check the environment for any keys.

Let me inspect the repo first.

I'll start by checking what's in the repo and whether any Statsig credentials are available.

ls -la /sandbox/repo && echo "---" && cat /sandbox/repo/README* 2>/dev/null | head -50
total 16
total 16
drwxr-xr-x 3 root root 4096 Sep 18 08:00 .
drwxr-xr-x 7 root root 4096 Oct  6 19:58 ..
drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git
-rw-r--r-- 1 root root  408 Sep 18 08:00 .gitignore
---
env | grep -iE 'statsig|feature|flag' ; echo "--- env keys count:"; env | wc -l; echo "--- dotfiles:"; ls -la ~ 2>/dev/null | head -20
--- env keys count:
--- env keys count:
40
--- dotfiles:
total 28
drwxr-xr-x  7 root root 4096 Oct  6 19:58 .
drwxr-xr-x 22 root root 4096 Sep 18 07:59 ..
drwxr-xr-x  3 root root 4096 Oct  6 19:58 .cache
drwxr-xr-x  3 root root 4096 Oct  6 19:58 .gauge
drwxr-xr-x  3 root root 4096 Oct  6 19:58 .pi
drwxrwxrwt  2 root root 4096 Sep 18 08:00 .tmp
drwxr-xr-x  3 root root 4096 Sep 18 08:00 repo
Thinking…

Empty repo, no Statsig keys. Let me check network access and whether the Statsig SDK can be installed. Statsig has a public SDK; but it needs a server-side secret key to fetch gates. Without credentials, I can't do a real end-to-end example. Let me check network availability to see if I can at least install the SDK.

Let me check python/node availability and network.

Empty repo, no Statsig credentials in the environment. Let me check network access and available runtimes.

cd /tmp && node -v; python3 -V; echo "--- npm registry reachability:"; timeout 20 curl -s -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/statsig-node-core; echo "--- statsig api reachability:"; timeout 20 curl -s -o /dev/null -w "%{http_code}\n" https://featureassets.org/v1/check_gate
v22.23.2
v22.23.2
Python 3.12.3
--- npm registry reachability:
200
--- statsig api reachability:
403
cd /tmp && timeout 25 curl -s https://api.statsig.com/v1/rulesets -H "STATSIG-SERVER-SECRET: secret-invalid-test" -w "\nHTTP:%{http_code}\n" | tail -3
RBAC: access denied
RBAC: access denied
HTTP:403
Thinking…

Network works, npm registry works, Statsig endpoints reachable (403 due to invalid key — expected). So I can install the SDK and build a working example. But without a real secret key I cannot demonstrate a live flag evaluation.

Can I sign up for a Statsig account programmatically? No — signup requires email verification, a UI, org creation. That's a blocker for a live end-to-end run. But I can still build the example with the SDK and demonstrate the offline/bootstrap path — Statsig's Node SDK supports "bootstrap" values, meaning you can inject gate values without a network call. Actually, statsig-node-core supports initialization with a data adapter, and there's also Statsig.initialize with network config. There's a way to run offline: statsig-node has an offline mode / DataAdapter where you can supply local values.

Let me be honest about the situation: The Statsig workflow requires creating an account in a web console to get a secret key. That's a human step. I cannot do the live part. But I can:

  1. Install the SDK.
  2. Write a runnable example that uses a bootstrap/offline data adapter so it runs without a key, and clearly shows where the real key goes.
  3. Verify it runs.

Let me check the actual statsig-node-core API to make sure I get this right. Let me install and inspect.

Let me install statsig-node-core in the repo.

Network and npm work; the Statsig endpoints respond (403 = invalid key, as expected). Let me install the SDK and inspect its real API.

cd /sandbox/repo && npm init -y >/dev/null 2>&1 && timeout 120 npm install statsig-node-core 2>&1 | tail -5; echo "---"; node -e "console.log(require('/sandbox/repo/node_modules/statsig-node-core/package.json').version)"
Failednpm error code ENOVERSIONS
npm error code ENOVERSIONS
npm error No versions available for statsig-node-core
npm error A complete log of this run can be found in: /sandbox/.npm/_logs/2026-10-06T19_58_36_319Z-debug-0.log
---
node:internal/modules/cjs/loader:1433
  throw err;
  ^

Error: Cannot find module '/sandbox/repo/node_modules/statsig-node-core/package.json'
Require stack:
- /sandbox/repo/[eval]
    at Function._resolveFilename (node:internal/modules/cjs/loader:1430:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1040:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1045:22)
    at Function._load (node:internal/modules/cjs/loader:1216:25)
    at wrapModuleLoad (node:internal/modules/cjs/loader:254:19)
    at Module.require (node:internal/modules/cjs/loader:1527:12)
    at require (node:internal/modules/helpers:147:16)
    at [eval]:1:13
    at runScriptInThisContext (node:internal/vm:209:10)
    at node:internal/process/execution:446:12 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/sandbox/repo/[eval]' ]
}

Node.js v22.23.2


Command exited with code 1
Thinking…

The package name is wrong. Let me check what Statsig packages exist. @statsig/node-sdk is the newer one. Let me query the registry.

Wrong package name. Let me find the correct current package.

cd /tmp && for p in @statsig/node-sdk statsig-node statsig-node-core @statsig/js-client; do echo "== $p"; timeout 20 curl -s "https://registry.npmjs.org/$(echo $p|sed 's|/|%2f|')" | python3 -c "
import sys,json
d=json.load(sys.stdin)
if 'dist-tags' in d: print('latest:',d['dist-tags'].get('latest'),'| deprecated?',d.get('versions',{}).get(d['dist-tags']['latest'],{}).get('deprecated'))
else: print('ERR',str(d)[:100])
"; done
== @statsig/node-sdk
== @statsig/node-sdk
ERR {'error': 'Not found'}
== statsig-node
latest: 6.5.2 | deprecated? None
== statsig-node-core
ERR {'_id': 'statsig-node-core', 'name': 'statsig-node-core', 'time': {'created': '2025-01-31T01:22:58.4
== @statsig/js-client
latest: 3.33.5 | deprecated? None
Thinking…

So statsig-node v6.5.2 is the current Node server SDK. Let me install it and inspect its API.

Note: I should verify whether statsig-node v6 supports offline/bootstrap. Let me install and inspect exports.

statsig-node v6.5.2 is the current server SDK. Installing and inspecting its API.

cd /sandbox/repo && timeout 180 npm install statsig-node 2>&1 | tail -3; echo "=== version:"; node -e "console.log(require('statsig-node/package.json').version)"
run `npm fund` for details
  run `npm fund` for details

found 0 vulnerabilities
=== version:
6.5.2
Thinking…

Installed. Now inspect the exports to see what's available for offline/bootstrap initialization.

cd /sandbox/repo && node -e "
const s=require('statsig-node');
console.log(Object.keys(s).sort().join('\n'));
" 2>&1 | head -40
DataAdapterKeyPath
DataAdapterKeyPath
DynamicConfig
Layer
StatsigInvalidArgumentError
StatsigLocalModeNetworkError
StatsigServer
StatsigTooManyRequestsError
StatsigUninitializedError
_enforceServer
checkGate
checkGateSync
checkGateWithExposureLoggingDisabled
checkGateWithExposureLoggingDisabledSync
clearAllConfigOverrides
clearAllGateOverrides
clearAllLayerOverrides
default
flush
getAutotuneList
getClientInitializeResponse
getConfig
getConfigSync
getConfigWithExposureLoggingDisabled
getConfigWithExposureLoggingDisabledSync
getDynamicConfigList
getExperiment
getExperimentLayer
getExperimentList
getExperimentSync
getExperimentWithExposureLoggingDisabled
getExperimentWithExposureLoggingDisabledSync
getFeatureGate
getFeatureGateList
getFeatureGateSync
getFeatureGateWithExposureLoggingDisabled
getLayer
getLayerList
getLayerSync
getLayerWithExposureLoggingDisabled
getLayerWithExposureLoggingDisabledSync
cd /sandbox/repo && ls node_modules/statsig-node/dist/types/ 2>/dev/null; echo "=== DataAdapter / offline refs:"; grep -rlioE 'offline|bootstrap' node_modules/statsig-node/dist/types/ | head -20
=== DataAdapter / offline refs:
=== DataAdapter / offline refs:
grep: node_modules/statsig-node/dist/types/: No such file or directory
Thinking…

There's StatsigLocalModeNetworkError and _enforceServer — interesting, that suggests a local mode exists. Let me look for local mode / data adapter support in the package.

Let me check the exports more and look for DataAdapter, StatsigLocal, overrides.

There's a StatsigLocalModeNetworkError export — suggesting a local mode. Let me investigate.

cd /sandbox/repo && node -e "const s=require('statsig-node');console.log(Object.keys(s).sort().join('\n'))" 2>&1 | tail -30; echo "=== files:"; ls node_modules/statsig-node/dist/ | head; echo "=== local mode refs:"; grep -rlioE 'localMode|DataAdapter|setOverride' node_modules/statsig-node/dist/*.js node_modules/statsig-node/dist/**/*.js 2>/dev/null | head
getExperiment
getExperiment
getExperimentLayer
getExperimentList
getExperimentSync
getExperimentWithExposureLoggingDisabled
getExperimentWithExposureLoggingDisabledSync
getFeatureGate
getFeatureGateList
getFeatureGateSync
getFeatureGateWithExposureLoggingDisabled
getLayer
getLayerList
getLayerSync
getLayerWithExposureLoggingDisabled
getLayerWithExposureLoggingDisabledSync
getUserPersistedValues
initialize
logEvent
logEventObject
manuallyLogConfigExposure
manuallyLogExperimentExposure
manuallyLogGateExposure
manuallyLogLayerParameterExposure
overrideConfig
overrideGate
overrideLayer
shutdown
shutdownAsync
syncConfigSpecs
syncIdLists
=== files:
ConfigEvaluation.d.ts
ConfigEvaluation.js
ConfigSpec.d.ts
ConfigSpec.js
Diagnostics.d.ts
Diagnostics.js
DynamicConfig.d.ts
DynamicConfig.js
ErrorBoundary.d.ts
ErrorBoundary.js
=== local mode refs:
node_modules/statsig-node/dist/ErrorBoundary.js
node_modules/statsig-node/dist/Errors.js
node_modules/statsig-node/dist/LogEventProcessor.js
node_modules/statsig-node/dist/SpecStore.js
node_modules/statsig-node/dist/StatsigOptions.js
node_modules/statsig-node/dist/StatsigServer.js
node_modules/statsig-node/dist/index.js
node_modules/statsig-node/dist/interfaces/IDataAdapter.js
node_modules/statsig-node/dist/utils/IDListUtil.js
node_modules/statsig-node/dist/utils/StatsigFetcher.js
Thinking…

There's overrideGate, overrideConfig, overrideLayer and an IDataAdapter. And initialize supports options. Let me look at StatsigOptions to see if there's a dataAdapter option and a local mode (no network) option. This would let me build a genuinely runnable example without a real key.

Let me check StatsigOptions.d.ts and IDataAdapter.d.ts.

Good — there's an IDataAdapter and override APIs. Let me check the options for a no-network mode.

cd /sandbox/repo && cat node_modules/statsig-node/dist/StatsigOptions.d.ts | head -80
import DynamicConfig from './DynamicConfig';
import DynamicConfig from './DynamicConfig';
import { FeatureGate } from './FeatureGate';
import { IDataAdapter } from './interfaces/IDataAdapter';
import { IUserPersistentStorage, UserPersistedValues } from './interfaces/IUserPersistentStorage';
import Layer from './Layer';
import LogEvent from './LogEvent';
import { StatsigUser } from './StatsigUser';
import { HashingAlgorithm } from './utils/Hashing';
export type RulesUpdatedCallback = (rulesJSON: string, time: number) => void;
export type RetryBackoffFunc = (retriesRemaining: number) => number;
type StringLiteralOrString<T extends string> = T | (string & {});
export type StatsigEnvironment = {
    tier?: StringLiteralOrString<'production' | 'staging' | 'development'>;
    [key: string]: string | undefined;
};
export type InitStrategy = 'await' | 'lazy' | 'none';
export interface LoggerInterface {
    debug?(message?: any, ...optionalParams: any[]): void;
    info?(message?: any, ...optionalParams: any[]): void;
    warn(message?: any, ...optionalParams: any[]): void;
    error(message?: any, ...optionalParams: any[]): void;
    logLevel: 'none' | 'debug' | 'info' | 'warn' | 'error';
}
export type NetworkOverrideFunc = (url: string, params: RequestInit) => Promise<Response>;
export type EvaluationCallbacks = {
    gateCallback?: (gate: FeatureGate, user: StatsigUser, event: LogEvent) => void;
    dynamicConfigCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;
    experimentCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void;
    layerCallback?: (layer: Layer, user: StatsigUser) => void;
    layerParamCallback?: (layer: Layer, paramName: string, user: StatsigUser, event: LogEvent) => void;
};
export type ExplicitStatsigOptions = {
    api: string;
    apiForDownloadConfigSpecs: string;
    apiForGetIdLists: string;
    fallbackToStatsigAPI: boolean;
    networkOverrideFunc: NetworkOverrideFunc | null;
    bootstrapValues: string | null;
    environment: StatsigEnvironment | null;
    rulesUpdatedCallback: RulesUpdatedCallback | null;
    logger: LoggerInterface;
    localMode: boolean;
    initTimeoutMs: number;
    dataAdapter: IDataAdapter | null;
    rulesetsSyncIntervalMs: number;
    idListsSyncIntervalMs: number;
    loggingIntervalMs: number;
    loggingMaxBufferSize: number;
    disableDiagnostics: boolean;
    initStrategyForIP3Country: InitStrategy;
    initStrategyForIDLists: InitStrategy;
    postLogsRetryLimit: number;
    postLogsRetryBackoff: RetryBackoffFunc | number;
    disableRulesetsSync: boolean;
    disableIdListsSync: boolean;
    disableAllLogging: boolean;
    userPersistentStorage: IUserPersistentStorage | null;
    evaluationCallback?: (config: FeatureGate | DynamicConfig | Layer) => void;
    evaluationCallbacks?: EvaluationCallbacks;
};
/**
 * An object of properties for initializing the sdk with advanced options
 */
export type StatsigOptions = Partial<ExplicitStatsigOptions>;
export declare function OptionsWithDefaults(opts: StatsigOptions): ExplicitStatsigOptions;
export declare function OptionsLoggingCopy(options: Record<string, unknown>): StatsigOptions;
export type PersistentAssignmentOptions = {
    enforceTargeting?: boolean;
};
export type CheckGateOptions = CoreApiOptions;
export type GetConfigOptions = CoreApiOptions;
export type GetExperimentOptions = CoreApiOptions & {
    userPersistedValues?: UserPersistedValues | null;
    persistentAssignmentOptions?: PersistentAssignmentOptions;
};
export type GetLayerOptions = CoreApiOptions & {
    userPersistedValues?: UserPersistedValues | null;
    persistentAssignmentOptions?: PersistentAssignmentOptions;
};
export type CoreApiOptions = {
cd /sandbox/repo && cat node_modules/statsig-node/dist/interfaces/IDataAdapter.d.ts; echo "=== initialize sig:"; cat node_modules/statsig-node/dist/index.d.ts 2>/dev/null | head -40
export type AdapterResponse = {
export type AdapterResponse = {
    result?: string | object;
    time?: number;
    error?: Error;
};
export declare enum DataAdapterKeyPath {
    V1Rulesets = "/v1/download_config_specs",
    V2Rulesets = "/v2/download_config_specs",
    V1IDLists = "/v1/get_id_lists",
    IDList = "id_list"
}
export declare enum CompressFormat {
    PlainText = "plain_text",
    Gzip = "gzip"
}
export declare function getDataAdapterKey(hashedSDKKey: string, path: DataAdapterKeyPath, format?: CompressFormat, idListName?: string | undefined): string;
/**
 * An adapter for implementing custom storage of config specs.
 * Useful for backing up data in memory.
 * Can also be used to bootstrap Statsig server.
 */
export interface IDataAdapter {
    /**
     * Returns the data stored for a specific key
     * @param key - Key of stored item to fetch
     */
    get(key: string): Promise<AdapterResponse>;
    /**
     * Updates data stored for each key
     * @param key - Key of stored item to update
     * @param value - New value to store
     * @param time - Time of update
     */
    set(key: string, value: string, time?: number): Promise<void>;
    /**
     * Startup tasks to run before any fetch/update calls can be made
     */
    initialize(): Promise<void>;
    /**
     * Cleanup tasks to run when statsig is shutdown
     */
    shutdown(): Promise<void>;
    /**
     * Determines whether the SDK should poll for updates from
     * the data adapter for the given key
     * @param key - Key of stored item to poll from data adapter
     */
    supportsPollingUpdatesFor?(key: DataAdapterKeyPath): boolean;
}
=== initialize sig:
import DynamicConfig from './DynamicConfig';
import { StatsigInvalidArgumentError, StatsigLocalModeNetworkError, StatsigTooManyRequestsError, StatsigUninitializedError } from './Errors';
import { ClientInitializeResponse } from './Evaluator';
import { FeatureGate } from './FeatureGate';
import { InitializationDetails } from './InitializationDetails';
import { AdapterResponse, DataAdapterKeyPath, IDataAdapter } from './interfaces/IDataAdapter';
import { UserPersistedValues } from './interfaces/IUserPersistentStorage';
import Layer from './Layer';
import { CheckGateOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseOptions, ClientInitializeResponseValueOverride, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, GetConfigOptions, GetExperimentOptions, GetLayerOptions, InitStrategy, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, RulesUpdatedCallback, StatsigEnvironment, StatsigOptions } from './StatsigOptions';
import StatsigServer, { LogEventObject } from './StatsigServer';
import { StatsigUser } from './StatsigUser';
export type { AdapterResponse, LogEventObject, StatsigUser, InitializationDetails, FeatureGate, RulesUpdatedCallback, StatsigOptions, GetExperimentOptions, GetLayerOptions, StatsigEnvironment, InitStrategy, CoreApiOptions, EvaluationCallbacks, ExplicitStatsigOptions, LoggerInterface, NetworkOverrideFunc, PersistentAssignmentOptions, RetryBackoffFunc, ClientInitializeResponseOptions, ClientInitializeResponseExperimentOverride, ClientInitializeResponseValueOverride, };
export * from './StatsigOptions';
export { DynamicConfig, IDataAdapter, DataAdapterKeyPath, Layer, StatsigServer, };
export declare const Statsig: {
    /**
     * Initializes the statsig server SDK.
     * This must be called before checking gates/configs or logging events.
     *
     * @param {string} secretKey - The secret key for this project from the statsig console. Secret keys should be kept secure on the server side, and not used for client-side integrations
     * @param {?StatsigOptions} [options={}] - manual sdk configuration for advanced setup
     * @returns {Promise<void>} - a promise which rejects only if you fail to provide a proper SDK Key
     * @throws Error if a Server Secret Key is not provided
     */
    initialize(secretKey: string, options?: StatsigOptions): Promise<InitializationDetails>;
    /**
     * Gets the boolean result of a gate, evaluated against the given user.
     * An exposure event will automatically be logged for the gate.
     *
     * @param {StatsigUser} user - the user to check this gate value for
     * @param {string} gateName - the name of the gate to check
     * @param {CheckGateOptions} options - the options on how gate should be checkced
     * @returns {boolean} - The value of the gate for the user.  Gates are off (return false) by default
     * @throws Error if initialize() was not called first
     */
    checkGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): boolean;
    getFeatureGate(user: StatsigUser, gateName: string, options?: CheckGateOptions): FeatureGate;
    /**
     * Logs an exposure event for the gate
     *
Thinking…

There's a localMode: boolean option. Local mode means no network calls — the SDK uses overrides only. That's exactly what I need to build a runnable example without a real secret key. Let me check what local mode does — StatsigLocalModeNetworkError implies network calls throw in local mode. Let me check StatsigServer for localMode handling and whether initialize requires a valid key format.

Key question: does initialize require a key that looks like secret-...? Let me check. And in local mode with overrides, does checkGate work?

Let me test empirically — that's fastest.

There's a localMode option — no network calls, overrides only. Let me test empirically whether it runs without a real key.

cd /sandbox/repo && timeout 60 node -e "
const {Statsig}=require('statsig-node');
(async()=>{
  try{
    const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});
    console.log('init OK:',JSON.stringify(d,null,1).slice(0,400));
    Statsig.overrideGate('demo_gate',true);
    const u={userID:'user-1'};
    console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));
    await Statsig.shutdownAsync();
    console.log('shutdown ok');
  }catch(e){console.log('ERR:',e.constructor.name,e.message);}
})();
" 2>&1 | tail -25
ERR: TypeError Cannot read properties of undefined (reading 'initialize')
ERR: TypeError Cannot read properties of undefined (reading 'initialize')
Thinking…

The default export. require('statsig-node') — Statsig is exported as default and also Statsig? Earlier keys showed Statsig in list? Let me check: the keys list included default and also StatsigServer, but I saw Statsig maybe not. Let me check the actual export shape.

Earlier list: DataAdapterKeyPath, DynamicConfig, Layer, StatsigInvalidArgumentError, StatsigLocalModeNetworkError, StatsigServer, StatsigTooManyRequestsError, StatsigUninitializedError, _enforceServer, checkGate, checkGateSync, ... default, flush, ...

So top-level functions are exported directly, and default is the Statsig object. Let me use default.

Exports are flat + a default. Let me retry with the correct shape.

cd /sandbox/repo && timeout 60 node -e "
const sdk=require('statsig-node');
const Statsig=sdk.default||sdk.Statsig;
(async()=>{
  try{
    const d=await Statsig.initialize('secret-fake-not-real',{localMode:true});
    console.log('init OK. details:',JSON.stringify(d).slice(0,300));
    Statsig.overrideGate('demo_gate',true);
    const u={userID:'user-1'};
    console.log('gate demo_gate =',Statsig.checkGate(u,'demo_gate'));
    Statsig.overrideConfig('demo_config',{color:'blue',maxItems:7});
    console.log('config =',JSON.stringify(Statsig.getConfig(u,'demo_config').value));
    await Statsig.shutdownAsync();
    console.log('shutdown ok');
  }catch(e){console.log('ERR:',e.constructor.name,e.message);}
})();
" 2>&1 | tail -20
init OK. details: {"duration":33,"success":true}
init OK. details: {"duration":33,"success":true}
gate demo_gate = true
config = {"color":"blue","maxItems":7}
shutdown ok
Thinking…

Local mode works without a real key. So I can build a genuinely runnable example. Now let me write a clean example app that:

  1. Reads a real secret key from env if present → live mode against the hosted Statsig API.
  2. Falls back to localMode with overrides for a demo without credentials.

This is the honest, useful approach: same code path, real SDK, works either way.

Let me write the files. I'll make a small example: a feature-gated feature + a dynamic config + event logging.

Let me write src/index.js (CommonJS since package.json is commonjs by default) and a README explaining pricing and how to go live.

Actually let me reconsider structure — keep it simple: one runnable file example.js or src/app.js. Plus README.

Let me build it properly and verify it runs in both modes.

I'll write the main example.

Local mode runs without a real key — so I can build a genuinely runnable example. Let me write it so it uses the real hosted API when a key is present, and falls back to local overrides otherwise.

File edits

1 file

Completed
Writtensrc/app.jsView changes

/sandbox/repo/src/app.js

Written content · previous contents were not recorded

'use strict';
/**
* Minimal Statsig server-side example using the official `statsig-node` SDK.
*
* Two modes, same code path:
*
* LIVE - STATSIG_SECRET_KEY is set. The SDK downloads your real rules from
* the hosted Statsig API, so gates/configs reflect the console.
* LOCAL - No key available. `localMode: true` makes the SDK evaluate without
* any network calls, and we supply values via overrides.
*
* Run: node src/app.js
*/
const sdk = require('statsig-node');
const Statsig = sdk.default || sdk.Statsig;
const SECRET_KEY = process.env.STATSIG_SECRET_KEY || '';
const LIVE = Boolean(SECRET_KEY);
// Values only used in LOCAL mode, so the demo shows something meaningful
// without credentials. In LIVE mode these are ignored and the console wins.
const LOCAL_OVERRIDES = {
gates: {
'new-checkout-flow': true,
'dark-mode': false,
},
configs: {
'pricing-banner': { headline: 'Summer sale', discountPct: 15 },
},
};
function applyLocalOverrides() {
for (const [gate, value] of Object.entries(LOCAL_OVERRIDES.gates)) {
Statsig.overrideGate(gate, value);
}
for (const [config, value] of Object.entries(LOCAL_OVERRIDES.configs)) {
Statsig.overrideConfig(config, value);
}
}
async function main() {
console.log(`mode: ${LIVE ? 'LIVE (hosted Statsig API)' : 'LOCAL (offline overrides)'}`);
// initialize() must be awaited before any evaluation. In LIVE mode it fetches
// the ruleset once at boot; evaluations afterwards are in-memory and fast.
const details = await Statsig.initialize(
LIVE ? SECRET_KEY : 'secret-local-mode-placeholder',
{
localMode: !LIVE,
// Keep the example quiet; set logLevel:'debug' when troubleshooting.
logger: { logLevel: 'none', warn() {}, error() {} },
},
);
console.log(`initialized in ${details.duration}ms (success=${details.success})`);
if (!LIVE) applyLocalOverrides();
// ---- 1. Feature gates: boolean on/off, the core use case -----------------
const users = [
{ userID: 'user-100', email: 'ada@example.com', country: 'US' },
{ userID: 'user-200', email: 'linus@example.com', country: 'DE' },
];
for (const user of users) {
const newCheckout = Statsig.checkGate(user, 'new-checkout-flow');
// getFeatureGate() also exposes the rule that matched — useful for debugging.
const darkMode = Statsig.getFeatureGate(user, 'dark-mode');
console.log(
`\n${user.userID}: new-checkout-flow=${newCheckout} ` +
`dark-mode=${darkMode.value} (rule="${darkMode.ruleID}")`,
);
// ---- 2. Dynamic configs: typed JSON values with defaults ---------------
const banner = Statsig.getConfig(user, 'pricing-banner');
console.log(
` pricing-banner headline="${banner.get('headline', 'Default headline')}" ` +
`discount=${banner.get('discountPct', 0)}%`,
);
// ---- 3. Events: custom metrics feeding Statsig's experiment analysis ---
Statsig.logEvent(user, 'checkout_started', { value: 42.5, currency: 'USD' });
}
// Exposures for gates/configs are logged automatically by checkGate/getConfig,
// which is what powers experiment analysis in the console.
await Statsig.flush();
await Statsig.shutdownAsync();
console.log('\ndone: events flushed, SDK shut down cleanly');
}
main().catch((err) => {
console.error('statsig example failed:', err);
process.exitCode = 1;
});
Thinking…

Now let me verify it runs in local mode. Also add npm script and README.

Let me run it.

cd /sandbox/repo && timeout 60 node src/app.js 2>&1 | tail -20; echo "EXIT=$?"
mode: LOCAL (offline overrides)
mode: LOCAL (offline overrides)
initialized in 30ms (success=true)

user-100: new-checkout-flow=true dark-mode=false (rule="override")
  pricing-banner headline="Summer sale" discount=15%

user-200: new-checkout-flow=true dark-mode=false (rule="override")
  pricing-banner headline="Summer sale" discount=15%

done: events flushed, SDK shut down cleanly
EXIT=0
Thinking…

Runs cleanly. Now let me verify the LIVE path is at least correctly wired — I can't get a real key, but I can confirm the failure mode is graceful with an invalid key. Let me test with a fake key to show what happens (it should fail to download specs but still work, defaulting to false).

Let me test that.

Runs cleanly. Let me verify the LIVE path behaves gracefully with an invalid key (to confirm the wiring without having a real key).

cd /sandbox/repo && timeout 90 env STATSIG_SECRET_KEY=secret-not-a-real-key node src/app.js 2>&1 | tail -15; echo "EXIT=${PIPESTATUS[0]}"
mode: LIVE (hosted Statsig API)
mode: LIVE (hosted Statsig API)
initialized in 661ms (success=false)

user-100: new-checkout-flow=false dark-mode=false (rule="")
  pricing-banner headline="Default headline" discount=0%

user-200: new-checkout-flow=false dark-mode=false (rule="")
  pricing-banner headline="Default headline" discount=0%
EXIT=0
Thinking…

As expected: with an invalid key, success=false, gates default to false, config defaults apply. This is the safe fail-open/closed behavior. Good — the wiring is correct; with a real key it would download real specs.

Now let me finish: package.json script, .gitignore check, README with pricing + how to go live.

Let me check .gitignore and add npm script.

Also I should double check pricing info for Statsig. Let me be careful about pricing claims — I should verify rather than assert from memory. Let me fetch the pricing page to be accurate.

Let me check .gitignore + package.json and fetch pricing.

The LIVE wiring is correct (invalid key → success=false, gates safely default to false). Let me finalize packaging and verify current pricing rather than rely on memory.

cd /sandbox/repo && cat .gitignore; echo "=== pkg:"; cat package.json
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs.

# Logs / OS cruft
*.log
.DS_Store

# JavaScript / TypeScript
node_modules/
.next/
dist/
build/
coverage/

# Python
.venv/
venv/
__pycache__/
*.py[cod]
.pytest_cache/
*.egg-info/

# Rust
target/

# Go / PHP / Ruby vendored deps
vendor/
.bundle/

# Java / Kotlin / Scala
.gradle/

# .NET
bin/
obj/

# Elixir
_build/
deps/

# Dart / Flutter
.dart_tool/
=== pkg:
{
  "name": "repo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "statsig-node": "^6.5.2"
  }
}
cd /tmp && timeout 40 curl -sL https://statsig.com/pricing 2>/dev/null | python3 -c "
import sys,re,html
t=sys.stdin.read()
t=re.sub(r'<script.*?</script>','',t,flags=re.S|re.I)
t=re.sub(r'<style.*?</style>','',t,flags=re.S|re.I)
t=re.sub(r'<[^>]+>',' ',t)
t=html.unescape(t)
t=re.sub(r'\s+',' ',t)
for kw in ['free','Free','\\\$','per month','MAU','seats','1M','unlimited']:
    for m in re.finditer(kw,t):
        s=max(0,m.start()-90); print('...'+t[s:m.start()+120].strip()+'...')
" 2>/dev/null | sort -u | head -40
...) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per
...) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & e...
...When will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages...
...an additional log event for each additional dimension added. What happens if I exceed my free events limit? If you exceed the 2 million Developer Tier events limit, you’ll be shown a prompt to enter a credit c...
...card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web...
...cludes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing period. This overage fee will be included in your monthly b...
...d use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charged $0.05 per additional 1K events used within the month billing p...
...ext billing period. For example, if your subscription begins on January 1, you will pay a $150 fee on January 1, and on the first day of each subsequent month. If you incur overages during the month of January,...
...ics separately? All tiers of Statsig includes core product analytics capabilities. On the free tier that includes our Core Product Analytics features and 1 year of data retention for analysis. On the pro-tier,...
...included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer, plus: Advanced experimentation Advanced product analytics Unlimited analy...
...integrations with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experim...
...ion is to be an end-to-end data platform to help businesses build better products. On our free and pro tiers, we want to make that simple and offer a competitively priced solution that spans our product offerin...
...ions with CDPs, observability tools, and other providers Compare Plans Developer Free Pro $150 /mo Enterprise Custom Experimentation Core experimentation Multi-variate experiments Multi-variate experiments A/B/...
...is included at no extra cost. This includes access to all product analytics features and unlimited data retention for analysis. On the enterprise tier, we want to provide the flexibility to pick what makes the...
...limited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month Get Started Everything in Developer...
...management Product and web analytics 1-year analytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & c...
...most affordable offering for each of our 5+ products. Cut your current spend or start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events...
...re no longer calculated. What happens if I’m on the Pro Tier, and use more than 5M events per month? Statsig’s Pro Tier includes 5 million Metered Events per month. Once you exceed this amount, you’ll be charge...
...sign up for Statsig, you automatically start on Statsig’s Developer Tier, which gives you free access to gates, configs, experimentation and analytics. On this plan, you get 2 million Metered Events (see defini...
...start for free (like 90% of our customers) Developer Growth tools for individual builders Free (no credit card required) 2M events per month Unlimited flag & config checks 50,000 session replays per month Start...
...stomers are able to include one event dimension (via Value field) with each log event for free. Customers are charged an additional log event for each additional dimension added. What happens if I exceed my fre...
...vents per month Unlimited flag & config checks 50,000 session replays per month Start for Free Get started with: A/B tests & experimentation Feature flags & config management Product and web analytics 1-year an...
...will Statsig charge me? On Statsig’s Pro tier, you will be charged a baseline fee of $150 per month at the start of each billing period. If you incur overages during a given billing period, those overages will...
...ytics retention Session replays Unlimited seats Pro A complete platform for growing teams $150 /mo 5M events included, then $0.05 per 1K events Unlimited flag & config checks 100,000 session replays per month G...
Thinking…

I have accurate, verified pricing from the live page. Now let me finalize package.json and write a README.

Key verified pricing facts:

  • Developer tier: Free, no credit card. 2M metered events/month. Unlimited flag & config checks. 50,000 session replays/month. Unlimited seats. 1-year analytics retention.
  • Pro: $150/mo baseline, includes 5M metered events, then $0.05 per additional 1K events. 100,000 session replays/month.
  • Enterprise: custom.
  • Important: flag/config checks are unlimited — you pay on metered events (logged events + exposures). Each additional dimension on a log event counts as an extra event.
  • Exceeding 2M on free tier prompts for credit card.

Now write README and update package.json.

Verified pricing from the live page. Now let me finalize packaging and docs.

File edits

1 file

Completed
Modifiedpackage.json+3−2View changes

/sandbox/repo/package.json

Replacement excerpt

- "description": "",
- "main": "index.js",
+ "description": "Minimal Statsig server-side feature flag / dynamic config example",
+ "main": "src/app.js",
"scripts": {
+ "start": "node src/app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Thinking…

Also add .env to gitignore since secret keys shouldn't be committed. Let me add that.

Then write README.

File edits

1 file

Completed
Modified.gitignore+4−0View changes

/sandbox/repo/.gitignore

Replacement excerpt

# Logs / OS cruft
*.log
.DS_Store
+
+# Secrets — never commit Statsig server secret keys
+.env
+.env.*
Thinking…

Let me verify the .env ignore works and then write the README.

cd /sandbox/repo && printf 'STATSIG_SECRET_KEY=secret-abc\n' > .env && git check-ignore -v .env && git status --porcelain
.gitignore:8:.env .env
.gitignore:8:.env	.env
 M .gitignore
?? package-lock.json
?? package.json
?? src/
Thinking…

.env is ignored correctly and not showing in git status. Let me remove the .env test file since it was just a test.

Then write README.

.env is correctly ignored. Let me remove that test file and write the docs.

cd /sandbox/repo && rm -f .env && git status --porcelain
M .gitignore
 M .gitignore
?? README.md
?? package-lock.json
?? package.json
?? src/

File edits

1 file

Completed
WrittenREADME.mdView changes

/sandbox/repo/README.md

Written content · previous contents were not recorded

# Statsig minimal example (server-side)
A small, runnable example using Statsig's official Node server SDK
(`statsig-node` v6). It demonstrates the three things you actually use
Statsig for on a backend:
1. **Feature gates** — boolean flags, evaluated per user.
2. **Dynamic configs** — typed JSON values with code-side defaults.
3. **Event logging** — custom events that feed experiment analysis.
## Run it
```bash
npm install
npm start # or: node src/app.js
```
Runs with **no credentials required**. Without a key the SDK starts in
`localMode` (no network calls) and uses local override values, so you can see
the whole flow immediately.
```
mode: LOCAL (offline overrides)
initialized in 30ms (success=true)
user-100: new-checkout-flow=true dark-mode=false (rule="override")
pricing-banner headline="Summer sale" discount=15%
...
```
## Connecting it to the real hosted console
Statsig is a hosted SaaS — rules live in their console, the SDK downloads them.
To point this example at a real project:
1. Create a free account at https://console.statsig.com
2. **Project Settings → API Keys → Server API Keys**, copy the secret key.
3. Create a gate named `new-checkout-flow` and a config named `pricing-banner`.
4. Export the key and run:
```bash
export STATSIG_SECRET_KEY=secret-xxxx
npm start
```
When the key is present, `src/app.js` switches to LIVE mode automatically:
`localMode` turns off, the SDK fetches your real ruleset at boot, and the local
override values are ignored. You then change behaviour from the console without
deploying.
> Server secret keys are **server-only**. They can read your targeting rules and
> must never ship to a browser or mobile client — use `@statsig/js-client` with
> a *client* SDK key for that. `.env` is gitignored here for this reason.
## Notes on the code
- `Statsig.initialize()` is awaited once at boot; later evaluations are
in-memory, so they're cheap on the request path.
- `checkGate()` / `getConfig()` log exposure events automatically — that's what
powers experiment analysis. Use the `...WithExposureLoggingDisabled` variants
when you want a value without an exposure.
- `getFeatureGate()` returns the matched `ruleID`, which is useful when
debugging why a user got a given value.
- Fail-safe behaviour: if the ruleset can't be downloaded, gates return `false`
and configs fall back to the defaults you pass in code. Verified by running
with an invalid key — init reports `success=false` and the app still runs.
- `flush()` then `shutdownAsync()` on exit so buffered events aren't dropped.
## Pricing
Verified against statsig.com/pricing. Billing is driven by **metered events**
(logged events + exposures), **not** by flag checks.
| Tier | Price | Metered events | Flag/config checks | Seats |
|---|---|---|---|---|
| Developer | Free, no credit card | 2M / month | Unlimited | Unlimited |
| Pro | $150 / month | 5M included, then $0.05 per additional 1K | Unlimited | Unlimited |
| Enterprise | Custom | Custom | Unlimited | Custom |
Details worth knowing:
- **Flag and config checks are unlimited on every tier.** Your read volume does
not drive cost, which is the main structural difference from per-flag or
per-check pricing.
- **One event dimension is free** (via the `value` field) on a log event; each
additional dimension is counted as an extra metered event. The
`{ value: 42.5, currency: 'USD' }` event in this example therefore costs
2 metered events, not 1.
- Exceeding the free 2M/month limit prompts you to add a card rather than
silently cutting off. On Pro, overages are billed on the next invoice.
- Free tier includes core product analytics with 1-year retention; Pro adds
advanced experimentation/analytics and 100,000 session replays/month.
For a small project the free Developer tier is genuinely sufficient — the
example above generates a handful of events per run.
## Layout
```
src/app.js # the whole example: init, gates, config, events, shutdown
package.json # statsig-node dependency + `npm start`
```
Thinking…

Let me do a final verification run of everything and confirm the README claims are accurate. Let me re-run both modes.

Also I claimed the {value, currency} event costs 2 metered events. Let me double check that from the pricing text: "Customers are able to include one event dimension (via Value field) with each log event for free. Customers are charged an additional log event for each additional dimension added." So value is free, currency is an additional dimension → 2 events total. That's accurate.

Let me do a final run to confirm everything works.

cd /sandbox/repo && echo "=== LOCAL ==="; timeout 60 npm start --silent 2>&1 | tail -12; echo "EXIT=${PIPESTATUS[0]}"; echo; echo "=== files ==="; git status --porcelain; echo; echo "=== syntax check ==="; node --check src/app.js && echo "app.js OK"
=== LOCAL ===
=== LOCAL ===
mode: LOCAL (offline overrides)
initialized in 40ms (success=true)

user-100: new-checkout-flow=true dark-mode=false (rule="override")
  pricing-banner headline="Summer sale" discount=15%

user-200: new-checkout-flow=true dark-mode=false (rule="override")
  pricing-banner headline="Summer sale" discount=15%

done: events flushed, SDK shut down cleanly
EXIT=0

=== files ===
 M .gitignore
?? README.md
?? package-lock.json
?? package.json
?? src/

=== syntax check ===
app.js OK
Thinking…

Everything works. Let me write the final summary.

Key points to cover:

  1. What I built
  2. How pricing works (verified)
  3. Whether it will be easy for me to manage — honest assessment
  4. Where I got blocked — the credentials step

On "easy for you to manage": I should be honest. Statsig is a hosted product where the interesting state (rules, targeting, experiments) lives in a web console I cannot access. What I CAN manage: the SDK integration code, which is stable and well-typed. What I CANNOT manage: creating/editing gates, changing targeting, reading experiment results — those require console login, which needs interactive auth/email verification. There's an API (api.statsig.com) with the server secret key that could manage some things programmatically, but creating/managing feature gates via API — let me be careful. Actually Statsig does have some management API but the primary workflow is the console. I tested /v1/rulesets with an invalid key and got "RBAC: access denied", which suggests there IS an API but it needs valid credentials + permissions.

I should be honest: I can't verify the management API without a key. So the honest answer is: code integration = easy for me; ongoing flag management = console-bound, so mostly on you unless you give me an API key with permissions, which I couldn't test.

Let me be clear and concise.

Done. Built and verified a working example — no blockers worth stopping on, with one honest caveat below.

What I built

/sandbox/repo/src/app.js — a runnable Statsig example using the official statsig-node v6 SDK, covering the three real backend use cases: feature gates, dynamic configs, and event logging.

npm install && npm start

It runs right now with no credentials: with no key it uses the SDK's localMode (zero network calls) plus override values; with STATSIG_SECRET_KEY set it switches to live mode and downloads your real rules from the hosted API. Same code path either way, so going live is just exporting a key.

mode: LOCAL (offline overrides)
initialized in 40ms (success=true)
user-100: new-checkout-flow=true dark-mode=false (rule="override")
  pricing-banner headline="Summer sale" discount=15%
done: events flushed, SDK shut down cleanly

I also verified the live path is correctly wired by running it with an invalid key: init reports success=false, gates fall back to false, configs use code defaults, and the app still exits 0. That fail-safe behaviour is the thing most worth knowing before you ship.

I stayed light as asked — no local stack, nothing long-running. Just the SDK plus HTTP calls to the hosted service.

Pricing (verified against statsig.com/pricing)

TierPriceMetered eventsFlag/config checksSeats
DeveloperFree, no card2M/moUnlimitedUnlimited
Pro$150/mo5M, then $0.05/1KUnlimitedUnlimited
EnterpriseCustomCustomUnlimitedCustom

The structural point: flag/config checks are unlimited on every tier. You pay on metered events (logged events + exposures), so read volume doesn't drive cost. One gotcha — one event dimension is free via the value field, and each extra dimension counts as another event. My { value: 42.5, currency: 'USD' } call costs 2 metered events, not 1. Going over the free 2M prompts for a card rather than cutting you off.

Will this be easy for me to manage?

Split answer, and it's worth being precise.

The SDK integration — yes, easy. Well-typed, stable API, one init call, evaluations are in-memory. I can maintain that code indefinitely.

Ongoing flag management — mostly not me. The interesting state (gates, targeting rules, experiment results) lives in the web console, which needs interactive login and email verification I can't do. So creating a gate, changing a rollout percentage, or reading experiment analysis stays with you.

There's also a management API at api.statsig.com — I got RBAC: access denied rather than 404 with a bogus key, so it exists — but I couldn't verify what it covers without real credentials, so I'm not going to claim you could hand flag management to me.

The one caveat

Creating the account and copying a secret key is a human step — signup requires the console UI. Everything after that works. So the example is complete and verified, but I can't take it to a live end-to-end run against your project until you supply a key.

AX Fundamentals

23 checks. Open an area for details.

This check is a quick preview. Test more tasks and models with Gauge Agents.

Test your product with Gauge Agents

AX Check by Gauge