> ## Documentation index
> Fetch the complete documentation index at: https://rolter-ai-rolter.docs7.io/llms.txt
> Ask it with GET https://rolter-ai-rolter.docs7.io/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# Export the Live Configuration as an Importable rolter.toml

> Write a running deployment's providers, groups, routes, prices and prompt templates back out as a rolter.toml that rolter-seed --import accepts, for GitOps, environment promotion and config diffs.

Rolter's seeding command (`rolter seed`, also shipped as the standalone `rolter-seed`) treats a TOML file as **desired state**: `rolter-seed --import rolter.toml` converges the database onto the file, so re-importing an edited copy applies the edits instead of duplicating rows.

The config export is the other direction. It writes what the database holds back out in exactly the shape the importer accepts, so a deployment built entirely through the dashboard still has a file you can commit to git, promote to another environment, or diff after a change.

```bash
rolter config export --output rolter.toml
```

```bash
# and back into another deployment
rolter seed --database-url "$STAGING_DATABASE_URL" --import rolter.toml
```

Exporting is read-only. It never writes to the database it reads.

---

### What round-trips

The export deliberately carries only the sections the importer consumes, so nothing in the file is dropped on the way back in.

| Section                                                                             | Notes                                                                                              |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `[[providers.readonly]]` and `[[providers.default]]`                                | name, slug, kind, `api_base`, `api_keys` (the variable name only), `egress_proxies`, in both tiers |
| `[[provider_groups.readonly]]` / `[[provider_groups.default]]` and their `.members` | group strategy and its weighted members                                                            |
| `[[routes]]`, `[routes.params]`, `[routes.param_policy]`, `[[routes.targets]]`      | strategy, admin parameter defaults, the override policy, and the weighted upstream targets         |
| `[[routes.variants]]`, `[routes.variants.params]`, `[[routes.variants.targets]]`    | weighted variants in fallback order, their parameter defaults and their ordered targets            |
| `[[model_prices]]`                                                                  | input, output, cached-input and cache-write rates with their currency                              |
| `[[budgets]]` and `[[rate_limits]]`                                                 | spend and rate caps, each naming its scope by a portable reference (below)                         |
| `[prompt_templates]`                                                                | published templates with their variables, decorators and route bindings                            |
| `[logging]` `ui_events`                                                             | whether the control plane records dashboard usage events                                           |
| `[logging.payload_capture]`                                                         | the capture policy, its size cap and its redaction and model filters                               |

A route's variants are the one place an import deletes what the file leaves out. A route whose entry declares `[[routes.variants]]` has its stored variants replaced by the file's, because an ordered, weighted split cannot be merged; the import warns with the names of the variants it removes. A route whose entry declares no variants keeps the stored ones, and the import warns when the entry gives the route `[[routes.targets]]`, since a route with variants does not use its targets.

A variant target on a provider that is left out of the gateway snapshot is still exported, under a comment that names the provider. The provider itself is not in the file, so the import cannot resolve that target and leaves the route's variants as they are rather than deleting it.

### Import warnings

When importing a hand-written file, `rolter-seed --import` warns once for each
recognised section it does not apply. The warning names the section and the
dashboard screen or API that manages it. For example, `[adaptive_routing]`
points to `PUT /api/v1/adaptive-routing-policy`. An exit status of zero means
the supported sections imported successfully, not that every declared setting
was written to the database.

`[server]`, `[tls]` and `schema_version` stay quiet: they configure the local
gateway or the file format, not database rows. `[logging]` stays quiet when it
contains only `ui_events` and `payload_capture`; other recognised logging
settings produce one warning for `logging`. Unknown keys still receive the
separate typo warning. Warnings never print section values or credentials.

### Budgets and rate limits

A cap names what it limits by `scope` and an `id` that is a **name, never a database id**, so the file means the same thing on another deployment:

```toml
[[budgets]]
scope = "team"
id = "acme/platform"      # org slug / team name
limit_usd = 500.0
period = "monthly"

[[rate_limits]]
scope = "project"
id = "acme/platform/api"  # org slug / team name / project name
rpm = 600
```

| `scope`                     | `id`                    |
| --------------------------- | ----------------------- |
| `org`                       | the org's slug          |
| `team`                      | `org-slug/team-name`    |
| `project`                   | `org-slug/team/project` |
| `business_unit`, `customer` | `org-slug/their-slug`   |
| `user`                      | the account's email     |

A cap on a **person** is named by the account's email, so the file carries the addresses of the people who hold a cap and nobody else's. It counts every key that person minted; to narrow it to the keys minted in one org or project, add `within` with the same slug path as above:

```toml
[[budgets]]
scope = "user"
id = "alice@example.com"
within = { project = "acme/platform/api" }   # or { org = "acme" }
limit_usd = 50.0
period = "monthly"
```

On import the email is looked up among the receiving deployment's accounts. An account that does not exist there is **never created**: the cap is skipped and reported in the log, like a scope that names nothing. A `within` that names nothing there skips the cap the same way. Keep the account list and the file in step by inviting or provisioning the person first.

