Here is a question with an uncomfortable answer at most companies: if your feature flag vendor’s account was suspended tomorrow morning over a billing mix-up, what would you have?
Not “could you carry on serving traffic”. You probably could, assuming your SDK defaults are sane and your services keep their last-known-good values. I mean the configuration itself. The forty targeting rules somebody tuned over eighteen months. The segment definitions. Which flags are on in production but off in staging, and the reason, which is in a comment field you last read in March.
For most teams the honest answer is that it exists in one place, on a vendor’s servers, and there is no copy of it anywhere else. We version-control our infrastructure, our database schemas, our CI pipelines. Flag configuration decides what code paths actually execute in production, and it usually lives outside all of that.
This is a twenty-minute fix. I’m using LaunchDarkly here because it’s what most people reading this are on, but every flag vendor worth using has the same endpoint under a different name.
The script
You need an access token with read access. A service token is better than a personal one if this is going to run on a schedule, because personal tokens stop working when their owner’s account does, which is a genuinely annoying way to discover your backup has been failing for five months.
#!/usr/bin/env bash
# Snapshot every LaunchDarkly flag definition to ./ld-export/
set -euo pipefail
: "${LD_ACCESS_TOKEN:?set LD_ACCESS_TOKEN first}"
API="https://app.launchdarkly.com/api/v2"
VERSION="20240415"
OUT="ld-export/$(date +%Y-%m-%d)"
mkdir -p "$OUT"
auth=(-sS -H "Authorization: $LD_ACCESS_TOKEN" -H "LD-API-Version: $VERSION")
curl "${auth[@]}" "$API/projects?limit=100" > "$OUT/projects.json"
for key in $(jq -r '.items[].key' "$OUT/projects.json"); do
echo "→ $key"
curl "${auth[@]}" "$API/flags/$key?summary=false&limit=100" > "$OUT/flags-$key.json"
curl "${auth[@]}" "$API/segments/$key" > "$OUT/segments-$key.json" || true
sleep 1
done
echo "done → $OUT"
Three things in there do the actual work.
summary=false is the one people miss. Leave it off and you get a trimmed response that looks complete right up until you need the targeting rules, which is precisely when you’ll be looking at it. The full form is bigger and it’s the one worth keeping.
The LD-API-Version header pins you to a dated version of the API. Omit it and your requests run against whatever version is recorded on the token you happened to use, which means the same script can behave differently depending on who ran it. That’s a fun bug to chase.
And the sleep 1 is there because LaunchDarkly rate-limits per account in ten-second windows, shared across every token you have. A backup script that trips the limit for your deploy pipeline is worse than no backup script. If you want to do this properly rather than politely, read X-Ratelimit-Reset off the response and back off against it, keeping in mind that it’s an epoch timestamp in milliseconds rather than a number of seconds to wait. I wrote up the correct retry loop on our LaunchDarkly REST API page.
Commit the output to a private repo, or push it to object storage with versioning on. Run it nightly. The diff between two days is also the most readable flag-debt report you’ll ever get, which is a nice side effect of a backup nobody expects to use.
What the export doesn’t include
This is the part that matters if you’re exporting because you’re thinking about leaving, rather than because you want a backup.
You get configuration. Flags, their variations, targeting rules, per-environment state, segments. That’s the majority of the value and it’s what you’d rebuild from.
You don’t get experiment results. Historical evaluation data, statistical outcomes, the record of which variant won and by how much, none of that comes across in a form another system can use, and no vendor in this category has an incentive to change that. If you’re running experiments and considering a move, keep them where they are and finish them. We say the same thing on our own migration guide, which is a slightly odd thing to publish as the company hoping you’ll switch, but the alternative is watching someone abandon a half-finished test on our advice.
Audit history is also separate, and often the thing a compliance reviewer asks about six months after a migration nobody documented.
The bit that isn’t about backups
Once you have this running, you’ve quietly acquired something more useful than a backup: a machine-readable inventory of every flag you own.
Point jq at it and you can answer questions that are tedious in any dashboard. Which flags are permanently on in every environment and have been for a year (those are no longer flags, they’re config, and they should be deleted from the codebase). Which have targeting rules referencing segments that no longer exist. Which were created by people who left.
# flags that are on everywhere, i.e. probably dead code paths
jq -r '.items[]
| select([.environments[].on] | all)
| "\(.key)\t\(.creationDate | tonumber / 1000 | todate)"' \
ld-export/*/flags-default.json
Every team over about thirty flags has a double-digit number of those. Finding them is the hard part, and it turns out the finding is a jq one-liner away from a backup you should have had anyway. There’s more on the cleanup process in managing feature flag technical debt if that’s the rabbit hole you’re now in.
Do it this week
The reason to do this before you need it is that all three occasions you’d need it are bad ones. A billing dispute that locks the account. An engineer who deletes the wrong project at 4pm on a Friday. A renewal quote that arrives with a number on it you weren’t expecting, at which point “how long would it take us to leave” becomes an urgent question rather than an interesting one, and the answer is much better if you already have the JSON.
Twenty minutes, a cron entry, a private repo. It’s the cheapest leverage available in this whole category.
If you’re curious what the other side looks like: ShipSilently has the same endpoints, a downloadable OpenAPI spec, and costs a flat $49 a month. We’re also missing things LaunchDarkly has, and the comparison page lists them rather than hiding them.