Skip to main content

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.

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.

SectionNotes
[[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 .membersgroup 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_eventswhether 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:

scopeid
orgthe org’s slug
teamorg-slug/team-name
projectorg-slug/team/project
business_unit, customerorg-slug/their-slug
userthe 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:

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:

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:

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.

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:

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), 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:

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): 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.