---
title: Edit Dynamic Config Review
description: Edit Dynamic Config Review
product: general
token_estimate: 10128
---
# Edit Dynamic Config Review

> For AI agents: a documentation index is available at [/llms.txt](/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

## Edit Dynamic Config Review

**PUT** `/console/v1/dynamic_configs/{id}/reviews/{reviewID}`

Full URL: `https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}`

Edit Dynamic Config Review

## Authorizations

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| STATSIG-API-KEY | string | Yes | — | apiKey (header) |

## Path parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| id (path) | string | Yes | — | — |
| reviewID (path) | string | Yes | — | — |

## Body (application/json)

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| description | string | No | — | Updated human-readable description of the proposed change. Constraints: min length: 1, max length: 2000 |
| reviewer_ids | string[] | No | — | Replacement set of user IDs to request review from. Replaces the existing set. |
| reviewer_group_ids | string[] | No | — | Replacement set of reviewer group IDs to request review from. Replaces the existing set. |
| change | object | No | — | The proposed change. Provide exactly one change slot: the content bundle (`rules` and/or `default_value`), one verb/metadata field, or `restore`. |
| change.rules | object[] | No | — | Proposed full rule set for the dynamic config. Same shape as the DC update contract. |
| change.rules.name | string | Yes | — | The name of this rule. |
| change.rules.passPercentage | number | No | — | Of the users that meet the conditions of this rule, what percent should return true. Constraints: min: 0, max: 100 |
| change.rules.conditions | object[] | Yes | — | An array of Condition objects. |
| change.rules.conditions.targetValue | oneOf | No | — | Constraints: nullable, string[], number[], string, number |
| change.rules.conditions.operator | string | No | — | — |
| change.rules.conditions.field | string | No | — | Constraints: nullable |
| change.rules.conditions.customID | string | No | — | Constraints: nullable |
| change.rules.conditions.type | string | Yes | — | Allowed values: app_version, browser_name, browser_version, country, custom_field, email, environment_tier, fails_gate, fails_segment, ip_address, locale, os_name, os_version, passes_gate, passes_segment, public, time, unit_id, user_id, user_agent, url, javascript, device_model, target_app, experiment_group |
| change.rules.environments | string[] | No | — | Constraints: nullable |
| change.rules.id | string | No | — | The Statsig ID of this rule. |
| change.rules.baseID | string | No | — | The base ID of this rule, i.e. without any added metadata. Will remain the exact same throughout |
| change.rules.returnValue | object | No | — | — |
| change.rules.completedAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that have already completed. |
| change.rules.completedAutomatedRollouts.time | number | No | — | — |
| change.rules.completedAutomatedRollouts.passPercent | number | Yes | — | — |
| change.rules.pendingAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that are scheduled but not yet complete. |
| change.rules.pendingAutomatedRollouts.time | number | No | — | — |
| change.rules.pendingAutomatedRollouts.passPercent | number | Yes | — | — |
| change.rules.returnValueJson5 | string | No | — | — |
| change.rules.variants | object[] | No | — | — |
| change.rules.variants.id | string | No | — | — |
| change.rules.variants.name | string | Yes | — | — |
| change.rules.variants.passPercentage | number | Yes | — | Constraints: min: 0, max: 100 |
| change.rules.variants.returnValue | object | No | — | — |
| change.rules.variants.returnValueJson5 | string | No | — | — |
| change.default_value | object | No | — | Proposed default value for the dynamic config as a JSON object. |
| change.is_enabled | boolean | No | — | Proposed enabled state for the dynamic config. An explicit target, not a toggle. |
| change.is_archived | boolean | No | — | Proposed archived state for the dynamic config. `true` archives, `false` unarchives. |
| change.delete | boolean | No | — | Delete the dynamic config. Set to `true`. Allowed values: true |
| change.resalt | boolean | No | — | Re-randomize the dynamic config's salt. Set to `true`. Allowed values: true |
| change.disable_reviews_locally | boolean | No | — | Stop requiring review on this dynamic config. Set to `true`. Allowed values: true |
| change.restore | object | No | — | Revert to a prior committed snapshot (review type `rules`). |
| change.restore.snapshot_id | string | Yes | — | Snapshot ID (SUID) of the committed historical state to restore. Constraints: min length: 1 |
| change.release_pipeline_id | string | No | — | Set or clear the dynamic config's release pipeline (review type `update_release_pipeline`). Pass a pipeline ID to attach, or `null` to detach. Constraints: nullable, min length: 1 |
| change.id_type | string | No | — | Change the dynamic config's primary unit ID type (review type `edit_id_type`). Constraints: min length: 1 |
| change.allow_self_approval | boolean | No | — | Whether this dynamic config's reviews may be self-approved (review type `update_allow_self_approval`). |
| change.target_app_ids | string[] | No | — | Replace the dynamic config's target applications (review type `update_target_applications`). |
| change.allowed_reviewers | object | No | — | Set who may review this dynamic config (review type `allowed_reviewers`). |
| change.allowed_reviewers.user_ids | string[] | No | — | User IDs allowed to review. Replaces the existing set. |
| change.allowed_reviewers.group_ids | string[] | No | — | Reviewer group IDs allowed to review. Replaces the existing set. |

## Response (application/json)

**200** — The updated dynamic config review.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| message | string | Yes | — | A simple string explaining the result of the operation. |
| data | object | Yes | — | — |
| data.review_id | string | Yes | — | Unique ID of the review. |
| data.status | string | Yes | — | Lifecycle status of the review. Allowed values: pending, accepted, rejected, committed, changes_requested, pending_webhook_validation, failed_webhook_validation, approved_webhook_validation |
| data.type | string | Yes | — | Kind of change the review proposes. Allowed values: rules, toggle_enabled, archive, delete, resalt, disable_reviews_locally, update_team, update_release_pipeline, edit_id_type, update_allow_self_approval, update_target_applications, update_owners, allowed_reviewers |
| data.author | object | Yes | — | The user who created the review. |
| data.author.id | string | Yes | — | — |
| data.author.email | string | Yes | — | Constraints: nullable |
| data.reviewers | object[] | Yes | — | Requested reviewers (users and groups). |
| data.reviewers.id | string | Yes | — | — |
| data.reviewers.kind | string | Yes | — | Allowed values: user, group |
| data.description | string | Yes | — | Human-readable description of the proposed change. |
| data.created_at | string | Yes | — | ISO-8601 timestamp of when the review was created. |
| data.content | object | No | — | The proposed change, as a `current` / `proposed` pair over only the fields this review type updates. `null` means — and only ever means — that this review type carries no proposed value (`delete`, `resalt`, `disable_reviews_locally`); those are fully described by `type`. When the change exists but could not be read back, `content` is present with an `unavailable_reason` instead, so the two cases stay distinguishable. Only populated on single-review responses (get / create / edit / approve / reject); the list endpoint omits it entirely rather than paying a snapshot read per row. Constraints: nullable |
| data.content.type | string | Yes | — | Same value as the envelope `type`, repeated for self-containment. Allowed values: rules, toggle_enabled, archive, delete, resalt, disable_reviews_locally, update_team, update_release_pipeline, edit_id_type, update_allow_self_approval, update_target_applications, update_owners, allowed_reviewers |
| data.content.current | object | Yes | — | The entity's live value at read time, for each field this review type updates. Matches the Console's "Original Version (Before)" column for pending reviews. This is NOT status-dependent: for a committed or rejected review it is still the live entity as it stands now, which for a committed review normally equals `proposed`. |
| data.content.current.rules | object[] | No | — | The FULL rule array for this side, not just the rules that changed — commit replaces the array wholesale and rule order is evaluation-significant. Rules are serialized exactly as the entity's own read endpoint returns them, so this can be diffed against it with no translation: gates match `GET /gates/{id}` (no `returnValue`/`returnValueJson5`/`variants` — a gate rule has no return value), dynamic configs match `GET /dynamic_configs/{id}` and do carry those three. |
| data.content.current.rules.name | string | Yes | — | The name of this rule. |
| data.content.current.rules.passPercentage | number | Yes | — | Of the users that meet the conditions of this rule, what percent should return true. Constraints: min: 0, max: 100 |
| data.content.current.rules.conditions | object[] | Yes | — | An array of Condition objects. |
| data.content.current.rules.conditions.targetValue | oneOf | No | — | Constraints: nullable, string[], number[], string, number |
| data.content.current.rules.conditions.operator | string | No | — | — |
| data.content.current.rules.conditions.field | string | No | — | Constraints: nullable |
| data.content.current.rules.conditions.customID | string | No | — | Constraints: nullable |
| data.content.current.rules.conditions.type | string | Yes | — | Allowed values: app_version, browser_name, browser_version, country, custom_field, email, environment_tier, fails_gate, fails_segment, ip_address, locale, os_name, os_version, passes_gate, passes_segment, public, time, unit_id, user_id, user_agent, url, javascript, device_model, target_app, experiment_group |
| data.content.current.rules.environments | string[] | No | — | Constraints: nullable |
| data.content.current.rules.id | string | No | — | The Statsig ID of this rule. |
| data.content.current.rules.baseID | string | No | — | The base ID of this rule, i.e. without any added metadata. Will remain the exact same throughout |
| data.content.current.rules.returnValue | object | No | — | — |
| data.content.current.rules.completedAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that have already completed. |
| data.content.current.rules.completedAutomatedRollouts.time | number | No | — | — |
| data.content.current.rules.completedAutomatedRollouts.passPercent | number | Yes | — | — |
| data.content.current.rules.pendingAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that are scheduled but not yet complete. |
| data.content.current.rules.pendingAutomatedRollouts.time | number | No | — | — |
| data.content.current.rules.pendingAutomatedRollouts.passPercent | number | Yes | — | — |
| data.content.current.rules.returnValueJson5 | string | No | — | — |
| data.content.current.rules.variants | object[] | No | — | — |
| data.content.current.rules.variants.id | string | No | — | — |
| data.content.current.rules.variants.name | string | Yes | — | — |
| data.content.current.rules.variants.passPercentage | number | Yes | — | Constraints: min: 0, max: 100 |
| data.content.current.rules.variants.returnValue | object | No | — | — |
| data.content.current.rules.variants.returnValueJson5 | string | No | — | — |
| data.content.current.is_enabled | boolean | No | — | — |
| data.content.current.default_value | unknown | No | — | The config's default value. Parsed JSON when parseable, otherwise the raw stored string. For a gate this is the launch flag: `true` means "launched" (serve the gate to everyone), `false` means plain enabled/disabled. |
| data.content.current.is_launched | boolean | No | — | Dynamic configs only. Whether the config is serving its launched payload to every user, bypassing rules. Derived: a dynamic config is launched when it is disabled AND has a launched payload. This is the dynamic-config equivalent of a gate's `default_value: true`, and it is what a dynamic config `toggle_enabled` review actually moves — `default_value` plays no part in that decision. |
| data.content.current.launched_payload | unknown | No | — | Dynamic configs only. The value served to every user while the config is launched; `null` when it is not launched. Parsed JSON when parseable. Omitted entirely for secret dynamic configs — unlike `default_value`, the launched payload has no ciphertext-preserving accessor, so surfacing it would put a secret in cleartext on a response that `GET /console/v1/dynamic_configs/{id}` never returns it on. Use `is_launched` to see the state change for those. |
| data.content.current.default_value_gradual_rollout | object | No | — | Dynamic configs only. The in-progress partial rollout of a new default value, or `null` when there is none. Omitted for secret dynamic configs: the target value is stored unencrypted inside the snapshot, so it is suppressed for the same reason as `launched_payload`. Constraints: nullable |
| data.content.current.default_value_gradual_rollout.id | string | Yes | — | — |
| data.content.current.default_value_gradual_rollout.target_value | unknown | No | — | — |
| data.content.current.default_value_gradual_rollout.percentage_pass | number | Yes | — | Constraints: format: double |
| data.content.current.default_value_gradual_rollout.salt | string | Yes | — | — |
| data.content.current.default_value_gradual_rollout.rollouts | object[] | Yes | — | Constraints: nullable |
| data.content.current.default_value_gradual_rollout.rollouts.time | number | Yes | — | — |
| data.content.current.default_value_gradual_rollout.rollouts.pass_percent | number | Yes | — | — |
| data.content.current.default_value_gradual_rollout.rollouts.is_complete | boolean | Yes | — | — |
| data.content.current.salt | string | No | — | The bucketing salt. Present when committing would change it — which re-buckets every user in every percentage rollout on this config, even though no rule text moved. A `restore` review that reverts past a resalt is the case to watch for. |
| data.content.current.description | string | No | — | — |
| data.content.current.show_dev_rules | boolean | No | — | — |
| data.content.current.show_staging_rules | boolean | No | — | — |
| data.content.current.subscribers | object[] | No | — | Who is notified about alerts on this config (users, teams, Slack channels, PagerDuty on-calls). |
| data.content.current.subscribers.type | string | Yes | — | — |
| data.content.current.subscribers.id | string | Yes | — | — |
| data.content.current.analytics_enabled | boolean | No | — | — |
| data.content.current.scheduled_reload | object | No | — | Gates only. The DWH-native scheduled-reload settings, which commit writes as a unit. Absent on non-warehouse-native projects, where none of it is set on either side. |
| data.content.current.scheduled_reload.hour | number | Yes | — | Constraints: format: double, nullable |
| data.content.current.scheduled_reload.days | number[] | Yes | — | Constraints: nullable |
| data.content.current.scheduled_reload.reload_type | string | Yes | — | Allowed values: incremental, full |
| data.content.current.scheduled_reload.turbo_mode | boolean | Yes | — | Constraints: nullable |
| data.content.current.is_archived | boolean | No | — | Present on `archive` reviews. Archive and unarchive share the external type `archive`, so this field is what distinguishes them. |
| data.content.current.id_type | string | No | — | — |
| data.content.current.secondary_id_type | string | No | — | Constraints: nullable |
| data.content.current.identity_resolution_source_id | string | No | — | Constraints: nullable |
| data.content.current.target_app_ids | string[] | No | — | — |
| data.content.current.owners | object[] | No | — | — |
| data.content.current.owners.owner_id | string | Yes | — | — |
| data.content.current.owners.owner_type | string | Yes | — | — |
| data.content.current.team_id | string | No | — | Constraints: nullable |
| data.content.current.allow_self_approval | boolean | No | — | — |
| data.content.current.release_pipeline_id | string | No | — | Constraints: nullable |
| data.content.current.allowed_reviewers | object | No | — | Who may approve reviews on this config. Carried by `allowed_reviewers` reviews, which set it outright, and ALSO by `update_team` reviews, which can widen it as a side effect: moving a config onto a team whose review-approval mode is `admin_only` appends that team's admins to `user_ids`, and `team_only` appends the team itself to `group_ids`. Identical values on both sides of an `update_team` review mean that move grants no new approval rights. |
| data.content.current.allowed_reviewers.user_ids | string[] | Yes | — | — |
| data.content.current.allowed_reviewers.group_ids | string[] | Yes | — | — |
| data.content.current.schema | string | No | — | Constraints: nullable |
| data.content.current.monitoring_metrics | object[] | No | — | — |
| data.content.current.monitoring_metrics.name | string | Yes | — | — |
| data.content.current.monitoring_metrics.type | string | Yes | — | — |
| data.content.current.monitoring_metric_tags | object[] | No | — | — |
| data.content.current.monitoring_metric_tags.id | string | Yes | — | — |
| data.content.current.real_time_metrics | object[] | No | — | — |
| data.content.current.real_time_metrics.name | string | Yes | — | — |
| data.content.current.real_time_metrics.type | string | Yes | — | — |
| data.content.current.overrides | object | No | — | — |
| data.content.current.overrides.id_overrides | object[] | Yes | — | — |
| data.content.current.overrides.id_overrides.ids | string[] | Yes | — | — |
| data.content.current.overrides.id_overrides.groupID | string | Yes | — | — |
| data.content.current.overrides.id_overrides.environment | string | No | — | Constraints: nullable |
| data.content.current.overrides.id_overrides.unitID | string | No | — | Constraints: nullable |
| data.content.current.overrides.custom_id_overrides | object[] | Yes | — | — |
| data.content.current.overrides.custom_id_overrides.ids | string[] | Yes | — | — |
| data.content.current.overrides.custom_id_overrides.groupID | string | Yes | — | — |
| data.content.current.overrides.custom_id_overrides.environment | string | No | — | Constraints: nullable |
| data.content.current.overrides.custom_id_overrides.unitID | string | No | — | Constraints: nullable |
| data.content.proposed | object | Yes | — | The value this review proposes — what commit will apply. Carries exactly the same keys as `current`. Array-valued fields carry the full array on both sides, never only the changed elements. |
| data.content.proposed.rules | object[] | No | — | The FULL rule array for this side, not just the rules that changed — commit replaces the array wholesale and rule order is evaluation-significant. Rules are serialized exactly as the entity's own read endpoint returns them, so this can be diffed against it with no translation: gates match `GET /gates/{id}` (no `returnValue`/`returnValueJson5`/`variants` — a gate rule has no return value), dynamic configs match `GET /dynamic_configs/{id}` and do carry those three. |
| data.content.proposed.rules.name | string | Yes | — | The name of this rule. |
| data.content.proposed.rules.passPercentage | number | Yes | — | Of the users that meet the conditions of this rule, what percent should return true. Constraints: min: 0, max: 100 |
| data.content.proposed.rules.conditions | object[] | Yes | — | An array of Condition objects. |
| data.content.proposed.rules.conditions.targetValue | oneOf | No | — | Constraints: nullable, string[], number[], string, number |
| data.content.proposed.rules.conditions.operator | string | No | — | — |
| data.content.proposed.rules.conditions.field | string | No | — | Constraints: nullable |
| data.content.proposed.rules.conditions.customID | string | No | — | Constraints: nullable |
| data.content.proposed.rules.conditions.type | string | Yes | — | Allowed values: app_version, browser_name, browser_version, country, custom_field, email, environment_tier, fails_gate, fails_segment, ip_address, locale, os_name, os_version, passes_gate, passes_segment, public, time, unit_id, user_id, user_agent, url, javascript, device_model, target_app, experiment_group |
| data.content.proposed.rules.environments | string[] | No | — | Constraints: nullable |
| data.content.proposed.rules.id | string | No | — | The Statsig ID of this rule. |
| data.content.proposed.rules.baseID | string | No | — | The base ID of this rule, i.e. without any added metadata. Will remain the exact same throughout |
| data.content.proposed.rules.returnValue | object | No | — | — |
| data.content.proposed.rules.completedAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that have already completed. |
| data.content.proposed.rules.completedAutomatedRollouts.time | number | No | — | — |
| data.content.proposed.rules.completedAutomatedRollouts.passPercent | number | Yes | — | — |
| data.content.proposed.rules.pendingAutomatedRollouts | object[] | No | — | Read-only: Automated rollout phases that are scheduled but not yet complete. |
| data.content.proposed.rules.pendingAutomatedRollouts.time | number | No | — | — |
| data.content.proposed.rules.pendingAutomatedRollouts.passPercent | number | Yes | — | — |
| data.content.proposed.rules.returnValueJson5 | string | No | — | — |
| data.content.proposed.rules.variants | object[] | No | — | — |
| data.content.proposed.rules.variants.id | string | No | — | — |
| data.content.proposed.rules.variants.name | string | Yes | — | — |
| data.content.proposed.rules.variants.passPercentage | number | Yes | — | Constraints: min: 0, max: 100 |
| data.content.proposed.rules.variants.returnValue | object | No | — | — |
| data.content.proposed.rules.variants.returnValueJson5 | string | No | — | — |
| data.content.proposed.is_enabled | boolean | No | — | — |
| data.content.proposed.default_value | unknown | No | — | The config's default value. Parsed JSON when parseable, otherwise the raw stored string. For a gate this is the launch flag: `true` means "launched" (serve the gate to everyone), `false` means plain enabled/disabled. |
| data.content.proposed.is_launched | boolean | No | — | Dynamic configs only. Whether the config is serving its launched payload to every user, bypassing rules. Derived: a dynamic config is launched when it is disabled AND has a launched payload. This is the dynamic-config equivalent of a gate's `default_value: true`, and it is what a dynamic config `toggle_enabled` review actually moves — `default_value` plays no part in that decision. |
| data.content.proposed.launched_payload | unknown | No | — | Dynamic configs only. The value served to every user while the config is launched; `null` when it is not launched. Parsed JSON when parseable. Omitted entirely for secret dynamic configs — unlike `default_value`, the launched payload has no ciphertext-preserving accessor, so surfacing it would put a secret in cleartext on a response that `GET /console/v1/dynamic_configs/{id}` never returns it on. Use `is_launched` to see the state change for those. |
| data.content.proposed.default_value_gradual_rollout | object | No | — | Dynamic configs only. The in-progress partial rollout of a new default value, or `null` when there is none. Omitted for secret dynamic configs: the target value is stored unencrypted inside the snapshot, so it is suppressed for the same reason as `launched_payload`. Constraints: nullable |
| data.content.proposed.default_value_gradual_rollout.id | string | Yes | — | — |
| data.content.proposed.default_value_gradual_rollout.target_value | unknown | No | — | — |
| data.content.proposed.default_value_gradual_rollout.percentage_pass | number | Yes | — | Constraints: format: double |
| data.content.proposed.default_value_gradual_rollout.salt | string | Yes | — | — |
| data.content.proposed.default_value_gradual_rollout.rollouts | object[] | Yes | — | Constraints: nullable |
| data.content.proposed.default_value_gradual_rollout.rollouts.time | number | Yes | — | — |
| data.content.proposed.default_value_gradual_rollout.rollouts.pass_percent | number | Yes | — | — |
| data.content.proposed.default_value_gradual_rollout.rollouts.is_complete | boolean | Yes | — | — |
| data.content.proposed.salt | string | No | — | The bucketing salt. Present when committing would change it — which re-buckets every user in every percentage rollout on this config, even though no rule text moved. A `restore` review that reverts past a resalt is the case to watch for. |
| data.content.proposed.description | string | No | — | — |
| data.content.proposed.show_dev_rules | boolean | No | — | — |
| data.content.proposed.show_staging_rules | boolean | No | — | — |
| data.content.proposed.subscribers | object[] | No | — | Who is notified about alerts on this config (users, teams, Slack channels, PagerDuty on-calls). |
| data.content.proposed.subscribers.type | string | Yes | — | — |
| data.content.proposed.subscribers.id | string | Yes | — | — |
| data.content.proposed.analytics_enabled | boolean | No | — | — |
| data.content.proposed.scheduled_reload | object | No | — | Gates only. The DWH-native scheduled-reload settings, which commit writes as a unit. Absent on non-warehouse-native projects, where none of it is set on either side. |
| data.content.proposed.scheduled_reload.hour | number | Yes | — | Constraints: format: double, nullable |
| data.content.proposed.scheduled_reload.days | number[] | Yes | — | Constraints: nullable |
| data.content.proposed.scheduled_reload.reload_type | string | Yes | — | Allowed values: incremental, full |
| data.content.proposed.scheduled_reload.turbo_mode | boolean | Yes | — | Constraints: nullable |
| data.content.proposed.is_archived | boolean | No | — | Present on `archive` reviews. Archive and unarchive share the external type `archive`, so this field is what distinguishes them. |
| data.content.proposed.id_type | string | No | — | — |
| data.content.proposed.secondary_id_type | string | No | — | Constraints: nullable |
| data.content.proposed.identity_resolution_source_id | string | No | — | Constraints: nullable |
| data.content.proposed.target_app_ids | string[] | No | — | — |
| data.content.proposed.owners | object[] | No | — | — |
| data.content.proposed.owners.owner_id | string | Yes | — | — |
| data.content.proposed.owners.owner_type | string | Yes | — | — |
| data.content.proposed.team_id | string | No | — | Constraints: nullable |
| data.content.proposed.allow_self_approval | boolean | No | — | — |
| data.content.proposed.release_pipeline_id | string | No | — | Constraints: nullable |
| data.content.proposed.allowed_reviewers | object | No | — | Who may approve reviews on this config. Carried by `allowed_reviewers` reviews, which set it outright, and ALSO by `update_team` reviews, which can widen it as a side effect: moving a config onto a team whose review-approval mode is `admin_only` appends that team's admins to `user_ids`, and `team_only` appends the team itself to `group_ids`. Identical values on both sides of an `update_team` review mean that move grants no new approval rights. |
| data.content.proposed.allowed_reviewers.user_ids | string[] | Yes | — | — |
| data.content.proposed.allowed_reviewers.group_ids | string[] | Yes | — | — |
| data.content.proposed.schema | string | No | — | Constraints: nullable |
| data.content.proposed.monitoring_metrics | object[] | No | — | — |
| data.content.proposed.monitoring_metrics.name | string | Yes | — | — |
| data.content.proposed.monitoring_metrics.type | string | Yes | — | — |
| data.content.proposed.monitoring_metric_tags | object[] | No | — | — |
| data.content.proposed.monitoring_metric_tags.id | string | Yes | — | — |
| data.content.proposed.real_time_metrics | object[] | No | — | — |
| data.content.proposed.real_time_metrics.name | string | Yes | — | — |
| data.content.proposed.real_time_metrics.type | string | Yes | — | — |
| data.content.proposed.overrides | object | No | — | — |
| data.content.proposed.overrides.id_overrides | object[] | Yes | — | — |
| data.content.proposed.overrides.id_overrides.ids | string[] | Yes | — | — |
| data.content.proposed.overrides.id_overrides.groupID | string | Yes | — | — |
| data.content.proposed.overrides.id_overrides.environment | string | No | — | Constraints: nullable |
| data.content.proposed.overrides.id_overrides.unitID | string | No | — | Constraints: nullable |
| data.content.proposed.overrides.custom_id_overrides | object[] | Yes | — | — |
| data.content.proposed.overrides.custom_id_overrides.ids | string[] | Yes | — | — |
| data.content.proposed.overrides.custom_id_overrides.groupID | string | Yes | — | — |
| data.content.proposed.overrides.custom_id_overrides.environment | string | No | — | Constraints: nullable |
| data.content.proposed.overrides.custom_id_overrides.unitID | string | No | — | Constraints: nullable |
| data.content.unavailable_reason | string | No | — | Present ONLY when the change could not be read back, in which case `current` and `proposed` are both empty objects and carry no meaning. This exists so that "this review type has nothing to propose" (`content: null`) is never confused with "we could not tell you what this review would do". `snapshot_not_found`: the review's snapshot no longer exists — expected for old reviews, whose snapshots are pruned as the config accumulates newer ones. `snapshot_unreadable`: the snapshot exists but could not be read or serialized; that is a server-side fault and is logged. Allowed values: snapshot_not_found, snapshot_unreadable |

```json
{
  "message": "string",
  "data": {
    "review_id": "string",
    "status": "pending",
    "type": "rules",
    "author": {
      "id": "string",
      "email": "string"
    },
    "reviewers": [
      {
        "id": "string",
        "kind": "user"
      }
    ],
    "description": "string",
    "created_at": "string",
    "content": {
      "type": "rules",
      "current": {
        "rules": [
          {
            "name": "string",
            "passPercentage": 0,
            "conditions": [
              {
                "targetValue": {},
                "operator": "string",
                "field": "string",
                "customID": "string",
                "type": "app_version"
              }
            ],
            "environments": [
              "string"
            ],
            "id": "string",
            "baseID": "string",
            "returnValue": {},
            "completedAutomatedRollouts": [
              {
                "time": 0,
                "passPercent": 0
              }
            ],
            "pendingAutomatedRollouts": [
              {
                "time": 0,
                "passPercent": 0
              }
            ],
            "returnValueJson5": "string",
            "variants": [
              {
                "id": "string",
                "name": "string",
                "passPercentage": 0,
                "returnValue": {},
                "returnValueJson5": "string"
              }
            ]
          }
        ],
        "is_enabled": true,
        "default_value": null,
        "is_launched": true,
        "launched_payload": null,
        "default_value_gradual_rollout": {
          "id": "string",
          "target_value": null,
          "percentage_pass": 0,
          "salt": "string",
          "rollouts": [
            {
              "time": 0,
              "pass_percent": 0,
              "is_complete": true
            }
          ]
        },
        "salt": "string",
        "description": "string",
        "show_dev_rules": true,
        "show_staging_rules": true,
        "subscribers": [
          {
            "type": "string",
            "id": "string"
          }
        ],
        "analytics_enabled": true,
        "scheduled_reload": {
          "hour": 0,
          "days": [
            0
          ],
          "reload_type": "incremental",
          "turbo_mode": true
        },
        "is_archived": true,
        "id_type": "string",
        "secondary_id_type": "string",
        "identity_resolution_source_id": "string",
        "target_app_ids": [
          "string"
        ],
        "owners": [
          {
            "owner_id": "string",
            "owner_type": "string"
          }
        ],
        "team_id": "string",
        "allow_self_approval": true,
        "release_pipeline_id": "string",
        "allowed_reviewers": {
          "user_ids": [
            "string"
          ],
          "group_ids": [
            "string"
          ]
        },
        "schema": "string",
        "monitoring_metrics": [
          {
            "name": "string",
            "type": "string"
          }
        ],
        "monitoring_metric_tags": [
          {
            "id": "string"
          }
        ],
        "real_time_metrics": [
          {
            "name": "string",
            "type": "string"
          }
        ],
        "overrides": {
          "id_overrides": [
            {
              "ids": [
                "string"
              ],
              "groupID": "string",
              "environment": "string",
              "unitID": "string"
            }
          ],
          "custom_id_overrides": [
            {
              "ids": [
                "string"
              ],
              "groupID": "string",
              "environment": "string",
              "unitID": "string"
            }
          ]
        }
      },
      "proposed": {
        "rules": [
          {
            "name": "string",
            "passPercentage": 0,
            "conditions": [
              {
                "targetValue": {},
                "operator": "string",
                "field": "string",
                "customID": "string",
                "type": "app_version"
              }
            ],
            "environments": [
              "string"
            ],
            "id": "string",
            "baseID": "string",
            "returnValue": {},
            "completedAutomatedRollouts": [
              {
                "time": 0,
                "passPercent": 0
              }
            ],
            "pendingAutomatedRollouts": [
              {
                "time": 0,
                "passPercent": 0
              }
            ],
            "returnValueJson5": "string",
            "variants": [
              {
                "id": "string",
                "name": "string",
                "passPercentage": 0,
                "returnValue": {},
                "returnValueJson5": "string"
              }
            ]
          }
        ],
        "is_enabled": true,
        "default_value": null,
        "is_launched": true,
        "launched_payload": null,
        "default_value_gradual_rollout": {
          "id": "string",
          "target_value": null,
          "percentage_pass": 0,
          "salt": "string",
          "rollouts": [
            {
              "time": 0,
              "pass_percent": 0,
              "is_complete": true
            }
          ]
        },
        "salt": "string",
        "description": "string",
        "show_dev_rules": true,
        "show_staging_rules": true,
        "subscribers": [
          {
            "type": "string",
            "id": "string"
          }
        ],
        "analytics_enabled": true,
        "scheduled_reload": {
          "hour": 0,
          "days": [
            0
          ],
          "reload_type": "incremental",
          "turbo_mode": true
        },
        "is_archived": true,
        "id_type": "string",
        "secondary_id_type": "string",
        "identity_resolution_source_id": "string",
        "target_app_ids": [
          "string"
        ],
        "owners": [
          {
            "owner_id": "string",
            "owner_type": "string"
          }
        ],
        "team_id": "string",
        "allow_self_approval": true,
        "release_pipeline_id": "string",
        "allowed_reviewers": {
          "user_ids": [
            "string"
          ],
          "group_ids": [
            "string"
          ]
        },
        "schema": "string",
        "monitoring_metrics": [
          {
            "name": "string",
            "type": "string"
          }
        ],
        "monitoring_metric_tags": [
          {
            "id": "string"
          }
        ],
        "real_time_metrics": [
          {
            "name": "string",
            "type": "string"
          }
        ],
        "overrides": {
          "id_overrides": [
            {
              "ids": [
                "string"
              ],
              "groupID": "string",
              "environment": "string",
              "unitID": "string"
            }
          ],
          "custom_id_overrides": [
            {
              "ids": [
                "string"
              ],
              "groupID": "string",
              "environment": "string",
              "unitID": "string"
            }
          ]
        }
      },
      "unavailable_reason": "snapshot_not_found"
    }
  }
}
```

**404** — Not Found. The requested resource could not be found.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| status | integer | Yes | — | Allowed values: 404 |
| message | string | Yes | — | — |

```json
{
  "status": 404,
  "message": "Not Found. The requested resource could not be found."
}
```

## Code samples

### cURL

```bash
curl -X PUT "https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}" \
  -H "Content-Type: application/json" \
  -H "STATSIG-API-KEY: YOUR_API_KEY"
```

### Python

```python
import requests

response = requests.put(
    "https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}",
    headers={
        "Content-Type": "application/json",
        "STATSIG-API-KEY": "YOUR_API_KEY"
    }
)
data = response.json()
```

### JavaScript

```javascript
const response = await fetch("https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "STATSIG-API-KEY": "YOUR_API_KEY"
  }
});
const data = await response.json();
```

### PHP

```php
<?php
$ch = curl_init("https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "STATSIG-API-KEY: YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
```

### Go

```go
package main

import (
  "bytes"
  "net/http"
)

func main() {
req, _ := http.NewRequest("PUT", "https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("STATSIG-API-KEY", "YOUR_API_KEY")
  client := &http.Client{}
  resp, _ := client.Do(req)
  defer resp.Body.Close()
}
```

### Java

```java
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}"))
      .method("PUT", HttpRequest.BodyPublishers.noBody())
.header("Content-Type", "application/json")
      .header("STATSIG-API-KEY", "YOUR_API_KEY")
      .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
```

### Ruby

```ruby
require "net/http"
require "json"

uri = URI("https://statsigapi.net/console/v1/dynamic_configs/{id}/reviews/{reviewID}")
request = Net::HTTP::Put.new(uri)
request["Content-Type"] = "application/json"
request["STATSIG-API-KEY"] = "YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end
```

