SDKs
OpenFeature (OFREP): any language
On this page
Base URL and keys
- Base URL:
https://edge.nanoflags.io. The provider adds/ofrep/v1/...itself. - Auth: your SDK key, sent as
Authorization: Bearer <key>or asX-API-Key: <key>.- Server key (
nf_srv_…): for backends. It evaluates every flag of its environment. - Browser key (
nf_pub_…): for web and mobile apps. It only sees flags marked client-exposed. Any other flag answersFLAG_NOT_FOUND. - Never put a server key in a browser or mobile app.
- Server key (
- Same results as the SDKs: the edge evaluates against your environment's current published, signed revision. It uses the same evaluation engine as the NanoFlags SDKs, so a given context gets the same value either way.
@nanoflags/sdk with @nanoflags/openfeature-provider; in Python, the nanoflags package; in Go, the Go SDK. Each has its own OpenFeature provider. Use OFREP for every other language.Quickstart
In all examples, targetingKey is the identity used for percentage rollouts, usually a user or account id. Every other attribute is matched by your targeting rules.
Go
go get github.com/open-feature/go-sdk/openfeature github.com/open-feature/go-sdk-contrib/providers/ofrepimport (
"context"
"os"
"github.com/open-feature/go-sdk-contrib/providers/ofrep"
"github.com/open-feature/go-sdk/openfeature"
)
provider := ofrep.NewProvider("https://edge.nanoflags.io", ofrep.WithBearerToken(os.Getenv("NANOFLAGS_SERVER_KEY")))
if err := openfeature.SetProviderAndWait(provider); err != nil {
panic(err)
}
client := openfeature.NewClient("checkout")
evalCtx := openfeature.NewEvaluationContext("user-123", map[string]any{"region": "US", "plan": "pro"})
enabled, _ := client.BooleanValue(context.Background(), "checkout-v2", false, evalCtx)Python
pip install openfeature-sdk openfeature-provider-ofrepimport os
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.contrib.provider.ofrep import OFREPProvider
key = os.environ["NANOFLAGS_SERVER_KEY"]
api.set_provider(OFREPProvider("https://edge.nanoflags.io", headers_factory=lambda: {"Authorization": f"Bearer {key}"}))
client = api.get_client()
ctx = EvaluationContext(targeting_key="user-123", attributes={"region": "US", "plan": "pro"})
enabled = client.get_boolean_value("checkout-v2", False, ctx)For number flags that hold whole numbers, use get_integer_value. The Python provider's get_float_value rejects a JSON 25.
Java
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.23.0</version>
</dependency>
<dependency>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>ofrep</artifactId>
<version>0.0.2</version>
</dependency>import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dev.openfeature.contrib.providers.ofrep.OfrepProvider;
import dev.openfeature.contrib.providers.ofrep.OfrepProviderOptions;
import dev.openfeature.sdk.*;
import java.util.Map;
OfrepProviderOptions options = OfrepProviderOptions.builder()
.baseUrl("https://edge.nanoflags.io")
.headers(ImmutableMap.of("Authorization", ImmutableList.of("Bearer " + System.getenv("NANOFLAGS_SERVER_KEY"))))
.build();
OpenFeatureAPI.getInstance().setProviderAndWait(OfrepProvider.constructProvider(options));
Client client = OpenFeatureAPI.getInstance().getClient();
EvaluationContext ctx = new ImmutableContext("user-123", Map.of("region", new Value("US"), "plan", new Value("pro")));
boolean enabled = client.getBooleanValue("checkout-v2", false, ctx);.NET
dotnet add package OpenFeature
dotnet add package OpenFeature.Providers.Ofrepusing OpenFeature;
using OpenFeature.Model;
using OpenFeature.Providers.Ofrep;
using OpenFeature.Providers.Ofrep.Configuration;
var options = new OfrepOptions("https://edge.nanoflags.io")
{
Headers = new Dictionary<string, string>
{
["Authorization"] = $"Bearer {Environment.GetEnvironmentVariable("NANOFLAGS_SERVER_KEY")}",
},
};
await Api.Instance.SetProviderAsync(new OfrepProvider(options));
var client = Api.Instance.GetClient();
var ctx = EvaluationContext.Builder()
.SetTargetingKey("user-123")
.Set("region", "US")
.Set("plan", "pro")
.Build();
var enabled = await client.GetBooleanValueAsync("checkout-v2", false, ctx);JavaScript: Node.js (server key)
npm install @openfeature/server-sdk @openfeature/ofrep-providerimport { OpenFeature } from "@openfeature/server-sdk";
import { OFREPProvider } from "@openfeature/ofrep-provider";
await OpenFeature.setProviderAndWait(
new OFREPProvider({ baseUrl: "https://edge.nanoflags.io", headers: [["Authorization", `Bearer ${process.env.NANOFLAGS_SERVER_KEY}`]] }),
);
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue("checkout-v2", false, { targetingKey: "user-123", region: "US" });JavaScript: browser (browser key)
npm install @openfeature/web-sdk @openfeature/ofrep-web-providerimport { OpenFeature } from "@openfeature/web-sdk";
import { OFREPWebProvider } from "@openfeature/ofrep-web-provider";
await OpenFeature.setContext({ targetingKey: "user-123", region: "US" });
await OpenFeature.setProviderAndWait(
new OFREPWebProvider({
baseUrl: "https://edge.nanoflags.io",
headers: [["X-API-Key", "nf_pub_…"]], // browser keys are public by design
pollInterval: 30_000,
}),
);
const enabled = OpenFeature.getClient().getBooleanValue("checkout-v2", false);The web provider fetches all client-exposed flags in one request, then evaluates locally. It re-checks every pollInterval with If-None-Match, and NanoFlags answers 304 Not Modified until something changes.
Providers exist for other languages too (PHP, Ruby, Swift, Kotlin/Android and more). See the OpenFeature ecosystem. Configure each one with the same base URL and key header.
What you get back
| Situation | reason | variant |
|---|---|---|
| Flag is turned off in this environment (its off value is served) | DISABLED | off |
| Flag has no rules | STATIC | default |
| No rule matched (the flag's default is served) | DEFAULT | default |
| A rule with a fixed value matched | TARGETING_MATCH | the rule id |
| A percentage rollout matched | SPLIT | <rule id>:rollout or <rule id>:otherwise |
flagMetadata includes revision, the published revision that was evaluated. When a rule matched, it also includes ruleIndex and ruleId, and for rollouts inRollout. A JSON flag whose value is null returns your code default.
Note on disabled flags: the Python and JavaScript providers return the flag's off value. The Go provider returns your code default.
Errors
| Error code | When | HTTP (single flag) |
|---|---|---|
FLAG_NOT_FOUND | The flag doesn't exist, or a browser key asks for a flag that isn't client-exposed | 404 |
TARGETING_KEY_MISSING | The flag reached a percentage rollout and the context has no targetingKey | 400 |
INVALID_CONTEXT | context is not an object, or targetingKey is not a string | 400 |
PARSE_ERROR | The request body is not JSON | 400 |
TYPE_MISMATCH | You asked for a different type than the flag holds (detected by the provider), or the flag's configuration doesn't match its type | 400 / provider |
GENERAL | The flag couldn't be evaluated | 400 |
When an error happens, the provider returns your code default together with the error code. At the HTTP level, the edge answers:
401for a missing, unknown or revoked key.403for CI and admin keys. Use an SDK key.429withRetry-Afterwhen a key exceeds 1,000 evaluations per 10 seconds at one Cloudflare location. Browser keys are counted per visitor network. Providers pause until the time inRetry-After.503withRetry-Afterif configuration can't be loaded at all. This is rare: the edge keeps serving the last signed configuration while the database is unreachable.
Raw HTTP
curl -X POST https://edge.nanoflags.io/ofrep/v1/evaluate/flags/checkout-v2 \
-H "Authorization: Bearer $NANOFLAGS_SERVER_KEY" -H "Content-Type: application/json" \
-d '{"context":{"targetingKey":"user-123","region":"US"}}'
# {"key":"checkout-v2","value":true,"reason":"TARGETING_MATCH","variant":"us","metadata":{"revision":42,"ruleIndex":0,"ruleId":"us"}}
curl -X POST https://edge.nanoflags.io/ofrep/v1/evaluate/flags \
-H "X-API-Key: nf_pub_…" -H "Content-Type: application/json" \
-d '{"context":{"targetingKey":"user-123"}}'
# {"flags":[{"key":"checkout-v2","value":false,"reason":"DEFAULT","variant":"default","metadata":{"revision":42}}, ...],
# "metadata":{"revision":42,"environment":"production"}} (header ETag: "…"; send it back as If-None-Match to get 304)Try it now with the public demo key: nf_pub_4ced72c484c4f388a900aeeaf81e130c110c11730d555dd8 (flags demo-new-checkout and demo-accent).
Limitations
- Shared test sessions (
x-nanoflags-session) are not applied to OFREP evaluations. - OFREP evaluations are not reported as evaluation events, so they don't show up in flag usage insights.
- There is no push channel (
eventStreams). Browser providers poll, and polls are cheap thanks to304answers.