Skip to content

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.

python
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:

TypePython / TS / Java / C# / GoNotes
STRINGset_string / setString / SetStringPlain strings
NUMBERset_number / setNumber / SetNumberIntegers and floats
BOOLEANset_boolean / setBoolean / SetBooleantrue / false
JSONset_json / setJson / SetJsonArbitrary 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.

python
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.

python
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

python
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.

python
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.

python
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.

python
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.

python
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