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. For users and JWT login, see Users, login, and IdP. All examples.
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 opaque management token is break-glass: full management and evaluation. Prefer a user JWT for day-to-day work. The application should not ship with the management token.
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. The project body is key and name. The environment body adds protected (default false) and has no secret. SDK tokens 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":{"subject":"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":{"subject":"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":{"subject":"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":{"subject":"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":{"subject":"bob","attributes":{"country":"US"}},"default":false}'
A context with a country and no subject does not enter the rollout. A rollout needs a subject. Omit subject; 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 token
The shop backend should not hold the management token. Create an SDK token bound to prod. name is your label. secret is returned once.
curl -s -X POST http://127.0.0.1:8080/v1/projects/shop/environments/prod/sdk-tokens \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"checkout-api"}'
Save id, name, prefix, and secret. id is the decimal number used to revoke (for example 2), not the prefix. 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='paste-the-secret-field-here' export SDK_TOKEN_ID=2
The application sends the SDK token and omits project_key and environment_key. The token 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":{"subject":"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":{"subject":"user-11","attributes":{"country":"IT"}}}'
The body is {"decisions": [ ... ]}. With only checkout-v2 in the project, that is the one decision.
08Retire the application token
Revoke it with the management token. The path must end with the numeric id from create (for example .../sdk-tokens/2). A path that ends with the prefix returns 404 and does not revoke. List tokens first if you forgot the number: the list shows id, name, and prefix.
curl -s -w "\n%{http_code}\n" -X DELETE \
http://127.0.0.1:8080/v1/projects/shop/environments/prod/sdk-tokens/$SDK_TOKEN_ID \
-H "Authorization: Bearer $TOKEN"
That must print {} and then 204. 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":{"subject":"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":{"subject":"user-11","attributes":{}},"default":false}'