Feature Flags and Targeting
Attaching rules to a config turns it into a feature flag that
/config/{key}/evaluate resolves per user. A rule set is an ordered array
plus a default: rules are tried top to bottom, the first rule that
produces a result wins, and if none does, default_enabled (and the
optional default_variant) decide.
Rule types
Rules are tagged JSON objects; type selects the shape. Any rule may carry
a variant payload, returned when that rule matches with enabled: true -
that is the A/B test hook.
user_segment-conditionsmaps attribute names to expected values; all must match (an array value means any-of). Non-matching users fall through to the next rule.percentage-valueis 0-100. Withsticky: true(the default) the bucket is a deterministic hash of config key + user id, so a user's answer never flaps between calls. A percentage rule is terminal: it always produces a result (match or no-match), so rules listed after it never run - put it last.schedule-start_time/optionalend_time. Inside the window the rule returns itsenabled; outside it falls through. Verified live: an open window returnedschedule_active, a future window fell through to the default.user_list- explicitwhitelistorblacklistofuser_ids. Listed users get the configured result (inverted for blacklists); everyone else falls through.
Managing rules
snug remote-config set-rules -k feature.dark-mode \
-r '[{"type":"user_segment","conditions":{"plan":"pro"},"enabled":true,"variant":{"theme":"midnight"}},
{"type":"percentage","value":25}]'
snug remote-config get-rules -k feature.dark-mode
snug remote-config delete-rules -k feature.dark-mode # admin onlyThe server assigns each rule an id (rule_NWZDfYWeMUcC in the live run)
and echoes the stored set with default_enabled and updated_at. On
set-rules, -d/--default-enabled is a boolean flag - not a description.
Environment-scoped rules shadow, they do not merge. set-rules -e staging stores a separate rule set for that environment, and once one
exists the global rules are ignored entirely there. Reproduced live: with
global rules matching plan=pro and a staging rule set containing only a
whitelist, a pro user in staging fell through to default_disabled.
get-rules -e falls back to the global set only when the environment has
no rules of its own.
Evaluating
snug --output json remote-config evaluate -k feature.dark-mode -u user-1 --attr plan=pro{
"key": "docs4-rc.dark-mode",
"enabled": true,
"reason": "user_segment_match",
"rule_id": "rule_NWZDfYWeMUcC",
"variant": { "theme": "midnight" }
}reason names the deciding path (user_segment_match,
percentage_no_match, schedule_active, user_whitelist,
default_disabled, no_rules, ...); the full list is in the
API reference. Evaluation is open
to any authenticated user - client apps can call it directly.
Over raw HTTP, user attributes are flattened query parameters, not a JSON blob:
GET /api/v1/config/{key}/evaluate?user_id=user-1&plan=proThere is no attributes parameter - passing one just creates an attribute
literally named attributes, which silently matches nothing.
Two verified requirements on user_id:
- Omitting it entirely is a
400with a plain-text body (Failed to deserialize query string: missing field 'user_id') - this rejection comes from query parsing and is not enveloped. - Passing it empty (
user_id=) is an enveloped400 { "msg": "user_id is required", "error": "BAD_REQUEST" }.
Attribute values are parsed as JSON when possible: --attr level=3 arrives
as the number 3. Reproduced live: a segment condition of {"level":3}
matched it, but {"level":"3"} (string) did not. Type your conditions to
match what clients send.
Batch evaluation
snug --output json remote-config evaluate-batch -k feature.dark-mode,limits.rps \
-u user-1 --attr plan=pro{
"evaluations": {
"limits.rps": { "enabled": false, "reason": "no_rules" },
"docs4-rc.dark-mode": {
"enabled": true,
"reason": "user_segment_match",
"rule_id": "rule_NWZDfYWeMUcC",
"variant": { "theme": "midnight" }
}
},
"evaluation_time_ms": 2.070708
}Keys are comma-separated in one -k, not repeated flags. Configs without
rules evaluate to enabled: false with reason no_rules.
Analytics
Every evaluation is counted per key, day, and environment. Reading the counters requires a platform admin or editor token:
snug --output json remote-config analytics -k feature.dark-mode -w 2026-08-28{
"key": "docs4-rc.dark-mode",
"window": "2026-08-28",
"enabled_count": 3,
"disabled_count": 3,
"total_evaluations": 6,
"unique_users": 2,
"reasons": { "user_segment_match": 3, "percentage_no_match": 3 }
}-w is a YYYY-MM-DD day window and defaults to today; -e scopes to an
environment.