Roll out a new checkout
A shop wants the new checkout for 10% of customers in Italy and France. Alice, a tester, always sees it. Everyone else keeps the old page.
The commands below assume a virtual environment, a shell at the repository root, and the process listening on 127.0.0.1:8080.
01Create the management token
python -m main bootstrap
The process creates flaggator.db if it is missing, applies migrations, and prints one token on stdout. A line on stderr says to save it and does not contain the token. Copy the stdout value. It is shown once.
export TOKEN=the-printed-token
This token is an operator credential. The application should not ship with it.
02Start the process
In another terminal, from the same directory and the same virtual environment:
python -m main
Leave it running. Migrations run again and change nothing. Health needs no token:
curl -s http://127.0.0.1:8080/health
03Create the project and production
curl -s -X POST http://127.0.0.1:8080/v1/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"shop","name":"Shop"}'
curl -s -X POST http://127.0.0.1:8080/v1/projects/shop/environments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"prod","name":"Production"}'
Each response is 201 with the key and the name. The environment response has no secret. SDK keys are a separate step, so listing environments cannot leak one.
04Create the flag, still off
Variations are ordered. Index 0 is false, the old checkout. Index 1 is true, the new one.
curl -s -X POST http://127.0.0.1:8080/v1/projects/shop/flags \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"checkout-v2","name":"Checkout v2","description":"New checkout page","variations":[false,true]}'
The 201 body includes one environment entry for prod. A new flag starts disabled there: enabled is false, off_variation is 0, and version is 1. Creating the flag does not turn the feature on.
default is required and is used only when the flag does not exist, so here it is ignored.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"key":"alice","attributes":{"country":"US"}},"default":false}'
{
"flag_key": "checkout-v2",
"value": false,
"variation_index": 0,
"reason": "off",
"rule_id": null,
"version": 1
}
05Turn on the rollout
Targeting is one document per environment. The version in the body is the version just read, 1. The service stores 2 and rejects a later write that still says 1.
The rule has no id. The service assigns one and returns it. The salt is s1 on purpose, so the same person always lands in the same bucket. Omitting salt assigns a random one and reshuffles who is in the 10%. Weights sum to 100000. Variation 1 takes 10000 (10%). The rest is variation 0.
curl -s -X PUT http://127.0.0.1:8080/v1/projects/shop/flags/checkout-v2/environments/prod \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"off_variation": 0,
"fallthrough": {"variation": 0},
"rules": [
{
"clauses": [
{"attribute": "country", "operator": "in", "values": ["IT", "FR"]}
],
"serve": {
"rollout": {
"salt": "s1",
"variations": [
{"variation_index": 1, "weight": 10000},
{"variation_index": 0, "weight": 90000}
]
}
}
}
],
"targets": {"1": ["alice"]},
"prerequisites": [],
"version": 1
}'
The 200 body is the stored document. version is 2. rules[0].id is the id later returned as rule_id. targets uses the variation index as a decimal string: "1" means the true variation. Alice is listed there, so she gets that variation even when her country would not match the rule.
06See who gets the new checkout
Same request shape. Only the context changes.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"key":"alice","attributes":{"country":"US"}},"default":false}'
user-11 lives in Italy, so the rule matches. The bucket of checkout-v2:s1:user-11 is 3913, inside the first 10000 of weight, so this customer is in the 10%.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"key":"user-11","attributes":{"country":"IT"}},"default":false}'
Bob is also in Italy, but the bucket of checkout-v2:s1:bob is 27461, past the first share, so he stays on the old checkout.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"key":"bob","attributes":{"country":"IT"}},"default":false}'
Bob in the US does not match the country rule, and he is not targeted. Fallthrough is variation 0.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"key":"bob","attributes":{"country":"US"}},"default":false}'
A context with a country and no key does not enter the rollout. A rollout needs a context key. Omit key; an empty string is rejected.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"checkout-v2","context":{"attributes":{"country":"IT"}},"default":false}'
07Give the application its own key
The shop backend should not hold the management token. Create an SDK key bound to prod. The body is an empty object. secret is returned once.
curl -s -X POST http://127.0.0.1:8080/v1/projects/shop/environments/prod/sdk-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
Save id, prefix, and secret. The prefix is the first eight characters of the secret and is safe to store in a log. The secret is not.
export SDK_TOKEN=the-printed-sdk-secret export SDK_KEY_ID=the-id
The application sends the SDK token and omits project_key and environment_key. The key already names shop / prod.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $SDK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"flag_key":"checkout-v2","context":{"key":"user-11","attributes":{"country":"IT"}},"default":false}'
A page that needs every flag for one customer uses one call. Decisions are ordered by flag key. There is no default: only flags that exist are returned.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate-all \
-H "Authorization: Bearer $SDK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"context":{"key":"user-11","attributes":{"country":"IT"}}}'
The body is {"decisions": [ ... ]}. With only checkout-v2 in the project, that is the one decision.
08Retire the application key
Revoke it with the management token. The path id is the id from create, not the secret and not the prefix.
curl -s -X DELETE \ http://127.0.0.1:8080/v1/projects/shop/environments/prod/sdk-keys/$SDK_KEY_ID \ -H "Authorization: Bearer $TOKEN"
The response is 204 and {}. The next evaluation with that secret is 401, the same response as an unknown token.
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $SDK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"flag_key":"checkout-v2","context":{"key":"user-11","attributes":{"country":"IT"}},"default":false}'
09Ask for a flag that was never created
The application can still ask. A missing flag is 200, not 404, and value is the caller’s default.
curl -s -X POST http://127.0.0.1:8080/v1/evaluate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_key":"shop","environment_key":"prod","flag_key":"not-a-flag","context":{"key":"user-11","attributes":{}},"default":false}'