Skip to content
NanoFlags
Start free

SDKs

OpenFeature (OFREP): any language

NanoFlags speaks the OpenFeature Remote Evaluation Protocol (OFREP) 0.4.0. Any OpenFeature OFREP provider can read your flags, including Go, Python, Java, .NET, PHP, Ruby, Swift, Kotlin and JavaScript. You don't need a NanoFlags SDK for these languages.

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 as X-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 answers FLAG_NOT_FOUND.
    • Never put a server key in a browser or mobile app.
  • 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.
OFREP or a native SDK?
An OFREP provider makes an HTTP request for each evaluation (server providers) or for each poll (web/mobile providers). The native NanoFlags SDKs download the signed configuration once and evaluate in-process, in microseconds, with live updates. In JavaScript or TypeScript, prefer @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

sh
go get github.com/open-feature/go-sdk/openfeature github.com/open-feature/go-sdk-contrib/providers/ofrep
go
import (
	"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

sh
pip install openfeature-sdk openfeature-provider-ofrep
python
import 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

pom.xml
<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>
java
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

sh
dotnet add package OpenFeature
dotnet add package OpenFeature.Providers.Ofrep
csharp
using 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)

sh
npm install @openfeature/server-sdk @openfeature/ofrep-provider
ts
import { 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)

sh
npm install @openfeature/web-sdk @openfeature/ofrep-web-provider
ts
import { 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

Situationreasonvariant
Flag is turned off in this environment (its off value is served)DISABLEDoff
Flag has no rulesSTATICdefault
No rule matched (the flag's default is served)DEFAULTdefault
A rule with a fixed value matchedTARGETING_MATCHthe rule id
A percentage rollout matchedSPLIT<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 codeWhenHTTP (single flag)
FLAG_NOT_FOUNDThe flag doesn't exist, or a browser key asks for a flag that isn't client-exposed404
TARGETING_KEY_MISSINGThe flag reached a percentage rollout and the context has no targetingKey400
INVALID_CONTEXTcontext is not an object, or targetingKey is not a string400
PARSE_ERRORThe request body is not JSON400
TYPE_MISMATCHYou asked for a different type than the flag holds (detected by the provider), or the flag's configuration doesn't match its type400 / provider
GENERALThe flag couldn't be evaluated400

When an error happens, the provider returns your code default together with the error code. At the HTTP level, the edge answers:

  • 401 for a missing, unknown or revoked key.
  • 403 for CI and admin keys. Use an SDK key.
  • 429 with Retry-After when 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 in Retry-After.
  • 503 with Retry-After if 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

sh
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 to 304 answers.