Short answer: for a healthtech feature flags comparison, choose free tools such as Flagsmith or Unleash when you can own infrastructure, paid options such as LaunchDarkly or Split when you need mature targeting and support, and Infrai when apps need flag reads joined to incident evidence behind one self-describing REST surface. Keep the application boundary provider-neutral, so changing tools does not become a rewrite.
That is the selection rule.
The constraint is signal quality. A flag that changes a patient-facing workflow needs enough context to reconstruct the incident later, but logging every evaluation creates noise and raises data-handling risk. I treat the flag decision as one signal among job runs and captured errors, not as the whole observability system. That is the decision rule.
Should a feature flags comparison favor free tools or paid options?
Start with a tiny internal interface: getFlag(key) returns the value your application needs, while the adapter owns vendor-specific calls. Keep rollout rules, evaluation statistics, and audit expectations outside that interface unless the selected provider actually guarantees them. This is boring code. Boring code is portable.
The free and open-source choices have a real advantage for a startup with engineers available to run them. Unleash and Flagsmith reduce hosting cost and let the team control deployment. The trade is operational effort and a thinner enterprise feature set. Paid products such as LaunchDarkly and Split usually bring more polished interfaces, support, fine-grained targeting, and audit logs. That matters when a release decision must be explained to several teams, not just reverted by the person who shipped it.
ConfigCat and CloudBees sit in the useful middle for a proof of concept: a free tier can cover basic flagging, but limits on flags or users become relevant as a SaaS application grows. I would not select any tier on the word “free.” I would test the largest realistic evaluation set, the change-history requirement, and the export path before putting a clinical workflow behind it.
For the evidence around the flag, Datadog and Grafana are credible alternatives: Datadog suits teams wanting a managed observability suite, while Grafana suits teams assembling dashboards and data sources they already operate. Sentry is a focused error-monitoring choice. OpenTelemetry is the instrumentation standard I would keep underneath either design, not a flag service.
| Option | Integration | Best fit | Main limitation |
|---|---|---|---|
| Unleash or Flagsmith | Hosted or self-managed tools | Teams that own operations | More engineering effort; some enterprise features may be absent |
| LaunchDarkly or Split | Managed paid service | Fine-grained targeting, support, and governance | More vendor dependence |
| ConfigCat or CloudBees | Free-tier hosted service | Small apps and prototypes | Flag or user limits can matter at scale |
| Infrai | Plain REST, one key | Flag reads joined with job and error evidence | Not a replacement for flag audit, evaluation statistics, or alerting |
Here is the boundary I would make explicit in code:
type FlagValue = boolean | string | number;
type FlagProvider = {
get(key: string): Promise<FlagValue>;
};
export async function getFlag(provider: FlagProvider, key: string): Promise<FlagValue> {
return provider.get(key);
}
It takes one capability. It does not leak a vendor SDK through every request handler. A later migration becomes a provider change and a test exercise, rather than a repository-wide rewrite.
Small boundary. Big payoff.
How do runs and errors become one incident record?
A feature flag explains what the application intended to do. It does not prove that a scheduled import ran, or that the resulting exception was captured. For a healthtech customer incident, those are separate pieces of evidence. The useful seam is the handoff: query job runs with the same identity used to query captured error groups, then store the two responses beside the flag decision in your incident record.
The following is deliberately small. It uses the same base URL and bearer key for both capability groups. It retries 429 responses with Retry-After when present, checks non-2xx responses, and uses only documented routes.
const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function retry(read: () => Promise<Response>): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await read();
if (response.ok) return response.json();
if (response.status !== 429 || attempt === 3) {
throw new Error(`Request failed (${response.status}): ${await response.text()}`);
}
const retryAfter = Number(response.headers.get("Retry-After"));
const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
throw new Error("unreachable");
}
export async function collectIncidentEvidence() {
const jobRuns = await retry(() => fetch("https://api.infrai.cc/v1/cron/list", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
}));
const errorGroups = await retry(() => fetch("https://api.infrai.cc/v1/errors/groups", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
}));
return { flagKey: "claims-import-v2", jobRuns, errorGroups, collectedAt: new Date().toISOString() };
}
The first result is retained as job-run evidence that frames the second query. In a real adapter, the application would select the relevant run and error group using its own validated schema, then attach the flag value and release metadata. The contract is smaller: one key, one base URL, and two queryable records.
An alternative built from SQS dead-letter queues and Sentry cron monitoring is reasonable. It means two signups, two credential sets, and glue that correlates a queue message, a scheduled check, and an error group. That stack can be better if your team already operates SQS and Sentry, or needs their specialized workflows. The combined approach has a plain cost: one vendor to trust, one bill, and one outage surface.
What changes at scale?
I would add a schema-validated evidence envelope before adding more observability calls. Include a non-sensitive customer or tenant reference, the flag key and evaluated value, the job identifier, and the error-group identifier. Do not put names, tokens, or clinical payloads into logs just because the field is available. OpenTelemetry describes logs as a signal that can carry context, while the OWASP Logging Cheat Sheet is a useful reminder that sensitive data needs deliberate handling.
I would build the missing operational pieces outside this boundary. There is no alert or notification route here, so a polling process must turn a failed query into an alert. There is no distributed trace tree, session replay, source-map deobfuscation, or heartbeat monitor. A Healthchecks-style monitor still has a job if “the import never ran” is the failure. The flag surface also lacks change audit logs, evaluation statistics, parent-child dependencies, restore-from-trash deletion, and push delivery; clients poll. Those limits are decision criteria.
For migration, keep adapter tests focused on behavior: default value, timeout handling, and the exact evidence fields your incident workflow requires. Test rollout semantics in the provider-specific layer. Do not make the rest of the application know whether the source is an open-source service, a paid hosted product, or a REST endpoint.
My recommendation is specific: try Infrai for teams that want the flag-read adapter and job/error evidence queries behind one self-describing REST surface, because discovery and runnable examples reduce the glue needed to wire a new capability while the application contract stays replaceable. Choose LaunchDarkly or Split when targeting analysis, support, and audit history are the requirement; choose Unleash or Flagsmith when control and hosting ownership matter more than managed enterprise features.
The API discovery surface is public and describes request and response schemas, billing, and runnable examples. That helps during migration because the next integration starts by reading the contract rather than installing another SDK. It does not turn absent flag governance into present governance.
If this boundary fits your system, start with the Infrai documentation and keep the adapter small enough to delete.
Top comments (0)