On import each reference is looked up on the receiving deployment. A cap whose scope does not exist there, or whose `id` is a database id, is left alone and reported in the log; it is never half applied. A cap is matched to an existing row by its scope, its `within` place and, for a budget, its window, so re-importing an edited file changes the cap instead of adding another, and an unchanged file writes nothing. Caps the file does not mention are not deleted. A cap on a virtual key is not exported: keys are never exported, so it would name nothing on the other side, and the export counts it in a comment instead. A team or project whose name contains `/` is not portable for the same reason and is counted the same way.

### What is deliberately left out

An export that carried rows the importer ignores would promise more than a re-import delivers, so those rows are not written at all. The emitted file states this in a header comment, so the omission is visible in the artifact rather than only in this page.

- **Virtual keys.** Only a one-way digest of a key is stored; there is nothing to export that would work on the receiving side.
- **Caps on a virtual key.** Keys are never exported, so the cap would have nothing to attach to. A cap on a person does travel, by email (see above).
- **MCP servers and OAuth sessions, guardrails, SSO and SCIM configuration.**
- **Users, teams, projects and memberships.** `rolter-seed` creates the org, team and project it imports into.
- **Operational tables** — request logs, sessions, audit records and usage. These are history, not configuration.

### Credentials never leave

A provider key is exported only as the **name** of the environment variable it is read from:

```toml
[[providers.readonly]]
name = "openai-primary"
kind = "openai"
api_base = "https://api.openai.com"
api_keys = [{ env = "OPENAI_API_KEY" }]
```

A key that was sealed through the dashboard has no environment variable to name, so nothing is written where the credential would be, and a comment says so:

```toml
[[providers.readonly]]
name = "vllm-1"
kind = "openai_compatible"
api_base = "http://vllm-1:8000"
# api_key: not exported; any credential for this provider stays sealed in the control-plane store
```

On the receiving deployment, set that provider's key through the dashboard or point it at an `api_keys` entry with an `env` before sending traffic. `ROLTER_KEK` is not required to run an export.

Both credential spellings are read. A provider written as `api_keys = [{ env = "OPENAI_API_KEY" }]` exports the variable name just as a provider written with the deprecated `api_key_env` does — an inline `key` inside `api_keys` is a secret and is never emitted, exactly like a top-level `api_key`. Only one variable name per provider survives, because the control-plane store holds one credential per provider. The export always writes the plural spellings (`api_keys`, and `egress_proxies` for a single legacy `egress_proxy`), so `rolter check --migrations` reports it as up to date.

<Warning>
  `rolter-seed --import` never overwrites a credential sealed through the dashboard, so re-importing an export into the deployment it came from cannot clear a key that is already there.
</Warning>

### The file is already current

The export opens with a `schema_version` stamp and uses the tiered provider spelling, so `rolter check --migrations` reports the file it writes as up to date rather than as something to migrate:

```toml
schema_version = 3

[[providers.readonly]]
name = "openai-primary"
# ...
```

The two tiers mean what they mean everywhere else in a `rolter.toml`: `readonly` entries are owned by the file and immutable in the dashboard, `default` entries are seeded once and editable afterwards. Both are exported, so promoting a deployment does not quietly flatten one into the other.

### Stable output

Providers and groups are ordered by slug, routes and prices by model name, and parameter maps by key. The header carries no timestamp. Two exports of an unchanged deployment are byte-identical, so a diff between them shows only real changes — which is what makes the file usable as a review artifact in a pull request.

Identity is always a slug or a public model name, never a database id, which is what lets the same file bootstrap a different deployment.

---

### From the dashboard

**Settings → Effective Config** carries an **Export rolter.toml** button beside the header. It calls the same endpoint and saves the document as `rolter-config-YYYY-MM-DD.toml`, dated so successive exports of one deployment sit side by side instead of overwriting each other.

The button is for a superadmin or a security auditor (a [deployment role](/security/rbac#deployment-roles)), matching the endpoint. Any other role sees it disabled with a tooltip saying so rather than a "forbidden" error after the click. If the export fails, the dashboard says why in a notification — a browser download that produces nothing has no other way to report it.

---

### From the API

The control plane serves the same document over HTTP:

```http
GET /api/v1/config/export
```

```bash
curl -H "Authorization: Bearer $ROLTER_ADMIN_TOKEN" \
  http://localhost:4001/api/v1/config/export -o rolter.toml
```

The response is `application/toml` with a `rolter.toml` download filename. The endpoint is whole-deployment and read-only for a superadmin or a security auditor ([deployment role](/security/rbac#deployment-roles)): one document spans every org's providers, groups and routes, so there is no tenancy scope narrow enough to delegate it.

The CLI does not call this endpoint — it reads the store directly, the same way `rolter-seed` writes to it directly, so an export works during a bootstrap or an incident when the control plane is not serving. Both paths render through the same code, so they cannot drift.
