Config Management
Most customers manage configs through the Console UI. The management API is for infrastructure-as-code, CI/CD pipelines, setup scripts, and automated testing. Management operations are stateless HTTP calls — no WebSocket or start() needed.
Management lives at client.manage.config.* on the runtime client (SmplClient). For setup scripts and admin tooling that have no runtime side effects, construct a standalone SmplManagementClient directly — same surface, no WebSocket, no metrics, no service registration.
Create a Config
Create a new config with the factory method, set items via the typed setters, then call save() to persist. All mutations are local until save() is called.
import asyncio
from smplkit import AsyncSmplManagementClient
async with AsyncSmplManagementClient() as manage:
user_service = manage.config.new(
"user-service",
name="User Service",
description="Configuration for the user microservice.",
)
user_service.set_string("database.host", "localhost")
user_service.set_number("database.port", 5432)
user_service.set_number("database.pool_size", 5)
user_service.set_number("cache_ttl_seconds", 300)
user_service.set_boolean("enable_signup", True)
await user_service.save()Item Types
Every config item has one of four types, set via the matching typed setter:
| Type | Python / TS / Java / C# / Go | Notes |
|---|---|---|
STRING | set_string / setString / SetString | Plain strings |
NUMBER | set_number / setNumber / SetNumber | Integers and floats |
BOOLEAN | set_boolean / setBoolean / SetBoolean | true / false |
JSON | set_json / setJson / SetJson | Arbitrary JSON-serializable values |
The type is locked in once an item is created — calling set_string("max_retries", "5") and later set_number("max_retries", 5) is an error. Pick the right type at creation time.
Environment Overrides
Each item can have per-environment overrides. The runtime resolves the active environment's override; if none is set, the base value is used.
user_service.set_string(
"database.host",
"prod-users-rds.internal.acme.dev",
environment="production",
)
user_service.set_number("database.pool_size", 20, environment="production")
user_service.set_number("cache_ttl_seconds", 600, environment="production")
user_service.set_boolean("enable_signup", False, environment="production")
await user_service.save()Parent–Child Configs
A config can have a parent. Resolved values from the parent are inherited; values defined in the child override the parent. The hierarchy is at most two levels deep — the server rejects a parent that itself has a parent.
shared = manage.config.new(
"common",
name="Common",
description="Shared defaults inherited by all services.",
)
shared.set_string("app_name", "Acme SaaS Platform")
shared.set_number("max_retries", 3)
await shared.save()
user_service = manage.config.new(
"user-service",
name="User Service",
parent=shared, # or parent="common" — pass either an id or a Config
)
user_service.set_string("database.host", "localhost")
await user_service.save()Inheritance is opt-in — a config with no parent stands alone. The built-in common config is convenient as a parent for shared defaults, but it is not applied automatically.
List and Get Configs
configs = await manage.config.list()
for cfg in configs:
parent_info = f" (parent: {cfg.parent})" if cfg.parent else " (root)"
print(f" {cfg.id}{parent_info}")
fetched = await manage.config.get("user-service")
print(f"id={fetched.id}, name={fetched.name}, items={list(fetched.items.keys())}")Update a Config
Fetch a config, mutate via the typed setters, then save. Mutations accumulate locally; a single save() persists everything. Re-call set_string("key", value) (without environment=) to overwrite the base value, or with environment=... to overwrite the override.
fetched = await manage.config.get("user-service")
fetched.description = "User microservice — updated description"
fetched.set_number("cache_ttl_seconds", 900, environment="production")
await fetched.save()Delete a Config
Children must be deleted before their parents, or the server will reject the parent delete with a ConflictError.
await manage.config.delete("user-service")
# or: await fetched.delete()Sync Client (Python)
For synchronous applications (Django, Flask, CLI tools), use SmplManagementClient instead of AsyncSmplManagementClient. The API is identical but without await.
from smplkit import SmplManagementClient
with SmplManagementClient() as manage:
cfg = manage.config.new("my-service", name="My Service")
cfg.set_string("database.host", "localhost")
cfg.set_number("max_retries", 3)
cfg.save()
fetched = manage.config.get("my-service")
fetched.set_number("max_retries", 5, environment="production")
fetched.save()
configs = manage.config.list()
manage.config.delete("my-service")From a runtime client
Inside a long-running service that already holds a SmplClient, reach for management work via client.manage.config.* instead of constructing a second client — the two share HTTP transports under the hood.
from smplkit import SmplClient
with SmplClient(environment="production", service="my-service") as client:
cfg = client.manage.config.get("user-service")
cfg.set_number("cache_ttl_seconds", 900, environment="production")
cfg.save()The same pattern applies in every SDK: client.manage.config.* (Python/TS/C#), client.Manage().Config() (Go), client.manage().config (Java).
Next Steps
- Config Runtime — Resolve values, subscribe to live updates, and react to changes
- Smpl Flags — Control feature rollouts with rules-based targeting
- API Reference — Config — Full REST API documentation

