Skip to content

Feature Flags Reference

API reference for the feature flag system.

Optional dependency

uv add "api-shield[flags]"

Engine methods

engine.use_openfeature()

Activate the feature flag subsystem. Call once before any flag evaluation or flag/segment CRUD.

engine = make_engine()
engine.use_openfeature()

engine.flag_client

OpenFeature-compatible async flag client. Available after use_openfeature().

value = await engine.flag_client.get_boolean_value(flag_key, default, context)
value = await engine.flag_client.get_string_value(flag_key, default, context)
value = await engine.flag_client.get_integer_value(flag_key, default, context)
value = await engine.flag_client.get_float_value(flag_key, default, context)
value = await engine.flag_client.get_object_value(flag_key, default, context)
Parameter Type Description
flag_key str The flag's unique key
default Any Returned when the flag is not found or an error occurs
context EvaluationContext Per-request context for targeting

engine.sync.flag_client

Thread-safe synchronous version for def (non-async) route handlers.

enabled = engine.sync.flag_client.get_boolean_value("my-flag", False, ctx)

Accepts EvaluationContext objects or plain dicts ({"targeting_key": user_id, ...}).


await engine.save_flag(flag, *, actor, platform, action, audit)

Create or replace a flag. Writes an audit log entry unless audit=False.

Parameter Type Default Description
flag FeatureFlag required Flag to persist
actor str "system" Identity recorded in the audit log
platform str "" Surface recorded in the audit log ("dashboard", "cli", etc.)
action str \| None None Override the audit action string. Defaults to flag_created or flag_updated based on whether the flag already existed
audit bool True Set to False to skip writing an audit log entry. Use this for startup seeds and programmatic initialization
# Normal save — audited
await engine.save_flag(FeatureFlag(key="my-flag", ...))

# Startup seed — no audit entry
await engine.save_flag(FeatureFlag(key="my-flag", ...), audit=False)

await engine.get_flag(key)

Return the FeatureFlag for key, or None if not found.


await engine.list_flags()

Return all flags as a list.


await engine.delete_flag(key, *, actor, platform, audit)

Delete a flag. Writes an audit log entry unless audit=False.

Parameter Type Default Description
key str required Key of the flag to delete
actor str "system" Identity recorded in the audit log
platform str "" Surface recorded in the audit log
audit bool True Set to False to skip the audit entry

await engine.save_segment(segment, *, actor, platform, audit)

Create or replace a segment. Writes an audit log entry unless audit=False.

Parameter Type Default Description
segment Segment required Segment to persist
actor str "system" Identity recorded in the audit log
platform str "" Surface recorded in the audit log
audit bool True Set to False to skip writing an audit log entry. Use this for startup seeds and programmatic initialization
# Startup seed — no audit entry
await engine.save_segment(Segment(key="beta-users", ...), audit=False)

await engine.get_segment(key)

Return the Segment for key, or None.


await engine.list_segments()

Return all segments as a list.


await engine.delete_segment(key, *, actor, platform, audit)

Delete a segment. Writes an audit log entry unless audit=False.

Parameter Type Default Description
key str required Key of the segment to delete
actor str "system" Identity recorded in the audit log
platform str "" Surface recorded in the audit log
audit bool True Set to False to skip the audit entry

Models

FeatureFlag

Definition of a feature flag.

class FeatureFlag(BaseModel):
    key: str
    name: str
    description: str = ""
    type: FlagType
    tags: list[str] = []

    variations: list[FlagVariation]
    off_variation: str
    fallthrough: str | list[RolloutVariation]

    enabled: bool = True
    prerequisites: list[Prerequisite] = []
    targets: dict[str, list[str]] = {}
    rules: list[TargetingRule] = []
    scheduled_changes: list[ScheduledChange] = []

    status: FlagStatus = FlagStatus.ACTIVE
    temporary: bool = True
    maintainer: str | None = None
    created_at: datetime
    updated_at: datetime
    created_by: str = "system"
Field Description
key Unique identifier used in code: get_boolean_value("my-flag", ...)
name Human-readable display name
type FlagType.BOOLEAN, STRING, INTEGER, FLOAT, or JSON
variations All possible values; must contain at least two
off_variation Variation served when enabled=False
fallthrough Default when no rule matches: a variation name (str) or a percentage rollout (list[RolloutVariation])
enabled Kill-switch. False means all requests get off_variation
prerequisites Flags that must pass before this flag's rules run
targets Individual targeting: {"on": ["user_1", "user_2"]}
rules Targeting rules evaluated top-to-bottom; first match wins

FlagType

class FlagType(StrEnum):
    BOOLEAN = "boolean"
    STRING  = "string"
    INTEGER = "integer"
    FLOAT   = "float"
    JSON    = "json"

FlagVariation

One possible value a flag can return.

class FlagVariation(BaseModel):
    name: str          # e.g. "on", "off", "control", "variant_a"
    value: bool | str | int | float | dict | list
    description: str = ""

RolloutVariation

One bucket in a percentage rollout (used in fallthrough or TargetingRule.rollout).

class RolloutVariation(BaseModel):
    variation: str    # references FlagVariation.name
    weight: int       # share of traffic, out of 100_000 total

Weights in a rollout list must sum to 100_000. Examples:

Percentage Weight
10% 10_000
25% 25_000
33.33% 33_333
50% 50_000

TargetingRule

A rule that matches clauses and serves a variation.

