Smpl Config overview
Smpl Config holds your application's configuration values — connection strings, timeouts, feature toggles for things that aren't behavioral flags. Configs support parent-based inheritance so a service can pull its values from a shared "common" parent and override only what differs, and environment overrides so production gets different values than staging without forking the config.
A concrete example
You have three services that all need the same Postgres host and port. Instead of duplicating those values across three configs, you put them in a shared parent and let each service inherit:
common (parent)
database.host = "db-prod.acme.internal"
database.port = 5432
payments (child of common)
database.pool_size = 20
notifications (child of common)
database.pool_size = 5When payments resolves its config, it sees database.host, database.port, and database.pool_size. The first two come from common; the third is its own. Change database.host in common and all three services pick it up — over WebSocket, immediately.
Configs and items
A config is a named bundle of items belonging to one account. It carries:
key— the stable identifier in URLs and SDK calls (payments,user-service,common).name— display label.description— free text.parent— UUID of another config to inherit from (ornullfor standalone).items— flat map of dot-notation keys to typed values.environments— per-environment overrides on the same items.
An item is one entry in items:
{
"database.host": {
"value": "db-prod.acme.internal",
"type": "STRING",
"description": "Primary database host."
}
}Item types: STRING, NUMBER, BOOLEAN, JSON. JSON values are stored and replaced wholesale — never deep-merged.
Flat dot-notation keys
Items are a flat map keyed by dot-notation strings. The dots are convention only — the platform doesn't interpret them as a hierarchy. database and database.host are independent keys that can coexist.
When the SDK resolves a config to a Python dict, Pydantic model, or JSON object, it expands dot-notation keys back into nested objects:
items = {
"database.host": {"value": "localhost", "type": "STRING"},
"database.port": {"value": 5432, "type": "NUMBER"},
}
# resolves to:
{"database": {"host": "localhost", "port": 5432}}Inheritance
Each config has an optional parent pointing to another config in the same account. Children inherit all keys, types, and descriptions from their ancestor chain. Override at any level by specifying the same key with a different value — type and description always come from the defining config.
A config inherits only from its explicit parent. If parent is null, the config stands alone — it doesn't fall back to any default.
The depth of an inheritance chain is capped:
- Hard cap: 10 levels. Including the config itself. Writes that would exceed this return 400.
- Plan-driven cap:
config.inheritance_depth. Lower limit on free plans, higher on paid.
Circular references are rejected at write time — setting a parent that would create a cycle returns 400.
The common config
Every account has a built-in config with key common. It's auto-created lazily the first time someone calls GET /api/v1/configs (i.e. lists configs). After creation it's a normal config: editable name, key, description, items, environments.
common serves two roles:
- Default parent. New configs without an explicit
parentfield default tocommon. - Cross-config defaults. Items defined here are inherited by every config that has
common(directly or transitively) in its ancestor chain.
common cannot be deleted — DELETE /api/v1/configs/common returns an error.
Note
Because common is created lazily, a freshly-provisioned account that never lists configs won't have a common row yet. The first list call (or first config created in the console) triggers creation.
Environment overrides
Per-environment overrides are stored under environments[env][item_key] as a flat map from item key to the override's raw value:
{
"items": {
"database.host": {"value": "localhost", "type": "STRING"}
},
"environments": {
"production": {
"database.host": "db-prod.acme.internal"
}
}
}An override carries only the value — the type and description always come from the defining config in the inheritance chain. Because the override has nothing else to carry, it's a bare key: value pair rather than a {value: V} wrapper.
Resolution for a key in environment E:
- Check
config.environments[E][key]— return if found. - Check
config.items[key]— return if found. - If
config.parentis set, restart at step 1 with the parent. - Repeat up the chain.
- If never found, return null.
There's no merge anywhere. The first matching value at the lowest level wins. There are no "tombstones" — you can't remove an inherited key, only override it with a different value.
Discovered vs managed — not yet
Config does not currently have a discovered/managed split. There's no managed column, no POST /api/v1/configs/bulk endpoint, no auto-registration from the SDK. Every config is explicitly created (in the console or via the management API), and every config counts toward config.items.
Flags and Logging both have a discovered/managed model where SDK-registered resources don't consume entitlement until promoted. The same model isn't yet implemented for Config — see Discovered vs managed.
Promote to managed
Not yet supported
Smpl Config does not currently have a promote-to-managed mechanic — every config is created managed from the start. The mechanics described under Discovered vs managed (auto-discovery via the SDK, deferred entitlement counting, explicit promote action) apply to Smpl Flags and Smpl Logging today, but not to Config. When parity ships, the section below will explain the promotion flow.
What works today is explicit creation, configuration, and deletion of configs:
- Create via the Config page in the console.
- Create via the API:
POST /api/v1/configswithidand any initialitems. - Create via the SDK:
manage.config.new(...)then.save().
Every config you create counts immediately toward config.items. The closest equivalent to "demoting" is deleting the config to free a slot.
For the full step-by-step API and SDK examples, see Smpl Config Management.
Limits
Plan-driven (entitlement keys):
config.items— total configs per account.config.keys— items per config (validated per write).config.value_size_bytes— byte size of each item value.config.inheritance_depth— chain depth.
Plus the hard caps:
- Inheritance depth: 10 levels — absolute, regardless of plan.
API surface
GET /api/v1/configs— list; lazily createscommonon first callGET /api/v1/configs/{key}— fetch onePOST /api/v1/configs— create (id required in body; defaults parent tocommon)PUT /api/v1/configs/{key}— full replace; can rename viaidfieldDELETE /api/v1/configs/{key}— soft-delete; returns 409 if other configs reference as parent
Related
- Smpl Config Management — SDK and API patterns
- Smpl Config Runtime —
bind()andget()semantics - Discovered vs managed
- API Reference — Config

