{"info":{"title":"Quonfig Public API","version":"1.0.0","description":"Quonfig's public REST API. Authenticate with a Bearer API key — either a user key (qf_uk_*) or a service-account key (qf_sa_*). Keys are workspace-scoped: the workspace every call acts on is inferred from the key.\n\nAuthentication. This API accepts Bearer API keys only; see the `apiKey` security scheme for what each kind of key may read and write, how to obtain one, and the rate limit. The Quonfig MCP server at https://mcp.quonfig.com/mcp accepts the same keys, or an OAuth access token issued through WorkOS AuthKit (RFC 9728 discovery at https://mcp.quonfig.com/.well-known/oauth-protected-resource/mcp). An OAuth session can select among the workspaces its user belongs to; an API key cannot.\n\nErrors are returned as `{ \"error\": \"<CODE>\", \"message\": \"<description>\" }` (see the `Error` schema); validation failures additionally carry a `details` field with structured issues."},"servers":[{"url":"https://api.quonfig.com/v1"}],"components":{"schemas":{"Error":{"type":"object","description":"Error envelope returned for every non-2xx response. `error` is a stable machine-readable code (e.g. UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BAD_REQUEST); `details` carries structured validation issues when present.","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["error","message"]}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A Quonfig API key, sent as `Authorization: Bearer <key>`. This REST API accepts API keys ONLY — cookie sessions and CLI JWTs are rejected with 401.\n\nTwo kinds of key exist, and both reach the same endpoints: no operation is restricted to one kind. They differ only in where their permissions come from.\n- `qf_uk_*` (user key) — acts as the person who minted it. Its permissions are that user's CURRENT role(s) in the workspace's organization, re-resolved on every request, so a role change takes effect on the next call.\n- `qf_sa_*` (service-account key) — acts as a service-account principal with its own roles, also re-resolved on every request.\n\nKeys are workspace-scoped: the workspace a call acts on is inferred from the key. No endpoint takes a workspace parameter, and `GET /workspaces` returns exactly that one workspace. `org.*` permissions are stripped from both kinds, so no key can manage organization members, billing, or service accounts — in particular a service-account key can never mint or rotate service-account keys.\n\nReads (every `GET`) require only a live, unrevoked key and carry no permission requirement. Writes are authorized per item and per environment: the required permission follows the item's `access` tier and whether the target environment is `protected` — `config.edit.support`, `config.edit.standard`, or `config.edit.protected`. The env-scoped `.nonprotected` role variants satisfy the first two tiers only in environments not marked protected. A denial is `403` with `details.code` = `PERMISSION_DENIED`, naming the permission that was actually required. Writes additionally require the organization's subscription to be active (`402` otherwise).\n\nGetting a key. A user key is minted by any workspace member for themselves, with an optional expiry, from Environments & Keys → \"CLI & API keys\" (`/workspaces/{workspaceId}/environments?tab=api-keys`). A service-account key is minted by an organization admin (permission `org.service_accounts.manage`) from workspace Settings → \"Service accounts\" (`/workspaces/{workspaceId}/settings?tab=service-accounts`), which is also where the account's roles are assigned.\n\nRate limit: a token bucket per key — 5 requests/second sustained, with a burst of 25. Over-limit returns `429` with a `Retry-After` header in seconds. Buckets are held in memory per server instance rather than shared, so they reset on deploy."}}},"security":[{"apiKey":[]}],"openapi":"3.1.1","paths":{"/whoami":{"get":{"operationId":"whoami","summary":"Identify the authenticated API key","description":"Returns the principal (user or service account) behind the Bearer API key and the workspace the key is scoped to.","tags":["Meta"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"workspaceId":{"type":"string"},"keyId":{"type":"string"},"principal":{"type":"object","properties":{"type":{"enum":["user","service_account"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["type","id","name"]}},"required":["workspaceId","keyId","principal"]}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags":{"get":{"operationId":"flags.list","summary":"List feature flags","description":"Lists the workspace's feature flags with their derived lifecycle status. `statuses` covers PRODUCTION environments only — that is also what ?status= filters on — while `environmentStatuses` covers every active environment, which is what distinguishes an environment gate (live in production, still off in staging) from a finished rollout. Status is derived from the values a flag's rules serve, plus the owner-set `readyForCleanup` marker; see the status field for the exact rule. Filter with ?tag= and/or ?status=.\n\nPagination is optional: without ?limit= and ?cursor= every flag is returned in one response, in an unspecified order. Supplying either one orders the result ascending by `key` and returns a `nextCursor` while further rows remain — pass it back as ?cursor= to continue. Filters are applied before paging, so send the same ?tag=/?status= with each page.","tags":["Flags"],"parameters":[{"name":"tag","in":"query","schema":{"type":"string","description":"Only return flags carrying this tag."},"allowEmptyValue":true,"allowReserved":true},{"name":"status","in":"query","schema":{"enum":["pre_rollout","rollout","live","ready_for_cleanup"],"description":"Only return flags with this status in any production environment. Matched against `statuses`, which is production-only — never against `environmentStatuses`."},"style":"deepObject","explode":true,"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of rows to return (1-100). OMIT to return every row in one response, which is the default and what this endpoint has always done — there is no implicit page size."},"allowEmptyValue":true,"allowReserved":true},{"name":"cursor","in":"query","schema":{"type":"string","description":"Opaque cursor from a previous response's `nextCursor`. Returns the rows ordered after it. Pass the same filters alongside it — the cursor names a position in the ordering, not a saved query. An empty string means the same as omitting it: start from the beginning."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"flags":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"valueType":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"readyForCleanup":{"type":"boolean","description":"Owner-set marker: the flag's job is done and it is safe to remove from code. Set by hand in the Quonfig UI. Always present — false when the owner has not marked the flag. When true it forces every entry in `statuses` and `environmentStatuses` to `ready_for_cleanup`, whatever the rules say."},"statuses":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"enum":["pre_rollout","rollout","live","ready_for_cleanup"],"description":"Lifecycle status of a boolean flag in ONE environment, derived from what that environment's rules serve. `ready_for_cleanup` is the exception: it is the owner-set `readyForCleanup` boolean on the flag, and it short-circuits the rule analysis entirely. Otherwise, over the environment's own rules — or the flag's `default.rules` when the environment has no entry of its own: no rules at all -> `pre_rollout`; any rule serving a real split -> `rollout`; with no ALWAYS_TRUE catch-all rule -> `pre_rollout` when every rule serves false, else `rollout`; with a catch-all -> `live` when every rule serves true, `pre_rollout` when every rule serves false, else `rollout`. A rule serves true or false when its value is a plain boolean, or a weighted rollout in which one boolean value carries all of the non-zero weight; anything else counts as a split. Non-boolean flags have no status."},"description":"Derived lifecycle status per PRODUCTION environment, keyed by environment name — and production only, which is also what ?status= filters on. A flag can be `live` here while still off in staging, so read `environmentStatuses` before calling one fully rolled out. Empty for non-boolean flags."},"environmentStatuses":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"enum":["pre_rollout","rollout","live","ready_for_cleanup"],"description":"Lifecycle status of a boolean flag in ONE environment, derived from what that environment's rules serve. `ready_for_cleanup` is the exception: it is the owner-set `readyForCleanup` boolean on the flag, and it short-circuits the rule analysis entirely. Otherwise, over the environment's own rules — or the flag's `default.rules` when the environment has no entry of its own: no rules at all -> `pre_rollout`; any rule serving a real split -> `rollout`; with no ALWAYS_TRUE catch-all rule -> `pre_rollout` when every rule serves false, else `rollout`; with a catch-all -> `live` when every rule serves true, `pre_rollout` when every rule serves false, else `rollout`. A rule serves true or false when its value is a plain boolean, or a weighted rollout in which one boolean value carries all of the non-zero weight; anything else counts as a split. Non-boolean flags have no status."},"description":"Derived lifecycle status for EVERY active environment, keyed by the same environment names GET /v1/environments returns. This is what tells an environment gate (`live` in production, `pre_rollout` in staging) apart from a finished rollout. Empty for non-boolean flags."},"commitSha":{"type":"string","description":"Git commit SHA of the flag's current version."},"lastModified":{"type":"object","properties":{"date":{"type":"string","description":"When the item last changed (ISO 8601 commit author date)."},"author":{"type":"string","description":"Name on the commit that last changed it. Service-account writes carry the service account's name; use the history endpoint to tell a bot from a human."}},"required":["date","author"],"description":"When and by whom this item last changed — the commit named by `commitSha`. Absent when that commit could not be attributed."}},"required":["key","valueType","tags","readyForCleanup","statuses","environmentStatuses","commitSha"]}},"nextCursor":{"type":"string","description":"Cursor for the next page — pass it back as `cursor`. Present ONLY when more rows remain; its absence means this was the last page. Never present when `limit` and `cursor` were both omitted, since that response already contains every row."}},"required":["flags"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"flags.create","summary":"Create a feature flag","description":"Creates a new feature flag. `type` defaults to bool, and a bool flag is created OFF everywhere and takes no `value` — creation is atomic and inert, and turning the flag on is a separate PATCH /v1/flags/{key}/environments/{env}. Every other type requires `value`, which its default rule serves.\n\nThe new item is created with a single unconditional default rule and an EMPTY `environments` array, which means every environment inherits that default until one is given an entry of its own. Everything richer than that — variants, targeting rules, `sendToClientSdk`, a non-standard access tier — is a follow-up through PUT /v1/flags/{key}/document, using the `commitSha` this response returns as `expectedCommitSha`.\n\nKeys are pooled case-insensitively across flags, configs, segments and log levels, so a key held by any of them fails with 409 `details.code` = ALREADY_EXISTS, carrying `collidingType` (the stored type that holds it: feature_flag | config | segment | log_level) and `collidingKey` (the key as stored, which differs from yours only on a case-variant collision). Never retry with a mutated key name. The document written is validated in full, so a rejected create returns 400 with `details.code` = VALIDATION_FAILED and the failing field paths.","tags":["Flags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":200,"description":"Key for the new item. Letters, numbers, dots, dashes and underscores only, 200 characters or fewer, no leading dot. Keys are pooled case-insensitively across flags, configs, segments and log levels, so a key already taken by ANY of those is a 409 ALREADY_EXISTS."},"type":{"enum":["bool","boolean","string","int","double","string_list","string-list","json","duration"],"description":"Value type of the flag. Defaults to bool — a boolean flag created OFF for every environment, which is what most flags are. The others are \"string\", \"int\", \"double\", \"string_list\", \"json\" and \"duration\"; \"boolean\" and \"string-list\" are accepted as aliases of the first and fourth.","default":"bool"},"value":{"description":"The value the flag's default rule serves, as bare JSON typed against `type`. REQUIRED for every type except bool, and REJECTED for bool: boolean flags are always created off, so use PATCH /v1/flags/{key}/environments/{env} to turn one on."},"description":{"type":"string","description":"Human-readable description stored on the item."},"tags":{"type":"array","items":{"type":"string"},"description":"Tags stored on the item. Flags can be filtered by tag."}},"required":["key"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"valueType":{"type":"string","description":"The stored value type, in the canonical spelling reads return (\"bool\", \"string_list\", …) — not the alias you may have sent."},"commitSha":{"type":"string","description":"The creating commit's SHA. Pass it as `expectedCommitSha` to PUT /v1/{flags,configs}/{key}/document if the next step is reshaping the document the create could not express."},"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete document as written to git — the default rule, the empty `environments` array (every environment inherits the default until one is given its own entry), and the variants, access tier and tags the server filled in."}},"required":["key","valueType","commitSha","document"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags/{key}":{"get":{"operationId":"flags.get","summary":"Get a feature flag","description":"Returns the full flag document: default rules, per-environment targeting rules (including percentage rollouts), and variants — plus `environmentStatuses`, the derived lifecycle status per active environment, so one call answers both what the flag is set to and where it stands. `statuses` is not returned here: it is the production-only map GET /v1/flags filters on, and `environmentStatuses` already covers those environments.","tags":["Flags"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"valueType":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"readyForCleanup":{"type":"boolean","description":"Owner-set marker: the flag's job is done and it is safe to remove from code. Set by hand in the Quonfig UI. Always present — false when the owner has not marked the flag. When true it forces every entry in `statuses` and `environmentStatuses` to `ready_for_cleanup`, whatever the rules say."},"default":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]}}},"required":["rules"],"description":"Rules evaluated when no environment-specific rules match the environment."},"environments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Environment id, e.g. \"production\"."},"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{},"value":{},"confidential":{},"decryptWith":{}}},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]}}},"required":["id","rules"]}},"variants":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["value"]}},"environmentStatuses":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"enum":["pre_rollout","rollout","live","ready_for_cleanup"],"description":"Lifecycle status of a boolean flag in ONE environment, derived from what that environment's rules serve. `ready_for_cleanup` is the exception: it is the owner-set `readyForCleanup` boolean on the flag, and it short-circuits the rule analysis entirely. Otherwise, over the environment's own rules — or the flag's `default.rules` when the environment has no entry of its own: no rules at all -> `pre_rollout`; any rule serving a real split -> `rollout`; with no ALWAYS_TRUE catch-all rule -> `pre_rollout` when every rule serves false, else `rollout`; with a catch-all -> `live` when every rule serves true, `pre_rollout` when every rule serves false, else `rollout`. A rule serves true or false when its value is a plain boolean, or a weighted rollout in which one boolean value carries all of the non-zero weight; anything else counts as a split. Non-boolean flags have no status."},"description":"Derived lifecycle status for EVERY active environment, keyed by the same environment names GET /v1/environments returns. This is what tells an environment gate (`live` in production, `pre_rollout` in staging) apart from a finished rollout. Empty for non-boolean flags."},"commitSha":{"type":"string","description":"Git commit SHA of the flag's current version."},"lastModified":{"type":"object","properties":{"date":{"type":"string","description":"When the item last changed (ISO 8601 commit author date)."},"author":{"type":"string","description":"Name on the commit that last changed it. Service-account writes carry the service account's name; use the history endpoint to tell a bot from a human."}},"required":["date","author"],"description":"When and by whom this item last changed — the commit named by `commitSha`. Absent when that commit could not be attributed."}},"required":["key","valueType","tags","readyForCleanup","default","environments","variants","environmentStatuses","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags/{key}/history":{"get":{"operationId":"flags.history","summary":"Get a flag's change history","description":"Returns the git commits that changed this flag, most recent first — the audit answer to \"who changed this and when\".","tags":["Flags"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"history":{"type":"array","items":{"type":"object","properties":{"sha":{"type":"string"},"message":{"type":"string","description":"Git commit message."},"authorName":{"type":"string"},"authorEmail":{"type":"string"},"committerName":{"type":"string"},"committerEmail":{"type":"string"},"date":{"type":"string","description":"Commit date (ISO 8601)."},"isServiceAccount":{"type":"boolean","description":"True when the change was made by a service account (bot)."}},"required":["sha","message","authorName","authorEmail","committerName","committerEmail","date","isServiceAccount"]},"description":"Commits that touched this item, most recent first."}},"required":["history"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags/{key}/environments/{env}":{"patch":{"operationId":"flags.updateEnvironment","summary":"Update a flag in one environment","description":"Sets the fallback value for one environment: the unconditional rule at the end of its rule list, what users receive when no targeting rule matches. Choose the operation — toggle a bool flag (\"enabled\"), serve a single value (\"value\"), or run a percentage rollout (\"rollout\") — exactly one per request. Targeting rules and percentage rollouts above the fallback are kept, and the response reports how many in preservedTargetingRuleCount. If the environment has no rules of its own, they are first copied from the flag's default rules, so inherited targeting is kept too. `env` may also be the literal \"default\", which writes those default rules themselves — what every environment WITHOUT its own entry serves — so a flag can be turned on by default with environments still able to override it. To set the value for everyone, including users matched by targeting rules, pass \"replaceTargeting\": true; this deletes the environment's targeting rules, they remain in git history, and previousCommitSha names the version to restore with the document endpoints (GET /v1/flags/{key}/document?at=<previousCommitSha>, then PUT the result back). A changed response from that path also carries replacedTargetingRuleCount; the trailing unconditional fallback is not counted as a targeting rule by either counter. Concurrent writes are retried server-side unless \"expectedCommitSha\" pins the write to a known version.","tags":["Flags"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"env","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Environment name, e.g. \"production\" — or the literal \"default\" to write the flag's default rules, which every environment WITHOUT its own entry inherits. \"default\" is reserved and can never be an environment name (qfg-k4m6.6)."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Toggle a bool flag on/off for the environment's fallback rule — what everyone who matches no targeting rule receives."},"value":{"description":"Serve this value (bare JSON, typed against the flag's valueType) from the environment's fallback rule — what everyone who matches no targeting rule receives."},"rollout":{"type":"array","minItems":2,"items":{"type":"object","properties":{"value":{"description":"The value this share of traffic receives, as bare JSON. Must match the flag's valueType; for flags with variants it must equal a defined variant's value."},"percent":{"type":"number","minimum":0,"maximum":100,"description":"Percentage of traffic (0-100, up to 3 decimal places)."}},"required":["percent"]},"description":"Percentage rollout across values. Percents must sum to exactly 100."},"expectedCommitSha":{"type":"string","minLength":1,"description":"Optional compare-and-set: the flag's commitSha from a prior GET. When set, the write fails with 409 STALE_COMMIT_SHA if the flag changed since — no server-side retry."},"replaceTargeting":{"type":"boolean","description":"Set true to serve this value to EVERYONE, including users matched by targeting rules: the environment's rules are replaced with a single unconditional rule. Destructive — it deletes the environment's targeting rules; they remain in git history at previousCommitSha. Omit it for the default, surgical write, which sets only the fallback value and keeps targeting."}},"required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"environment":{"type":"string"},"changed":{"type":"boolean","description":"False when the environment already matched the requested state — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The flag's commit SHA immediately before this write — the version to revert to. Absent when changed is false (nothing was committed), or in the rare case where the prior version had no attributable commit. This endpoint cannot restore prior rules itself — to undo, GET /v1/flags/{key}/document?at=<this sha>, then PUT the document it returns back."},"replacedTargetingRuleCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Present only on a replaceTargeting write that actually deleted real targeting rules: how many were replaced by the single unconditional rule. A trailing ALWAYS_TRUE catch-all is the environment's fallback value rather than a targeting rule, so it is NOT counted — an environment holding one targeting rule plus a catch-all reports 1. Those rules survive in the flag's git history at previousCommitSha — read them back with GET /v1/flags/{key}/document?at=<previousCommitSha>, and restore them by PUTting that document back."},"preservedTargetingRuleCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many targeting rules the environment still holds after this write — rules that match BEFORE the fallback, so those users do not receive the value just set. Absent when there are none, and absent on a replaceTargeting write, which leaves none behind. The trailing catch-all is the fallback itself and is not counted, so an environment holding one targeting rule plus a catch-all reports 1."},"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]},"description":"The environment's rules as now stored."}},"required":["key","environment","changed","commitSha","rules"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/flags/{key}/document":{"get":{"operationId":"flags.getDocument","summary":"Get a flag's raw stored document","description":"Returns the flag exactly as stored in git — including `access`, `$schema`, `type` and every other field the detail endpoints project away — together with the commit SHA it was read at. That SHA is what PUT's `expectedCommitSha` wants.\n\nPass ?at=<sha> to read the document as of an earlier commit (from a history response or a write's `previousCommitSha`). To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nDocuments are returned verbatim and are never validated against the current schema, so historical versions read fine even when they would no longer pass validation on write. Encrypted values are the stored ciphertext and ENV_VAR values are the stored { source, lookup }: nothing is resolved server-side.","tags":["Flags"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"at","in":"query","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{4,40}$","description":"Read the document as of this commit instead of the current version — the restore half of the undo recipe. Full or abbreviated SHA, from a history/activity response or a write's `previousCommitSha`."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commitSha":{"type":"string","description":"Git commit SHA this document was read at — the file's current last-touch commit, or the requested `at` sha. Pass it straight back as `expectedCommitSha` to write."},"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The stored JSON document, verbatim — including `access`, `$schema`, `type` and any field the detail endpoints project away. Encrypted values are the stored ciphertext and ENV_VAR `provided` values are the stored { source, lookup }: the server never resolves either."}},"required":["commitSha","document"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"flags.setDocument","summary":"Replace a flag's raw stored document","description":"Replaces the stored flag document wholesale. This is FULL REPLACEMENT, not a merge: what you send is what is stored, so a field you omit is deleted. `document.key` must equal the key in the URL and `document.type` must be \"feature_flag\"; `$schema` is stamped by the server.\n\n`expectedCommitSha` is REQUIRED and must come from a fresh GET of this document — a full replacement built on a stale read would silently discard whatever landed in between. A stale token fails with 409 STALE_COMMIT_SHA and nothing is written. When the document is byte-identical to what is stored the call is a no-op: `changed` is false and no commit is made.\n\nUnlike PATCH /v1/flags/{key}/environments/{env}, there is no targeting guard here — this endpoint can write any valid document, so it can also put replaced rules back. A successful write returns `previousCommitSha`. To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nWrites ARE validated against the current schema (reads are not), so restoring a very old version can be rejected with 400 — fix the JSON forward and PUT again. This endpoint updates only: it never creates or deletes.","tags":["Flags"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete replacement document. FULL REPLACEMENT, not a merge: a field you omit is deleted. `key` must equal the URL key and `type` must match the endpoint's family. The server stamps `$schema`; everything else is stored exactly as sent."},"expectedCommitSha":{"type":"string","minLength":1,"description":"REQUIRED compare-and-set token: the `commitSha` from a FRESH GET of this document. The write fails with 409 STALE_COMMIT_SHA if the item changed since — read-before-write is enforced by contract on this surface, because a full replacement built on a stale read silently discards whatever landed in between."}},"required":["document","expectedCommitSha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"changed":{"type":"boolean","description":"False when the stored document already matched byte-for-byte — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The document's commit SHA immediately before this write — the version to restore from. Absent when changed is false (nothing was committed), or in the rare case where the prior version had no attributable commit. To undo: GET this endpoint with ?at=<previousCommitSha>, then PUT that document back pinned to the CURRENT commitSha."}},"required":["key","changed","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/configs":{"get":{"operationId":"configs.list","summary":"List configs","description":"Lists the workspace's configs.\n\nPagination is optional: without ?limit= and ?cursor= every config is returned in one response, in an unspecified order. Supplying either one orders the result ascending by `key` and returns a `nextCursor` while further rows remain — pass it back as ?cursor= to continue.","tags":["Configs"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of rows to return (1-100). OMIT to return every row in one response, which is the default and what this endpoint has always done — there is no implicit page size."},"allowEmptyValue":true,"allowReserved":true},{"name":"cursor","in":"query","schema":{"type":"string","description":"Opaque cursor from a previous response's `nextCursor`. Returns the rows ordered after it. Pass the same filters alongside it — the cursor names a position in the ordering, not a saved query. An empty string means the same as omitting it: start from the beginning."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"configs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"valueType":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"sendToClientSdk":{"type":"boolean"},"schemaKey":{"type":"string","description":"Key of the JSON schema this config's values are validated against."},"commitSha":{"type":"string"},"lastModified":{"type":"object","properties":{"date":{"type":"string","description":"When the item last changed (ISO 8601 commit author date)."},"author":{"type":"string","description":"Name on the commit that last changed it. Service-account writes carry the service account's name; use the history endpoint to tell a bot from a human."}},"required":["date","author"],"description":"When and by whom this item last changed — the commit named by `commitSha`. Absent when that commit could not be attributed."}},"required":["key","valueType","tags","commitSha"]}},"nextCursor":{"type":"string","description":"Cursor for the next page — pass it back as `cursor`. Present ONLY when more rows remain; its absence means this was the last page. Never present when `limit` and `cursor` were both omitted, since that response already contains every row."}},"required":["configs"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"configs.create","summary":"Create a config","description":"Creates a new config whose default rule serves `value`. `valueType` is REQUIRED: inferring it from a bare JSON value is ambiguous (42 is int or double, \"90s\" is string or duration), and whatever a create guessed would be what every later write to this config is validated against.\n\nThe new item is created with a single unconditional default rule and an EMPTY `environments` array, which means every environment inherits that default until one is given an entry of its own. Values are stored PLAIN: this endpoint never encrypts, the config is created at the standard access tier, and `sendToClientSdk` is false. Secrets belong in `qfg secret`. To change any of those, or to add targeting, follow up with PUT /v1/configs/{key}/document using the `commitSha` this response returns.\n\nKeys are pooled case-insensitively across flags, configs, segments and log levels, so a key held by any of them fails with 409 `details.code` = ALREADY_EXISTS, carrying `collidingType` (the stored type that holds it: feature_flag | config | segment | log_level) and `collidingKey` (the key as stored, which differs from yours only on a case-variant collision). Never retry with a mutated key name. The document written is validated in full, so a rejected create returns 400 with `details.code` = VALIDATION_FAILED and the failing field paths.","tags":["Configs"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":200,"description":"Key for the new item. Letters, numbers, dots, dashes and underscores only, 200 characters or fewer, no leading dot. Keys are pooled case-insensitively across flags, configs, segments and log levels, so a key already taken by ANY of those is a 409 ALREADY_EXISTS."},"valueType":{"enum":["bool","boolean","string","int","double","string_list","string-list","json","duration"],"description":"Value type of the new item. \"bool\" (alias \"boolean\"), \"string\", \"int\", \"double\", \"string_list\" (alias \"string-list\"), \"json\", \"duration\". These are the same types `qfg create --type` offers; the canonical spellings are the ones reads return as `valueType`. Log levels are a separate family and cannot be created here."},"value":{"description":"The value the config's default rule serves, as bare JSON typed against `valueType`. Required. Stored as a plain value: this endpoint never encrypts, and secrets belong in `qfg secret`."},"description":{"type":"string","description":"Human-readable description stored on the item."},"tags":{"type":"array","items":{"type":"string"},"description":"Tags stored on the item. Flags can be filtered by tag."}},"required":["key","valueType"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"valueType":{"type":"string","description":"The stored value type, in the canonical spelling reads return (\"bool\", \"string_list\", …) — not the alias you may have sent."},"commitSha":{"type":"string","description":"The creating commit's SHA. Pass it as `expectedCommitSha` to PUT /v1/{flags,configs}/{key}/document if the next step is reshaping the document the create could not express."},"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete document as written to git — the default rule, the empty `environments` array (every environment inherits the default until one is given its own entry), and the variants, access tier and tags the server filled in."}},"required":["key","valueType","commitSha","document"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/configs/{key}":{"get":{"operationId":"configs.get","summary":"Get a config","description":"Returns the full config document: default rules, per-environment values, and variants. Encrypted values are returned as stored (ciphertext) — decryption happens in your own runtime.","tags":["Configs"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"valueType":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"sendToClientSdk":{"type":"boolean"},"schemaKey":{"type":"string"},"default":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]}}},"required":["rules"]},"environments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Environment id, e.g. \"production\"."},"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{},"value":{},"confidential":{},"decryptWith":{}}},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]}}},"required":["id","rules"]}},"variants":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["value"]}},"commitSha":{"type":"string"},"lastModified":{"type":"object","properties":{"date":{"type":"string","description":"When the item last changed (ISO 8601 commit author date)."},"author":{"type":"string","description":"Name on the commit that last changed it. Service-account writes carry the service account's name; use the history endpoint to tell a bot from a human."}},"required":["date","author"],"description":"When and by whom this item last changed — the commit named by `commitSha`. Absent when that commit could not be attributed."}},"required":["key","valueType","tags","default","environments","variants","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/configs/{key}/history":{"get":{"operationId":"configs.history","summary":"Get a config's change history","description":"Returns the git commits that changed this config, most recent first.","tags":["Configs"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"history":{"type":"array","items":{"type":"object","properties":{"sha":{"type":"string"},"message":{"type":"string","description":"Git commit message."},"authorName":{"type":"string"},"authorEmail":{"type":"string"},"committerName":{"type":"string"},"committerEmail":{"type":"string"},"date":{"type":"string","description":"Commit date (ISO 8601)."},"isServiceAccount":{"type":"boolean","description":"True when the change was made by a service account (bot)."}},"required":["sha","message","authorName","authorEmail","committerName","committerEmail","date","isServiceAccount"]},"description":"Commits that touched this item, most recent first."}},"required":["history"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/configs/{key}/environments/{env}":{"patch":{"operationId":"configs.updateEnvironment","summary":"Set a config's value in one scope","description":"Sets the fallback value for one scope of a config: the unconditional rule at the end of its rule list, what callers receive when no targeting rule matches. `env` is an environment name — or the literal \"default\", which writes the config's `default.rules`: the value every environment WITHOUT its own entry inherits, and for many configs the only place a value is stored. Writing an environment entry OVERRIDES the default for that environment and leaves the default in place; writing \"default\" changes what every inheriting environment serves. `value` is bare JSON, typed against the config's stored valueType. Targeting rules above the fallback are kept, and the response reports how many in preservedTargetingRuleCount. If the scope has no rules of its own, they are first copied from the config's default rules, so inherited targeting is kept too. To set the value for everyone, including users matched by targeting rules, pass \"replaceTargeting\": true; this deletes the scope's targeting rules, they remain in git history, and previousCommitSha names the version to restore with the document endpoints (GET /v1/configs/{key}/document?at=<previousCommitSha>, then PUT the result back). A changed response from that path also carries replacedTargetingRuleCount; the trailing unconditional fallback is not counted as a targeting rule by either counter. Concurrent writes are retried server-side unless \"expectedCommitSha\" pins the write to a known version.","tags":["Configs"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"env","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Environment to change, e.g. \"production\" — or the literal \"default\" to write the config's default rules, which every environment WITHOUT its own entry inherits. Environment names come from GET /v1/environments; \"default\" is reserved and can never be an environment."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"description":"Required. The value this scope serves when no targeting rule matches, as bare JSON typed against the config's stored valueType (an int config takes 42, a duration config takes \"30s\"). It is written to the scope's fallback rule; targeting rules above it are kept."},"expectedCommitSha":{"type":"string","minLength":1,"description":"Optional compare-and-set: the config's commitSha from a prior GET. When set, the write fails with 409 STALE_COMMIT_SHA if the config changed since — no server-side retry."},"replaceTargeting":{"type":"boolean","description":"Set true to serve this value to EVERYONE, including users matched by targeting rules: the scope's rules are replaced with a single unconditional rule. Destructive — it deletes the scope's targeting rules; they remain in git history at previousCommitSha. Omit it for the default, surgical write, which sets only the fallback value and keeps targeting."}},"required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"environment":{"type":"string","description":"The scope written: an environment name, or \"default\" for the rules every other environment inherits."},"changed":{"type":"boolean","description":"False when the scope's rules already looked exactly like this after the write would have been applied — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The config's commit SHA immediately before this write — the version to revert to. Absent when changed is false (nothing was committed), or in the rare case where the prior version had no attributable commit. This endpoint cannot restore prior rules itself — to undo, GET /v1/configs/{key}/document?at=<this sha>, then PUT the document it returns back."},"replacedTargetingRuleCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Present only on a replaceTargeting write that actually deleted real targeting rules: how many were replaced by the single unconditional rule. A trailing catch-all is the scope's fallback value rather than a targeting rule, so it is NOT counted — a scope holding one targeting rule plus a catch-all reports 1. Those rules survive in git at previousCommitSha — read them back with GET /v1/configs/{key}/document?at=<previousCommitSha>, and restore them by PUTting that document back."},"preservedTargetingRuleCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many targeting rules the scope still holds after this write — rules that match BEFORE the fallback, so those users do not receive the value just set. Absent when there are none, and absent on a replaceTargeting write, which leaves none behind. The trailing catch-all is the fallback itself and is not counted."},"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]},"description":"The scope's rules as now stored."}},"required":["key","environment","changed","commitSha","rules"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/configs/{key}/document":{"get":{"operationId":"configs.getDocument","summary":"Get a config's raw stored document","description":"Returns the config exactly as stored in git — including `access`, `$schema`, `type` and every other field the detail endpoints project away — together with the commit SHA it was read at. That SHA is what PUT's `expectedCommitSha` wants.\n\nPass ?at=<sha> to read the document as of an earlier commit (from a history response or a write's `previousCommitSha`). To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nDocuments are returned verbatim and are never validated against the current schema, so historical versions read fine even when they would no longer pass validation on write. Encrypted values are the stored ciphertext and ENV_VAR values are the stored { source, lookup }: nothing is resolved server-side.","tags":["Configs"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"at","in":"query","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{4,40}$","description":"Read the document as of this commit instead of the current version — the restore half of the undo recipe. Full or abbreviated SHA, from a history/activity response or a write's `previousCommitSha`."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commitSha":{"type":"string","description":"Git commit SHA this document was read at — the file's current last-touch commit, or the requested `at` sha. Pass it straight back as `expectedCommitSha` to write."},"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The stored JSON document, verbatim — including `access`, `$schema`, `type` and any field the detail endpoints project away. Encrypted values are the stored ciphertext and ENV_VAR `provided` values are the stored { source, lookup }: the server never resolves either."}},"required":["commitSha","document"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"configs.setDocument","summary":"Replace a config's raw stored document","description":"Replaces the stored config document wholesale. This is FULL REPLACEMENT, not a merge: what you send is what is stored, so a field you omit is deleted. `document.key` must equal the key in the URL and `document.type` must be \"config\"; `$schema` is stamped by the server.\n\n`expectedCommitSha` is REQUIRED and must come from a fresh GET of this document — a full replacement built on a stale read would silently discard whatever landed in between. A stale token fails with 409 STALE_COMMIT_SHA and nothing is written. When the document is byte-identical to what is stored the call is a no-op: `changed` is false and no commit is made.\n\nUnlike PATCH /v1/flags/{key}/environments/{env}, there is no targeting guard here — this endpoint can write any valid document, so it can also put replaced rules back. A successful write returns `previousCommitSha`. To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nWrites ARE validated against the current schema (reads are not), so restoring a very old version can be rejected with 400 — fix the JSON forward and PUT again. This endpoint updates only: it never creates or deletes.","tags":["Configs"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete replacement document. FULL REPLACEMENT, not a merge: a field you omit is deleted. `key` must equal the URL key and `type` must match the endpoint's family. The server stamps `$schema`; everything else is stored exactly as sent."},"expectedCommitSha":{"type":"string","minLength":1,"description":"REQUIRED compare-and-set token: the `commitSha` from a FRESH GET of this document. The write fails with 409 STALE_COMMIT_SHA if the item changed since — read-before-write is enforced by contract on this surface, because a full replacement built on a stale read silently discards whatever landed in between."}},"required":["document","expectedCommitSha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"changed":{"type":"boolean","description":"False when the stored document already matched byte-for-byte — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The document's commit SHA immediately before this write — the version to restore from. Absent when changed is false (nothing was committed), or in the rare case where the prior version had no attributable commit. To undo: GET this endpoint with ?at=<previousCommitSha>, then PUT that document back pinned to the CURRENT commitSha."}},"required":["key","changed","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/log-levels":{"get":{"operationId":"logLevels.list","summary":"List log levels","description":"Lists the workspace's log-level documents — ONE PER SERVICE, not per logger. For each, the fallback level per scope (the `default` block plus every environment with its own entry) and the per-logger targeting rules as `target -> level` pairs, where `target` is a logger path prefix matched on the `quonfig-sdk-logging.key` context property. No per-logger evaluation is performed: this is the stored targeting, not the answer for one logger name. Rules that are neither a catch-all nor a logger-prefix rule are reported as `otherRuleCount` — read the full JSON with GET /v1/log-levels/{key}/document when it is not 0. Unpaginated: every document is returned in one response.","tags":["Log levels"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"logLevels":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"scopes":{"type":"array","items":{"type":"object","properties":{"scope":{"type":"string","description":"An environment name, or \"default\" for the rules every environment without its own entry inherits."},"fallbackLevel":{"type":"string","description":"Level served to every logger in this scope with no matching target rule — the scope's unconditional catch-all. ABSENT when the scope has no catch-all at all, which for an environment entry means its unmatched loggers fall through to the `default` scope."},"targets":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string","description":"Logger path prefix the rule matches (one entry per prefix)."},"level":{"type":"string","description":"Level served to loggers under that prefix."}},"required":["target","level"]},"description":"Per-logger overrides in this scope, in evaluation order (earlier wins). A rule listing several prefixes appears once per prefix. No per-logger evaluation is attempted: this is the stored targeting, not the answer for a particular logger name."},"otherRuleCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many of this scope's rules are neither the catch-all nor a logger-prefix rule — hand-written targeting this projection cannot express. Read the document with GET /v1/log-levels/{key}/document when this is not 0."}},"required":["scope","targets","otherRuleCount"]},"description":"The `default` scope first, then one entry per environment that has its own rules. An environment absent here inherits `default` entirely."},"commitSha":{"type":"string","description":"Git commit SHA of the document's current version."},"lastModified":{"type":"object","properties":{"date":{"type":"string","description":"When the item last changed (ISO 8601 commit author date)."},"author":{"type":"string","description":"Name on the commit that last changed it. Service-account writes carry the service account's name; use the history endpoint to tell a bot from a human."}},"required":["date","author"],"description":"When and by whom this item last changed — the commit named by `commitSha`. Absent when that commit could not be attributed."}},"required":["key","tags","scopes","commitSha"]},"description":"One entry per log-level document (one per service), ordered by key. Unpaginated: every document is always returned."}},"required":["logLevels"]}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/log-levels/{key}":{"patch":{"operationId":"logLevels.upsert","summary":"Set a service's log level","description":"Sets a log level for one SERVICE, surgically. There is one log-level document per service (`log-level.api-delivery`), and individual loggers are controlled by targeting rules inside it — so `key` names the service (the `log-level.` prefix is added when you omit it) and `target` names a logger path prefix within it. This endpoint PRESERVES every rule it did not name: with `target` it adds or overwrites just that prefix's rule, and without `target` it sets only the scope's fallback (catch-all) level, leaving the targeted rules above it in place. PATCH /v1/flags/{key}/environments/{env} and its config twin set a fallback the same way, but they also accept `replaceTargeting` to flatten a scope on purpose; this endpoint has no such argument — nothing here is ever flattened. If the scope has no rules of its own, they are first copied from the document's default rules, so inherited per-logger targeting is kept too. `environment` is an environment name or the literal \"default\", and DEFAULTS to \"default\": the rules every environment without its own entry inherits, which for most log-level documents is the only place a level is stored. If the service has no document yet, one is CREATED with a single unconditional default rule serving `level` (the response says `created: true`); the new key is validated in full and a key already held by a flag, config, segment or another log level fails with 409 ALREADY_EXISTS carrying `collidingType`. A changed write returns `previousCommitSha` — to undo, GET /v1/log-levels/{key}/document?at=<that sha> and PUT it back.","tags":["Log levels"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"The SERVICE's log-level key. There is one log-level document per service, not per logger, so this is e.g. \"log-level.api-delivery\" — the \"log-level.\" prefix is added for you when you omit it, so \"api-delivery\" names the same document. Individual loggers are addressed with `target`, never with a key of their own."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"level":{"enum":["TRACE","DEBUG","INFO","WARN","ERROR","FATAL"],"description":"The level to serve. One of TRACE, DEBUG, INFO, WARN, ERROR, FATAL — the stored log_level enum; anything else is a 400."},"target":{"type":"string","minLength":1,"description":"Logger path PREFIX to scope this level to, e.g. \"Quonfig.Delivery.Cache\". Matched with PROP_STARTS_WITH_ONE_OF on the `quonfig-sdk-logging.key` context property the SDKs populate from the logger path, so it covers that logger and everything under it. Omit it to set the scope's FALLBACK level — what every logger with no matching rule gets."},"environment":{"type":"string","minLength":1,"description":"Scope to write: an environment name, or the literal \"default\" — the rules every environment WITHOUT its own entry inherits. DEFAULTS to \"default\", which is where most log-level documents keep their only value. Environment names come from GET /v1/environments; \"default\" is reserved and can never be an environment."},"expectedCommitSha":{"type":"string","minLength":1,"description":"Optional compare-and-set: the document's commitSha from a prior read. When set, the write fails with 409 STALE_COMMIT_SHA if the document changed since — no server-side retry — and a service with no document yet is a 404 rather than a create."}},"required":["level"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"The document's full key, with the \"log-level.\" prefix applied — which may differ from the key you sent."},"environment":{"type":"string","description":"The scope written: an environment name, or \"default\" for the rules every other environment inherits."},"target":{"type":"string","description":"The logger prefix whose rule was written. Absent when the write set the scope's fallback level instead."},"level":{"type":"string","description":"The level now served for that target/scope."},"created":{"type":"boolean","description":"True when this call CREATED the service's log-level document — no document existed for the key, so one was written with a single unconditional default rule serving `level`."},"changed":{"type":"boolean","description":"False when the scope already served exactly this — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The document's commit SHA immediately before this write — the version to revert to. Absent when nothing was committed, when the document was created by this call (there is no prior version), and in the rare case where the prior version had no attributable commit. To undo: GET /v1/log-levels/{key}/document?at=<this sha>, then PUT the document it returns back."},"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]},"description":"The written scope's rules as now stored, in evaluation order — including every sibling rule this write preserved."}},"required":["key","environment","level","created","changed","commitSha","rules"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/log-levels/{key}/document":{"get":{"operationId":"logLevels.getDocument","summary":"Get a log level's raw stored document","description":"Returns the log level exactly as stored in git — including `access`, `$schema`, `type` and every other field the detail endpoints project away — together with the commit SHA it was read at. That SHA is what PUT's `expectedCommitSha` wants.\n\nPass ?at=<sha> to read the document as of an earlier commit (from a history response or a write's `previousCommitSha`). To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nDocuments are returned verbatim and are never validated against the current schema, so historical versions read fine even when they would no longer pass validation on write. Encrypted values are the stored ciphertext and ENV_VAR values are the stored { source, lookup }: nothing is resolved server-side.","tags":["Log levels"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"at","in":"query","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{4,40}$","description":"Read the document as of this commit instead of the current version — the restore half of the undo recipe. Full or abbreviated SHA, from a history/activity response or a write's `previousCommitSha`."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commitSha":{"type":"string","description":"Git commit SHA this document was read at — the file's current last-touch commit, or the requested `at` sha. Pass it straight back as `expectedCommitSha` to write."},"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The stored JSON document, verbatim — including `access`, `$schema`, `type` and any field the detail endpoints project away. Encrypted values are the stored ciphertext and ENV_VAR `provided` values are the stored { source, lookup }: the server never resolves either."}},"required":["commitSha","document"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"logLevels.setDocument","summary":"Replace a log level's raw stored document","description":"Replaces the stored log level document wholesale. This is FULL REPLACEMENT, not a merge: what you send is what is stored, so a field you omit is deleted. `document.key` must equal the key in the URL and `document.type` must be \"log_level\"; `$schema` is stamped by the server.\n\n`expectedCommitSha` is REQUIRED and must come from a fresh GET of this document — a full replacement built on a stale read would silently discard whatever landed in between. A stale token fails with 409 STALE_COMMIT_SHA and nothing is written. When the document is byte-identical to what is stored the call is a no-op: `changed` is false and no commit is made.\n\nUnlike PATCH /v1/flags/{key}/environments/{env}, there is no targeting guard here — this endpoint can write any valid document, so it can also put replaced rules back. A successful write returns `previousCommitSha`. To undo a bad write: GET this endpoint with ?at=<previousCommitSha> from that write's response, then PUT the document it returns back, pinned to the CURRENT commitSha.\n\nWrites ARE validated against the current schema (reads are not), so restoring a very old version can be rejected with 400 — fix the JSON forward and PUT again. This endpoint updates only: it never creates or deletes.","tags":["Log levels"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete replacement document. FULL REPLACEMENT, not a merge: a field you omit is deleted. `key` must equal the URL key and `type` must match the endpoint's family. The server stamps `$schema`; everything else is stored exactly as sent."},"expectedCommitSha":{"type":"string","minLength":1,"description":"REQUIRED compare-and-set token: the `commitSha` from a FRESH GET of this document. The write fails with 409 STALE_COMMIT_SHA if the item changed since — read-before-write is enforced by contract on this surface, because a full replacement built on a stale read silently discards whatever landed in between."}},"required":["document","expectedCommitSha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"changed":{"type":"boolean","description":"False when the stored document already matched byte-for-byte — nothing was committed."},"commitSha":{"type":"string","description":"The new commit's SHA, or the current SHA when changed is false."},"previousCommitSha":{"type":"string","description":"The document's commit SHA immediately before this write — the version to restore from. Absent when changed is false (nothing was committed), or in the rare case where the prior version had no attributable commit. To undo: GET this endpoint with ?at=<previousCommitSha>, then PUT that document back pinned to the CURRENT commitSha."}},"required":["key","changed","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's subscription is inactive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's principal lacks the required permission (`details.code` = PERMISSION_DENIED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Write conflict: `details.code` = STALE_COMMIT_SHA (the item changed since `expectedCommitSha` was read) or ALREADY_EXISTS (the key is already held — `collidingType` and `collidingKey` say by what).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change was rejected by config validation (`details.code` = VERIFY_REJECTION).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/segments":{"get":{"operationId":"segments.list","summary":"List segments","description":"Lists the workspace's segments — the named, reusable membership rule sets that flag and config targeting rules reference via the IN_SEG and NOT_IN_SEG operators.","tags":["Segments"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"segments":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"ruleCount":{"type":"number","description":"Number of membership rules the segment evaluates."},"commitSha":{"type":"string","description":"Git commit SHA of the segment's current version."}},"required":["key","ruleCount","commitSha"]}}},"required":["segments"]}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/segments/{key}":{"get":{"operationId":"segments.get","summary":"Get a segment","description":"Returns a segment's membership rules — the answer to \"who is actually in this segment\" when a flag or config rule targets it with IN_SEG or NOT_IN_SEG. Rules are evaluated in order; a context is in the segment when the first matching rule serves true.","tags":["Segments"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"default":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"criteria":{"type":"array","items":{"type":"object","properties":{"propertyName":{"type":"string","description":"Context property this criterion targets. Absent for ALWAYS_TRUE and segment operators."},"operator":{"type":"string","description":"Match operator, e.g. \"ALWAYS_TRUE\", \"PROP_IS_ONE_OF\", \"PROP_STARTS_WITH_ONE_OF\", \"IN_SEG\", \"PROP_MATCHES\". New operators may be added over time."},"valueToMatch":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}},"required":["operator"]}},"value":{"anyOf":[{"type":"object","properties":{"type":{"const":"weighted_values"},"value":{"type":"object","properties":{"weightedValues":{"type":"array","items":{"type":"object","properties":{"value":{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]},"weight":{"type":"number","description":"Rollout weight in thousandths of a percent: 1000 = 1%."}},"required":["value","weight"]}},"hashByPropertyName":{"type":"string","description":"Context property used for sticky bucketing, e.g. \"user.key\"."},"splitEvenly":{"type":"boolean"}},"required":["weightedValues"]}},"required":["type","value"]},{"type":"object","properties":{"type":{"type":"string","description":"Value type: \"bool\" | \"string\" | \"int\" | \"double\" | \"json\" | \"string_list\" | \"duration\" | \"log_level\" | \"provided\". New types may be added over time."},"value":{"description":"The raw stored value. For encrypted values this is the ciphertext; for provided values this is { source, lookup }."},"confidential":{"type":"boolean","description":"True when the value is confidential (never logged or sent to client SDKs)."},"decryptWith":{"type":"string","description":"Key of the config holding the decryption key when the value is stored encrypted. Decryption happens in the customer's own runtime, never on Quonfig servers."}},"required":["type"]}]}},"required":["criteria","value"]}}},"required":["rules"],"description":"The segment's membership rules, evaluated in order. A context is in the segment when the first matching rule serves true."},"commitSha":{"type":"string","description":"Git commit SHA of the segment's current version."}},"required":["key","default","commitSha"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/segments/{key}/history":{"get":{"operationId":"segments.history","summary":"Get a segment's change history","description":"Returns the git commits that changed this segment, most recent first — the audit answer to who changed who is in the segment, and when.","tags":["Segments"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"history":{"type":"array","items":{"type":"object","properties":{"sha":{"type":"string"},"message":{"type":"string","description":"Git commit message."},"authorName":{"type":"string"},"authorEmail":{"type":"string"},"committerName":{"type":"string"},"committerEmail":{"type":"string"},"date":{"type":"string","description":"Commit date (ISO 8601)."},"isServiceAccount":{"type":"boolean","description":"True when the change was made by a service account (bot)."}},"required":["sha","message","authorName","authorEmail","committerName","committerEmail","date","isServiceAccount"]},"description":"Commits that touched this item, most recent first."}},"required":["history"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/environments":{"get":{"operationId":"environments","summary":"List environments","description":"Lists the workspace's active environments. Use the returned `name` wherever an environment is named: the `env` path segment of PATCH /v1/flags/{key}/environments/{env}, and the `id` of an entry in a flag's or config's `environments` array. `protected` tells you up front whether a write to that environment needs an elevated role.","tags":["Environments"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"environments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Environment name — the identifier every other endpoint uses: the `env` path segment of PATCH /v1/flags/{key}/environments/{env}, and the `id` of an entry in a flag's or config's `environments` array."},"environmentType":{"type":"string","description":"Environment type: \"production\" | \"staging\" | \"test\" | \"development\". Flag lifecycle statuses are derived only for production environments. New types may be added over time."},"protected":{"type":"boolean","description":"True when writing to this environment requires an elevated role. Necessary but NOT sufficient: an individual flag or config can carry its own access level, so a write to an unprotected environment can still fail with 403 (details.code = PERMISSION_DENIED). Treat this as one input to the decision, not as the pre-write check — the 403 body names the permission that was actually required."}},"required":["name","environmentType","protected"]},"description":"The workspace's active environments, ordered development, test, staging, production — alphabetically within a type. Archived environments are omitted."}},"required":["environments"]}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/activity":{"get":{"operationId":"activity","summary":"Workspace activity feed","description":"Recent changes across the workspace, translated into human-readable audit messages. Pass ?type=&key= to get the full change history of a single item instead.","tags":["Activity"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":30,"description":"Maximum number of entries to return (1-100, default 30)."},"allowEmptyValue":true,"allowReserved":true},{"name":"type","in":"query","schema":{"enum":["feature_flag","config","log_level","segment","schema"],"description":"With `key`: narrow to one item's full change history."},"style":"deepObject","explode":true,"allowEmptyValue":true,"allowReserved":true},{"name":"key","in":"query","schema":{"type":"string","minLength":1,"description":"With `type`: the item's full key. Keys frequently contain dots (e.g. \"checkout.new-flow\") — the whole string is the key; never split a dotted key into a workspace and a key."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"activity":{"type":"array","items":{"type":"object","properties":{"sha":{"type":"string"},"date":{"type":"string","description":"Commit date (ISO 8601)."},"action":{"type":"string","description":"Change action: \"created\" | \"updated\" | \"deleted\" | \"restored\"."},"type":{"type":"string","description":"Kind of item changed: \"feature_flag\" | \"config\" | \"log_level\" | \"segment\" | \"schema\"."},"key":{"type":"string"},"authorName":{"type":"string"},"authorEmail":{"type":"string"},"isServiceAccount":{"type":"boolean"},"messages":{"type":"array","items":{"type":"object","properties":{"scope":{"type":"string","description":"What part changed: \"metadata\", \"default\", or an environment id."},"message":{"type":"string","description":"Human-readable description of the change."}},"required":["scope","message"]}}},"required":["sha","date","action","type","key","authorName","authorEmail","isServiceAccount","messages"]},"description":"Change entries, most recent first."}},"required":["activity"]}}}},"400":{"description":"Invalid request — malformed parameter or body. `details` carries structured validation issues when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested item (or the key's workspace) does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Workspace temporarily unavailable (provisioning in progress or a transient git-backend failure). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces":{"get":{"operationId":"workspaces","summary":"List accessible workspaces","description":"Returns the workspaces this credential can act on. API keys are workspace-scoped, so this contains exactly the key's workspace (empty if that workspace has been archived).","tags":["Meta"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"}},"required":["id","slug"]},"description":"Workspaces this credential can act on. API keys are workspace-scoped, so this contains exactly the key's workspace."}},"required":["workspaces"]}}}},"401":{"description":"Missing, invalid, or revoked API key (or a disabled service account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Wait the number of seconds in the Retry-After header, then retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}