> For the complete documentation index, see [llms.txt](https://docs.runway.team/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.runway.team/api/configuration-as-code.md).

# Configuration as code

{% hint style="warning" %}
This feature is still in beta and limited to Enterprise plan. If you're interested, please reach out to us.
{% endhint %}

**Configuration as code** lets you manage an app's entire setup in Runway from a single YAML file. You can keep the file in your repository, review changes to it in pull requests, and apply them from your terminal, your CI, or your AI agent through Runway's public API or [MCP server](https://docs.runway.team/api/mcp-server).

Working with a configuration file always follows the same four steps:

1. **Export** your app's current configuration as a YAML file.
2. **Edit** the file.
3. **Plan** the file. Runway compares it with your app and lists every change it would make, without changing anything yet.
4. **Apply** the plan. Runway makes exactly the changes the plan listed.

### What you can manage

A configuration file covers your app's setup, one section each:

* [**App**](https://docs.runway.team/api/configuration-as-code/file-reference#app) **–** Name, group, icon and default release type.
* [**Versioning**](https://docs.runway.team/api/configuration-as-code/file-reference#versioning) **–** The files where Runway bumps your version, and custom version formats.
* [**Tags**](https://docs.runway.team/api/configuration-as-code/file-reference#tags) and [**branches**](https://docs.runway.team/api/configuration-as-code/file-reference#branches) **–** The patterns Runway uses for release tags, and the branches it works with.
* [**Destinations**](https://docs.runway.team/api/configuration-as-code/file-reference#destinations) **–** Where the app ships, like the App Store, Google Play or OTA updates.
* [**Integrations**](https://docs.runway.team/api/configuration-as-code/file-reference#integrations) **–** CI, app stores, Slack and more, from providers already connected in Runway.
* [**Flightpath**](https://docs.runway.team/api/configuration-as-code/file-reference#flightpath) **–** The steps every release goes through, their settings, and the order they show in.
* [**Required steps**](https://docs.runway.team/api/configuration-as-code/file-reference#required-steps) **–** Which steps gate submission and release for each release type.
* [**Checklists**](https://docs.runway.team/api/configuration-as-code/file-reference#checklists) **–** Checklist items on each step, their owners, and what they block.
* [**Automations**](https://docs.runway.team/api/configuration-as-code/file-reference#automations) **–** Which automations run and when, and how they differ for hotfixes and rollbacks.
* [**Notifications**](https://docs.runway.team/api/configuration-as-code/file-reference#notifications) **–** What's sent where, with overrides for specific steps.
* [**Health metrics**](https://docs.runway.team/api/configuration-as-code/file-reference#health-metrics) **–** What Runway watches during a rollout to tell whether a release is healthy.
* [**Feature affiliations**](https://docs.runway.team/api/configuration-as-code/file-reference#feature-affiliations) **–** How tickets in your issue tracker are tied to a release.
* [**Fix requests**](https://docs.runway.team/api/configuration-as-code/file-reference#fix-requests) **–** How fixes are proposed, approved and merged once a release branch is cut.
* [**Custom strings**](https://docs.runway.team/api/configuration-as-code/file-reference#custom-strings) **–** The text Runway writes in pull requests, commits and messages.

To learn how the file works and what each field does, see the [File reference](https://docs.runway.team/api/configuration-as-code/file-reference). For step-by-step examples, like [creating an app](https://docs.runway.team/api/configuration-as-code/examples#create-an-app), [adding a destination](https://docs.runway.team/api/configuration-as-code/examples#add-a-destination) or [planning on every pull request](https://docs.runway.team/api/configuration-as-code/examples#plan-on-every-pull-request-apply-on-merge), see [Examples](https://docs.runway.team/api/configuration-as-code/examples).

### Before you start

Exporting, planning and applying all require **admin** access:

* **Public API –** An API key with admin scope. For details on creating API keys, see the [Overview](https://docs.runway.team/api/overview) article.
* **MCP –** An API key with admin scope, or OAuth as an organization admin. For setup, see [MCP server](https://docs.runway.team/api/mcp-server).

You'll also need your organization ID for the plan and apply steps, which you'll find on your organization settings page.

### Export

The export contains your app exactly as it is, with every section filled in. Comments name the IDs in the file, like channels, groups and automations, the way Runway's settings pages show them, and link each section to its part of the [File reference](https://docs.runway.team/api/configuration-as-code/file-reference). Applying an unchanged export changes nothing.

{% tabs %}
{% tab title="API" %}
Call the [GET /app/{appId}/config-file endpoint](https://api-docs.runway.team/#tag/app/operation/exportConfigFile):

```bash
curl -s "https://api.runway.team/v1/app/$APP_ID/config-file" \
  -H "X-API-KEY: $RUNWAY_API_KEY" \
  -o runway.yaml
```

{% endtab %}

{% tab title="MCP" %}
Ask your agent to export the app's config file, for example:

> Export the config file for acme-ios.

The agent uses the `export_config_file` tool and gives you the whole file. Save it, and edit it yourself: the agent won't edit configuration files for you.
{% endtab %}
{% endtabs %}

### Edit

Change what you need, and remove what you don't want to manage: [anything you leave out of the file keeps its current value](https://docs.runway.team/api/configuration-as-code/file-reference#what-you-leave-out-stays-as-it-is). To clear a value, write its empty value instead (see [Clearing a value](https://docs.runway.team/api/configuration-as-code/file-reference#clearing-a-value)).&#x20;

{% hint style="warning" %}
Please review [Sections that remove, keep or replace entries](https://docs.runway.team/api/configuration-as-code/file-reference#sections-that-remove-keep-or-replace-entries)  as trimming certain sections, such as `destinations` and `flightpath.steps`, remove entries that are not listed.&#x20;
{% endhint %}

Entries such as destinations, steps and integrations are named by [keys](https://docs.runway.team/api/configuration-as-code/file-reference#keys-how-the-file-refers-to-things) (ex `$ios`), which the rest of the file uses to refer to them.

If `app.id` is provided, the given app will be updated if found. If `app.id` is missing, a new app will be created. See [Create an app](https://docs.runway.team/api/configuration-as-code/examples#create-an-app) for more details.

### Plan

Planning never changes your app. It checks the whole file and lists every change applying it would make.

{% tabs %}
{% tab title="API" %}
Send the file to the [POST /org/{orgId}/config-file/plan endpoint](https://api-docs.runway.team/#tag/org/operation/planConfigFileChanges):

```bash
curl -s -X POST "https://api.runway.team/v1/org/$ORG_ID/config-file/plan" \
  -H "X-API-KEY: $RUNWAY_API_KEY" \
  -H "Content-Type: application/x-yaml" \
  --data-binary @runway.yaml
```

The response lists every change, with its path in the file, its `action` (`create`, `update` or `delete`), the value before and after, and its line in your file. `summary` counts the changes in each section.

```json
{
  "planId": "8f3c1e2a-...",
  "orgId": "acme",
  "appId": "acme-ios",
  "status": "planned",
  "summary": {
    "flightpath": { "update": 1 },
    "notifications": { "update": 1 }
  },
  "changes": [
    { "path": "flightpath.steps.$release.settings.appStoreSettings.phasedRelease", "action": "update", "from": false, "to": true, "line": 212 },
    { "path": "notifications.BUILD_AVAIL.enabled", "action": "update", "from": true, "to": false, "line": 640 }
  ]
}
```

{% endtab %}

{% tab title="MCP" %}
Ask your agent to plan the file, for example:

> Plan the changes in runway.yaml for acme.

The agent uploads the file with `create_config_file_upload`, which gives it a command that sends the file straight to Runway, so the file never passes through the conversation. If your agent can't run commands, it gives you the command to run. It then plans the file with `plan_config_file_changes` and shows you the changes.

Uploads expire after 24 hours.
{% endtab %}
{% endtabs %}

If the file already matches your app, the plan's status is `unchanged` and there's nothing to apply. If there are any errors with the file, the plan lists all of the errors in the response (see [Troubleshooting](#troubleshooting)).

{% hint style="info" %}
When a file creates an app, its plan lists a single change, the app's creation. Review the file itself: everything in it is how the new app will be configured.
{% endhint %}

### Apply

Applying makes exactly the changes the plan listed. Since a large file can take a while, applying runs in the background.

{% tabs %}
{% tab title="API" %}
Send the **same file** you planned, along with the plan's ID, to the [POST /org/{orgId}/config-file/apply endpoint](https://api-docs.runway.team/#tag/org/operation/applyConfigFile):

```bash
curl -s -X POST "https://api.runway.team/v1/org/$ORG_ID/config-file/apply?planId=$PLAN_ID" \
  -H "X-API-KEY: $RUNWAY_API_KEY" \
  -H "Content-Type: application/x-yaml" \
  --data-binary @runway.yaml
```

The response is `202 Accepted`, with the plan now `queued`. To follow its progress, get the plan from the [GET /org/{orgId}/config-file/plan/{planId} endpoint](https://api-docs.runway.team/#tag/org/operation/getConfigFilePlan) until it's `applied` or `failed`:

```bash
curl -s "https://api.runway.team/v1/org/$ORG_ID/config-file/plan/$PLAN_ID" \
  -H "X-API-KEY: $RUNWAY_API_KEY"
```

{% endtab %}

{% tab title="MCP" %}
Once you've reviewed the plan, ask your agent to apply it. The agent applies it with `apply_config_file`, then checks on it with `get_config_file_plan` until it's `applied` or `failed`, and tells you the result.
{% endtab %}
{% endtabs %}

A plan's `status` is one of:

* **Planned –** Planned, but not applied yet.
* **Queued –** Waiting to be applied.
* **Applying –** Being applied now.
* **Applied –** Done. The `summary` now counts the changes that were actually made.
* **Failed –** Stopped partway. `error` says why, and `failedEntry` names the entry that failed.
* **Unchanged –** The file matches the app, so there's nothing to apply.

Applying is always safe to retry:

* If your app changed since you planned, for example because someone edited it in Runway or another file was applied ([spot those changes](https://docs.runway.team/api/configuration-as-code/examples#spot-changes-made-in-runway)), applying is refused with a `409` that lists what's different. Plan the file again and review the new changes.
* If the file you send isn't the one you planned, applying is refused as well. Plan the new file instead.
* A plan can only be applied once. Applying an `applied` plan returns a `409`.
* A `failed` plan can be applied again. It continues from where it stopped, and changes that were already made stay in place.

{% hint style="info" %}
To undo an apply, plan and apply the previous version of your file. If you don't commit your configuration file and cannot retrieve your old settings, please contact Runway support to restore an automated backup.
{% endhint %}

### History

Every applied plan is listed in your organization settings, under **Configuration as code**. For each one, you'll see the app, when it was applied, the API key that applied it, and every change it made as a diff.

* **Filter by app,** or **search by plan ID** to find a specific plan, like one your CI logged.
* **Copy a plan's link** to share it, for example in a pull request.

Only plans that were applied are listed, and only for apps you can see.

<figure><img src="https://727226624-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MjFmyReUsbJqDjHEAlW%2Fuploads%2Fy5oDDbfb6tYZqrbp3Kat%2Fimage.png?alt=media&amp;token=64920762-4fa0-44eb-a64f-81ea359169f3" alt=""><figcaption></figcaption></figure>

### Troubleshooting

#### The plan is out of date

Applying is refused when the file or your app changed since you planned, so a plan never overwrites changes you haven't reviewed. The response lists what's different:

```json
{
  "code": 409,
  "message": "Unable to apply because the app or the file changed after planning. Nothing was changed. Plan again to review the latest changes.",
  "planId": "8f3c1e2a-...",
  "differences": [
    {
      "path": "notifications.BUILD_AVAIL.enabled",
      "planned": { "action": "update", "from": true, "to": false }
    }
  ]
}
```

Plan the file again and review the new changes. If someone changed the app in Runway, the new plan shows whether your file undoes their change, so you can keep it by adding it to your file.

This usually happens when:

* **Someone edits the app's settings in Runway** between your plan and your apply.
* **Another file is applied to the same app,** for example by another CI run.
* **The file changed** after it was planned.

To make your file the only way an app's settings change:

* **Limit who can edit settings in Runway.** With [role-based access controls](https://docs.runway.team/using-runway/organization-settings/team#groups), put the app's users in groups without the **Update app settings**, **Update automations**, **Edit app store release settings** and **Add, update, delete integrations** actions. Organization admins can still edit settings.
* **Apply one file at a time.** In CI, run plans and applies for the same app one after another, for example with a [GitHub Actions concurrency group](https://docs.github.com/en/actions/using-jobs/using-concurrency).
* **Apply soon after planning,** so there's less time for the app to change in between.

#### The file is invalid

Planning checks the whole file and reports every problem at once, each with its path in the file and its line:

```json
{
  "code": 400,
  "message": "Invalid configuration: the file has 2 errors",
  "errors": [
    { "path": "flightpath.steps.$alpha.upstreamCIStep", "message": "a new step needs an upstream CI step", "line": 88 },
    { "path": "integrations.$slack.provider", "message": "\"slack:T999\" isn't a provider connected in Runway", "line": 41 }
  ]
}
```

Common errors:

| Message                                                                 | What to do                                                                                                                                                                                                                                    |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key must start with $ and use only lowercase letters, numbers and _`   | Rename the key, like `$ios_app`. See [Keys](https://docs.runway.team/api/configuration-as-code/file-reference#keys-how-the-file-refers-to-things)                                                                                             |
| `"$x" is already the key of an entry in ...`                            | Keys are unique across the file, so pick another. See [Keys](https://docs.runway.team/api/configuration-as-code/file-reference#keys-how-the-file-refers-to-things)                                                                            |
| `Unknown field`                                                         | Check the spelling. Your editor's autocomplete lists the valid keys (see [The shape of a file](https://docs.runway.team/api/configuration-as-code/file-reference#the-shape-of-a-file))                                                        |
| `"..." isn't a provider connected in Runway`                            | Connect the provider in Runway and check its Provider ID under **Organization settings → Integrations**, or copy its value from an export. See [Integrations](https://docs.runway.team/api/configuration-as-code/file-reference#integrations) |
| `the app already has a ... integration, and can only have one`          | Remove the other integration of that kind in the same file                                                                                                                                                                                    |
| `... steps can't be added from a config file`                           | Only CI, beta testing, regression testing and approvals steps can be added to an existing app. Other steps come with a new destination. See [Add a step](https://docs.runway.team/api/configuration-as-code/examples#add-a-step)              |
| `step "$x" is defined but missing from the step graph`                  | Add the step to `stepGraph`. See [Flightpath](https://docs.runway.team/api/configuration-as-code/file-reference#flightpath)                                                                                                                   |
| `only these steps can be moved: ...`                                    | Put other steps back where they were in `stepGraph`. See [Remove or move a step](https://docs.runway.team/api/configuration-as-code/examples#remove-or-move-a-step)                                                                           |
| `"..." depends on this step; point it at another step in the app first` | Change the other step's reference in the same file. See [Remove or move a step](https://docs.runway.team/api/configuration-as-code/examples#remove-or-move-a-step)                                                                            |
| `a new destination needs a step of each of these types ...`             | Add the listed step types for the destination. See [Add a destination](https://docs.runway.team/api/configuration-as-code/examples#add-a-destination)                                                                                         |
| `a destination can only have one release step`                          | Keep one release step per destination                                                                                                                                                                                                         |
| `an app needs at least one destination`                                 | Keep at least one destination                                                                                                                                                                                                                 |

#### An apply failed

If an apply fails, follow these steps:

1. `GET` the plan and see if there is an error in the response
   1. `error` says what went wrong, and `failedEntry` names the entry that failed
2. Fix the cause within the file that has the failure.
3. `POST` the plan
4. `APPLY` the plan