class TargetingRule(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    description: str = ""
    clauses: list[RuleClause] = []
    variation: str | None = None          # mutually exclusive with rollout
    rollout: list[RolloutVariation] | None = None
    track_events: bool = False

Clauses within a rule are AND-ed. Rules are evaluated top-to-bottom; first match wins.


RuleClause

A single condition within a targeting rule.

class RuleClause(BaseModel):
    attribute: str        # context attribute to inspect, e.g. "plan", "country", "email"
    operator: Operator    # comparison to apply
    values: list[Any]     # one or more values; multiple values use OR logic
    negate: bool = False  # invert the result

Operator

All supported targeting operators.

class Operator(StrEnum):
    # Equality
    IS         = "is"
    IS_NOT     = "is_not"
    # String
    CONTAINS   = "contains"
    NOT_CONTAINS  = "not_contains"
    STARTS_WITH   = "starts_with"
    ENDS_WITH     = "ends_with"
    MATCHES       = "matches"        # Python regex
    NOT_MATCHES   = "not_matches"
    # Numeric
    GT  = "gt"
    GTE = "gte"
    LT  = "lt"
    LTE = "lte"
    # Date (ISO-8601 lexicographic)
    BEFORE = "before"
    AFTER  = "after"
    # Collection
    IN     = "in"
    NOT_IN = "not_in"
    # Segment
    IN_SEGMENT     = "in_segment"
    NOT_IN_SEGMENT = "not_in_segment"
    # Semantic version (requires `packaging`)
    SEMVER_EQ = "semver_eq"
    SEMVER_LT = "semver_lt"
    SEMVER_GT = "semver_gt"

Prerequisite

A flag that must evaluate to a specific variation before the dependent flag runs.

class Prerequisite(BaseModel):
    flag_key: str    # key of the prerequisite flag
    variation: str   # variation the prerequisite must return

If the prerequisite returns any other variation, the dependent flag serves off_variation with reason PREREQUISITE_FAIL.


Segment

A reusable group of users for flag targeting.

class Segment(BaseModel):
    key: str
    name: str
    description: str = ""
    included: list[str] = []     # context keys always in the segment
    excluded: list[str] = []     # context keys always excluded (overrides rules + included)
    rules: list[SegmentRule] = []
    tags: list[str] = []
    created_at: datetime
    updated_at: datetime

Evaluation order for context key k:

  1. k in excluded → not in segment
  2. k in included → in segment
  3. Any SegmentRule matches → in segment
  4. Otherwise → not in segment

SegmentRule

An attribute-based rule inside a segment. Multiple segment rules are OR-ed: if any rule matches, the user is in the segment.

class SegmentRule(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    description: str = ""
    clauses: list[RuleClause] = []    # all must match (AND logic)

EvaluationContext

Per-request context for flag targeting and rollout bucketing.

class EvaluationContext(BaseModel):
    key: str                           # required — user/session/org ID
    kind: str = "user"                 # context kind
    email: str | None = None
    ip: str | None = None
    country: str | None = None
    app_version: str | None = None
    attributes: dict[str, Any] = {}    # any additional attributes

Named fields (email, ip, country, app_version) are accessible in rule clauses by the same names. Items in attributes are merged in and accessible by key.


ResolutionDetails

Full result of a flag evaluation, surfaced in hooks.

class ResolutionDetails(BaseModel):
    value: Any
    variation: str | None = None
    reason: EvaluationReason
    rule_id: str | None = None            # set when reason == RULE_MATCH
    prerequisite_key: str | None = None   # set when reason == PREREQUISITE_FAIL
    error_message: str | None = None      # set when reason == ERROR

EvaluationReason

Why a specific value was returned.

Value Description
OFF Flag is globally disabled. off_variation was served.
TARGET_MATCH Context key was in the individual targets list.
RULE_MATCH A targeting rule matched. rule_id is set.
FALLTHROUGH No targeting rule matched. Default rule was served.
PREREQUISITE_FAIL A prerequisite flag did not return the required variation.
ERROR Provider or evaluation error. Default value was returned.
DEFAULT Flag not found. SDK default was returned.


REST API

When ShieldAdmin is mounted with engine.use_openfeature(), these endpoints are registered under the admin path (e.g. /shield/api/):

Flags

Method Path Description
GET /api/flags List all flags
POST /api/flags Create a flag (full FeatureFlag body)
GET /api/flags/{key} Get a single flag
PUT /api/flags/{key} Replace a flag (full update)
PATCH /api/flags/{key} Partial update
DELETE /api/flags/{key} Delete a flag
POST /api/flags/{key}/enable Enable (kill-switch off)
POST /api/flags/{key}/disable Disable (kill-switch on)
POST /api/flags/{key}/evaluate Evaluate for a given context

Segments

Method Path Description
GET /api/segments List all segments
POST /api/segments Create a segment
GET /api/segments/{key} Get a single segment
PUT /api/segments/{key} Replace a segment
DELETE /api/segments/{key} Delete a segment

Evaluation algorithm

The evaluator (FlagEvaluator) is pure Python with no I/O, unit-testable in isolation.

from shield.core.feature_flags.evaluator import FlagEvaluator

evaluator = FlagEvaluator(segments={"beta": beta_segment})
result = evaluator.evaluate(flag, ctx, all_flags)
print(result.value, result.reason)

Rollout bucketing uses SHA-1 of "{flag_key}:{ctx.kind}:{ctx.key}" modulo 100_000. The same context always lands in the same bucket; bucketing is stable across restarts and deploys.

Prerequisite recursion is limited to depth 10. Circular dependencies are rejected at write time by engine.save_flag().


Dashboard routes

URL Page
/shield/flags Flag list with search and status filters
/shield/flags/{key} Flag detail (4 tabs: Overview, Targeting, Variations, Settings)
/shield/segments Segment list

Example

Example: examples/fastapi/feature_flags.py

uv run uvicorn examples.fastapi.feature_flags:app --reload