Operations
Troubleshooting
Diagnose API, authentication, database, bootstrap, and local evaluation problems in FlagForge
Troubleshooting
FlagForge failures usually become easier to isolate by checking the API first, then the credential type, environment name, and SDK configuration. Use this order before changing flag targeting.
Start with the service
Run the unauthenticated health check against the host and port where the API is listening:
curl -i http://localhost:4000/healthA healthy response has status 200 and a body like:
{"status":"ok","uptime":12.34}The endpoint checks both the running API process and database connectivity. A 503 response with "status":"degraded" means the process is up but the database check failed. Inspect DATABASE_URL, confirm that the configured database is available, and restart the API after correcting its environment.
Note:
/health does not require an API key. Use /docs for the interactive Swagger UI and /openapi.json for the live API contract once the service responds.
Check the service, credentials, and resource names
Confirm that
http://localhost:4000/healthreturnsstatus: ok.Send the request with an explicit
Authorization: Bearer <api-key>header and the correct key type.Verify that the project, flag, and environment names in the request are the ones configured in FlagForge.
Authentication failures
Every protected request uses a Bearer token:
curl -i http://localhost:4000/v1/projects \
-H "Authorization: Bearer $FLAGFORGE_SERVER_KEY"Check the response and credential prefix against this table:
| Symptom | Likely cause | Check |
|---|---|---|
401 with Missing or malformed Authorization header | The header is absent, misspelled, or not in Bearer form | Use exactly Authorization: Bearer <api-key>; avoid quotes becoming part of the token. |
401 with Invalid API key | The token is incorrect or not present in this API database | Compare the value with the server or client key issued by this FlagForge instance. |
403 on a management route | A client key was used for project management | Use a srv_ server key. Client keys are for evaluation. |
403 on a different project | A project-scoped server key is outside its scope | Use the key belonging to the requested project or a global server key. |
400 saying evaluation requires a project-scoped key | A global server key was used on an evaluation route | Use a project-scoped cli_ client key, or a project-scoped server key. |
Client keys are project-scoped and should be used by the SDK. Server keys are for management operations. Do not put a server key in application code that may be exposed to users.
Database and startup problems
The API validates its environment configuration at startup. The relevant settings are PORT, HOST, LOG_LEVEL, DATABASE_URL, and ADMIN_SERVER_KEY. If you do not set them, the defaults include port 4000, host 0.0.0.0, log level info, and SQLite URL file:./dev.db.
For a local SQLite setup, run the database setup command before starting the service:
pnpm --filter @flagforge/api db:setup
pnpm --filter @flagforge/api devFor PostgreSQL, set DATABASE_URL to the connection URL supported by your Prisma configuration before running setup. If startup fails or /health returns 503, check for these common issues:
The URL has a typo, uses an unreachable host, or contains invalid credentials.
The database schema has not been created for the current URL.
The running process did not receive the environment variables you changed.
A relative SQLite URL points to a different working directory than expected.
Note:
Changing DATABASE_URL can make an otherwise valid key, project, or flag appear to be missing because the API is now connected to a different database.
SDK bootstrap failures
FlagForgeClient.init() calls GET /v1/config?environment=<environment> and must complete before local evaluations use bootstrapped definitions. A failed bootstrap rejects init() with an error in this form:
FlagForge bootstrap failed: 401 UnauthorizedUse this direct check to separate SDK configuration from API behavior:
curl -i "http://localhost:4000/v1/config?environment=production" \
-H "Authorization: Bearer $FLAGFORGE_CLIENT_KEY"Then verify the following:
baseUrlis the API origin, such ashttp://localhost:4000, without an extra/v1path.clientKeyis a validcli_key for the project containing the flags.environmentexactly matches the environment key; names are not interchangeable with display names.The API and SDK are using the same FlagForge instance and database.
The URL is reachable from the application process, not merely from your workstation.
The SDK removes one trailing slash from baseUrl, but it does not repair an incorrect host, path, key, or environment. In Node.js runtimes without a global fetch, provide the SDK's fetch option.
Unexpected evaluation results
Use detailedVariation() while diagnosing instead of looking only at a typed value or fallback:
const result = client.detailedVariation("new-checkout", {
key: user.id,
attributes: { plan: user.plan, country: user.country },
});
console.log(result);The result includes variationKey, value, and a reason. The normal reasons identify the path taken:
| Reason | Meaning | What to inspect |
|---|---|---|
OFF | The flag is disabled | The flag's off variation and enabled state. |
RULE_MATCH | An ordered targeting rule matched | Rule conditions, segment membership, and the returned variation key. |
FALLTHROUGH | No targeting rule matched | The environment's defaultVariationKey. |
ERROR | Evaluation could not resolve a valid variation | Variation keys referenced by the flag, rules, rollouts, and off/default configuration. |
Targeting is evaluated per environment. Confirm that the SDK environment and the environment used when managing the flag are identical. For a rollout, use a stable context.key; changing it changes the deterministic bucket. If a rollout uses an attribute, ensure that attribute exists and has the expected value in context.attributes.
If a flag is unknown or evaluation returns an error, variation() returns its supplied default. Typed helpers also protect their return type: getString, getNumber, and getJson fall back when the resolved value is not the expected type. This can make a configuration problem look like a valid default, so inspect detailedVariation() during diagnosis.
Polling and change listeners
Polling is disabled when pollIntervalMs is 0 or omitted. A positive interval starts background refresh after the initial init() succeeds:
const client = new FlagForgeClient({
baseUrl: "http://localhost:4000",
clientKey: process.env.FLAGFORGE_CLIENT_KEY!,
environment: "production",
pollIntervalMs: 30_000,
});
client.onChange(() => {
console.log("Flag definitions refreshed");
});
await client.init();If changes are not appearing, check that:
pollIntervalMsis a positive number in milliseconds.The process remains able to reach the API and the client key remains valid.
The listener is registered on the same client instance that was initialized.
You are not expecting polling before
init()has completed.
A background refresh that fails is ignored by the polling timer; call refresh() yourself when you need to observe and handle the rejection. Call close() when the client is no longer needed to stop polling and release listeners.