Feature Flags
OpenFeature & OFREP
Evaluate feature flags using the standard OpenFeature Remote Evaluation Protocol — compatible with any OpenFeature SDK.
Overview
Kaiten implements the OpenFeature Remote Evaluation Protocol (OFREP), making it compatible with any OpenFeature SDK. No proprietary client library is required: configure a standard OFREP provider and evaluate flags from Go, Python, JavaScript, or any supported language.
Evaluate a single flag
Send a POST request with the flag key and an evaluation context:
curl -X POST http://localhost:6060/api/ofrep/v1/evaluate/flags/delivery-tracking-enabled \
-H "Authorization: Bearer ksh_xxx" \
-H "Content-Type: application/json" \
-d '{
"context": {
"targetingKey": "sakura-tokyo",
"kaiten": { "instanceSlug": "sakura-tokyo-prod" }
}
}'Response:
{
"key": "delivery-tracking-enabled",
"variant": "on",
"value": true,
"reason": "TARGETING_MATCH"
}| Field | Description |
|---|---|
key | The flag slug |
variant | The name of the resolved variant |
value | The variant's payload (boolean, string, number, or object) |
reason | Why this variant was selected — TARGETING_MATCH (a rule matched), SPLIT (a rollout distribution), DEFAULT (no rule matched), STATIC (the default variant, no rules), DISABLED (the flag is off). A failed evaluation answers 400 with errorCode and errorDetails instead of variant, value and reason: PROVIDER_FATAL when the context has no targetingKey, FLAG_NOT_FOUND for an unknown key |
Bulk evaluation
Evaluate all flags for an instance in a single request:
curl -X POST http://localhost:6060/api/ofrep/v1/evaluate/flags \
-H "Authorization: Bearer ksh_xxx" \
-H "Content-Type: application/json" \
-d '{
"context": {
"targetingKey": "sakura-tokyo",
"kaiten": { "instanceSlug": "sakura-tokyo-prod" }
}
}'Response:
{
"flags": [
{
"key": "delivery-tracking-enabled",
"variant": "on",
"value": true,
"reason": "TARGETING_MATCH"
},
{
"key": "promo-banner",
"variant": "salmon-week",
"value": { "bannerText": "Fresh salmon week — new arrivals daily", "bannerColor": "#C91D21" },
"reason": "TARGETING_MATCH"
}
]
}Abridged: the response carries one entry per flag of the organization. Sakura Tokyo is on Premium, so promo-banner's rule Standard or Premium license serves salmon-week; for Ninja Osaka, on Starter, the same request serves the default, control.
Bulk evaluation is more efficient when your application needs multiple flags at startup or on each request: it takes one HTTP call instead of N.
Using OpenFeature SDKs
Configure any OpenFeature SDK to use Kaiten as its OFREP provider. The provider is the generic OFREP provider of each SDK — go-sdk-contrib/providers/ofrep in Go, openfeature-provider-ofrep in Python, @openfeature/ofrep-provider in Node — and it takes two things: the API base URL and the caller's token. The base URL keeps its /api prefix, because the provider appends /ofrep/v1/… to it: http://localhost:6060/api on the Community Edition, as below, and https://app.kaiten.sh/api on Kaiten Cloud.
Each snippet registers the provider with the call that waits until it is ready. In Go that is not optional: SetProvider returns at once, and an evaluation made before the provider is ready returns the default value with a PROVIDER_NOT_READY error.
package main
import (
"context"
"fmt"
"log"
ofrep "github.com/open-feature/go-sdk-contrib/providers/ofrep"
"github.com/open-feature/go-sdk/openfeature"
)
func main() {
provider := ofrep.NewProvider("http://localhost:6060/api", ofrep.WithBearerToken("ksh_xxx"))
if err := openfeature.SetProviderAndWait(provider); err != nil {
log.Fatal(err)
}
client := openfeature.NewClient("my-app")
value, err := client.BooleanValue(context.Background(), "delivery-tracking-enabled", false,
openfeature.NewEvaluationContext("sakura-tokyo", map[string]any{
"kaiten": map[string]any{"instanceSlug": "sakura-tokyo-prod"},
}))
if err != nil {
log.Fatal(err) // value holds the default here, and the error says why
}
fmt.Println(value)
}from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.contrib.provider.ofrep import OFREPProvider
provider = OFREPProvider(
base_url="http://localhost:6060/api",
headers_factory=lambda: {"Authorization": "Bearer ksh_xxx"},
)
api.set_provider_and_wait(provider)
client = api.get_client("my-app")
value = client.get_boolean_value(
"delivery-tracking-enabled",
default_value=False,
evaluation_context=EvaluationContext(
targeting_key="sakura-tokyo",
attributes={"kaiten": {"instanceSlug": "sakura-tokyo-prod"}},
),
)
print(value)import { OpenFeature } from '@openfeature/server-sdk';
import { OFREPProvider } from '@openfeature/ofrep-provider';
const provider = new OFREPProvider({
baseUrl: 'http://localhost:6060/api',
headers: [['Authorization', 'Bearer ksh_xxx']],
});
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient('my-app');
const value = await client.getBooleanValue('delivery-tracking-enabled', false, {
targetingKey: 'sakura-tokyo',
kaiten: { instanceSlug: 'sakura-tokyo-prod' },
});
console.log(value);Context mapping
OpenFeature SDKs pass an EvaluationContext that maps to Kaiten's OFREP context:
| OpenFeature field | OFREP context key | Usage |
|---|---|---|
targetingKey | targetingKey | Required. The subject the evaluation is bucketed on; when it names a customer slug, Kaiten resolves that customer's License and entitlement facts (__kaiten.license, __kaiten.entitlements), read from the customer's oldest instance |
kaiten.instanceSlug (or kaiten.instanceId) | kaiten | Names the instance being evaluated; resolves __kaiten.instance, __kaiten.customer, __kaiten.deploymentZone |
| Custom attributes | Any key | Available in CEL targeting rules |
Example: passing custom context
With the client configured above, custom attributes travel in the same context:
value, err := client.BooleanValue(context.Background(), "delivery-tracking-enabled", false,
openfeature.NewEvaluationContext("sakura-tokyo", map[string]any{
"kaiten": map[string]any{"instanceSlug": "sakura-tokyo-prod"},
"orderChannel": "mobile-app",
"customerName": "Sakura Tokyo",
}))value = client.get_boolean_value(
"delivery-tracking-enabled",
default_value=False,
evaluation_context=EvaluationContext(
targeting_key="sakura-tokyo",
attributes={
"kaiten": {"instanceSlug": "sakura-tokyo-prod"},
"orderChannel": "mobile-app",
"customerName": "Sakura Tokyo",
},
),
)const value = await client.getBooleanValue('delivery-tracking-enabled', false, {
targetingKey: 'sakura-tokyo',
kaiten: { instanceSlug: 'sakura-tokyo-prod' },
orderChannel: 'mobile-app',
customerName: 'Sakura Tokyo',
});These custom attributes are available in CEL targeting rules, next to the facts Kaiten resolves itself — the License, for one, is never something you pass:
orderChannel == "mobile-app" && __kaiten.license.familySlug == "premium"Flag manifest
Kaiten exposes the OpenFeature flag manifest at:
GET /api/openfeature/v0/manifestIt returns the flags that carry a fallback_value in their metadata, each as { key, type, defaultValue, name, description } — the OpenFeature flag manifest an SDK uses for offline fallbacks.
Why OpenFeature
| Benefit | Description |
|---|---|
| No vendor lock-in | Switch flag providers by changing one line of configuration — evaluation code stays the same |
| Multi-language support | Official SDKs for Go, Python, JavaScript, Java, .NET, PHP, Ruby, and more |
| Standard protocol | OFREP is an open specification — any compliant provider works |
| Community ecosystem | Hooks, middleware, and integrations from the OpenFeature community |
Create & Manage Flags
Create, update, enable/disable, and delete feature flags via the REST API or the console.
Overview
The catalog of typed rights, the grant that carries each License's value and its enforcement, and the usage measured against it — the deep-dive behind Licenses & Entitlements and Usage & Metering.