The LaunchDarkly REST API
LaunchDarkly has two APIs and conflating them costs people an afternoon. The REST API at app.launchdarkly.com/api/v2 manages flags, projects and environments. The SDKs evaluate flags at runtime and are not the same surface. This page is about the first one.
Worth knowing who wrote this: we're ShipSilently, a feature flag service that competes with LaunchDarkly. We've kept this page to things you can verify, with sources at the bottom, because a page that shades the facts is worth less to us than one you'd send to a colleague. Found something wrong or out of date? Tell us and we'll correct it.
Not affiliated with or endorsed by LaunchDarkly. LaunchDarkly is a trademark of its owner, used here to refer to their product. For official documentation see launchdarkly.com/docs.
Authentication
Every request carries an access token in the Authorization header. LaunchDarkly's
own examples send the token directly, without a Bearer prefix:
curl https://app.launchdarkly.com/api/v2/projects \
-H "Authorization: $LD_ACCESS_TOKEN" \
-H "LD-API-Version: 20240415" There are two kinds of token, and picking the wrong one causes a specific kind of outage:
| Token type | Belongs to | Use it for |
|---|---|---|
| Personal access token | You, with your permissions Dies with your account. | Local scripts, one-off exploration, anything you'd be happy to lose. |
| Service token | The account, with an explicit role Survives staff changes. | CI pipelines, Terraform, deploy automation, anything that must not break when somebody leaves. |
The failure mode worth naming: a CI pipeline authenticated with a departed engineer's personal token keeps working right up until IT deactivates the account, then breaks during someone else's release. Use service tokens for machines.
Versioning
API versions are dates. You select one with the LD-API-Version header, formatted
yyyymmdd, matching the day that version was released. Omit the header and your
request runs against whatever version is recorded on the token.
That default is convenient and a bit of a trap, because the version becomes a property of a credential rather than of your code. Rotate to a token created later and behaviour can shift without a single line changing. Send the header explicitly and the surprise goes away.
Rate limits
Requests are limited globally per account in ten-second windows, with every personal and service token on the account drawing from the same allowance. Some routes have their own limits as well, also on ten-second windows and also shared between tokens hitting that route.
LaunchDarkly does not publish the actual numbers and reserves the right to change them, so third-party articles quoting a specific figure are guessing. Read the headers:
| Header | What it tells you |
|---|---|
X-Ratelimit-Route-Remaining | Requests left on this specific route in the current window. |
X-Ratelimit-Reset | When the window resets, as a Unix epoch timestamp in milliseconds. Not a delay in seconds. |
Retry-After | Present on 429 responses. Wait this long before retrying. |
Treating X-Ratelimit-Reset as "seconds to wait" produces a client that sleeps
for roughly fifty-five thousand years, which sounds like a joke until you find it in a
retry loop.
async function ldFetch(url, init = {}, attempt = 0) {
const res = await fetch(url, {
...init,
headers: {
Authorization: process.env.LD_ACCESS_TOKEN,
'LD-API-Version': '20240415',
...init.headers,
},
});
if (res.status === 429 && attempt < 5) {
// X-Ratelimit-Reset is epoch MILLISECONDS, not a duration.
const resetAt = Number(res.headers.get('x-ratelimit-reset'));
const retryAfter = Number(res.headers.get('retry-after'));
const waitMs = Number.isFinite(resetAt)
? Math.max(0, resetAt - Date.now())
: Number.isFinite(retryAfter)
? retryAfter * 1000
: 1000 * 2 ** attempt;
await new Promise((r) => setTimeout(r, Math.min(waitMs, 30_000) + 250));
return ldFetch(url, init, attempt + 1);
}
if (!res.ok) throw new Error(`${res.status} ${res.statusText} on ${url}`);
return res.json();
} Semantic patch: changing flags without clobbering them
Updating a flag through a plain JSON patch means describing the document you want. Get the path wrong and you can overwrite targeting rules somebody else added ninety seconds ago. LaunchDarkly's answer is semantic patch, where you send an instruction instead of a document position.
You opt in by appending domain-model=launchdarkly.semanticpatch to the
Content-Type header:
curl -X PATCH \
https://app.launchdarkly.com/api/v2/flags/default/new-checkout \
-H "Authorization: $LD_ACCESS_TOKEN" \
-H "Content-Type: application/json; domain-model=launchdarkly.semanticpatch" \
-d '{
"comment": "Disabling during incident INC-4471",
"environmentKey": "production",
"instructions": [{ "kind": "turnFlagOff" }]
}' Turning a flag off makes it serve its off-variation to everyone, regardless of any targeting rules. Instructions batch, so one request can add a rule, shift a rollout percentage and leave a comment in the audit log together. This is the call worth having in a runbook, since it works when the dashboard doesn't (see dashboard outages).
Exporting every flag you have
Two good reasons to run this whether or not you're going anywhere: a JSON snapshot of your flag configuration is a backup you currently don't have, and the diff between two snapshots is the clearest picture of flag debt you'll get.
#!/usr/bin/env bash
# Export every flag definition from every project to ./ld-export/
set -euo pipefail
: "${LD_ACCESS_TOKEN:?set LD_ACCESS_TOKEN first}"
API="https://app.launchdarkly.com/api/v2"
VERSION="20240415"
mkdir -p ld-export
auth=(-sS -H "Authorization: $LD_ACCESS_TOKEN" -H "LD-API-Version: $VERSION")
# 1. list projects
curl "${auth[@]}" "$API/projects?limit=100" > ld-export/projects.json
# 2. pull flags for each project, with environment-level config
for key in $(jq -r '.items[].key' ld-export/projects.json); do
echo "exporting $key…"
curl "${auth[@]}" "$API/flags/$key?summary=false&limit=100" \
> "ld-export/flags-$key.json"
sleep 1 # stay well inside the ten-second window
done
# 3. how many flags did we actually get?
for f in ld-export/flags-*.json; do
printf '%s\t%s flags\n' "$f" "$(jq -r '.items | length' "$f")"
done
echo "done → ./ld-export" summary=false is the important flag: the default response is trimmed, and the
full form is what carries targeting rules and per-environment state. If you have more than a
hundred flags in a project, follow the pagination links in the response rather than raising
limit indefinitely.
What comes out is configuration. Experiment results, historical evaluation data and audit history are separate endpoints, and experiment history in particular does not move anywhere useful, which is worth knowing before you plan a migration around it. Our migration guide maps the concepts across and is candid that in-flight experiments should stay where they are.
FAQ
What is the LaunchDarkly API base URL?
https://app.launchdarkly.com/api/v2 for the commercial instance. Customers on the US federal instance use app.launchdarkly.us instead, with separate tokens.
How do I authenticate with the LaunchDarkly API?
Send an access token in the Authorization header on every request. Tokens come in two flavours: personal access tokens, which inherit your own permissions and belong to you as an individual, and service tokens, which belong to the account and are the right choice for anything running in CI or on a server, because they survive the person who created them leaving.
What is the LD-API-Version header?
It pins your requests to a dated version of the API, formatted yyyymmdd (for example 20240415). If you omit it, requests use the version recorded on the access token itself. Setting it explicitly in code is the safer habit: it means a token created later, or by someone else, doesn't silently change your integration's behaviour.
What are the LaunchDarkly API rate limits?
LaunchDarkly deliberately does not publish the specific numbers and says they may change. There is a global per-account limit measured over ten-second windows, shared by every personal and service token on the account, and some routes carry their own limits on top. Their guidance is to read the response headers rather than hardcode a figure, which is also the only approach that keeps working.
How do I handle a 429 from the LaunchDarkly API?
Back off using the headers on the response rather than a fixed sleep. X-Ratelimit-Reset carries the moment the current window resets as a Unix epoch timestamp in milliseconds, not a number of seconds to wait, which is the detail that trips people up. Retry-After is also present on 429 responses.
Can I export all my flags from LaunchDarkly?
Yes, and you should have a copy regardless of whether you are planning to move. GET /api/v2/flags/{projectKey} returns your flag definitions as JSON, including targeting rules and per-environment state. There is a working script on this page. Note that the export covers flag configuration; experimentation results and historical evaluation data are a separate matter.
More on LaunchDarkly
If you're writing scripts to work around your flag vendor
ShipSilently ships a REST API with a downloadable OpenAPI 3.0 spec, so you can generate a client instead of hand-rolling one. We do not have a CLI yet and we will not pretend otherwise. Flat $49/month, and the comparison page is honest about the rest.
$49/month is the 2026 price. List goes to $89/month on January 1, 2027, and subscriptions started before December 31, 2026 stay at $49.
Sources
- REST API overview, authentication, versioning and rate limiting: launchdarkly.com/docs/api and apidocs.launchdarkly.com
- Using the REST API (guide): launchdarkly.com/docs/guides/api/rest-api
- API version migration (dated versions, yyyymmdd): launchdarkly.com/docs/guides/api/api-migration-guide
- Semantic patch format and the
turnFlagOffinstruction: launchdarkly.com/docs/api/feature-flags/patch-feature-flag - Rate limit headers and 429 handling: support.launchdarkly.com
- Official API clients (Go, JavaScript, Ruby, Python): github.com/launchdarkly
Checked against the linked sources on 2026-07-25. LaunchDarkly changes its docs and pricing without notice, so treat their pages as authoritative over ours and email us when we drift.