Services
A service is one discrete software component in your account: payments, web, notifications-worker. Every SDK client identifies itself with a service name when it boots, and the platform uses that identity to scope telemetry, attribute flag evaluations, and segment logger discovery.
A concrete example
You run three things:
payments— a Python backendweb— a TypeScript single-page appnotifications-worker— a Go worker
Each runs the SDK with a distinct service value:
from smplkit import SmplClient
with SmplClient(environment="production", service="payments") as client:
passThe first time each service boots, smplkit creates a service row automatically. The console shows three services running in production, plus whichever environments each one has reached.
How services are registered
When the SDK initializes, it sends a single bulk request to /api/v1/contexts/bulk containing both the environment and the service. If either doesn't exist yet, the platform creates it with name = key. If both already exist, the call is a no-op.
This is fire-and-forget — the SDK doesn't block on it, and a network hiccup doesn't prevent your app from starting. The next successful boot fills in whatever was missed.
You don't have to pre-create services or list them anywhere. The set of services is whatever your code is actually running.
Services are stored as contexts
Under the hood, services are stored as context instances under a built-in context type with key service. That's why the SDK's service parameter ends up in flag evaluations as a service context — your targeting rules can match on {"var": "service.key"} to vary behavior between payments and web without any extra wiring. See Contexts and context types.
Because services are contexts, they're managed through both the dedicated services API and the contexts API:
GET /api/v1/services— list all servicesGET /api/v1/services/{key}— fetch onePUT /api/v1/services/{key}— renameDELETE /api/v1/services/{key}— remove (rarely needed)
How services scope telemetry
Several pieces of platform data are partitioned by service:
- Flag sources. Each (flag, service, environment) combination is a row in
flag_source— so the console can show which services declared this flag, with what type, in which environments. Drift across services (different defaults, different types) shows up as separate rows. - Logger sources. Same idea:
logger_sourcerows track which services have observed which loggers in which environments. A logger'slast_seenis per-source, so you can see exactly where it's still active. - Service-scoped flag evaluations. Every flag evaluation automatically includes the SDK's service in the context (
{"service": {"key": "payments"}}), so rules can target services without any per-call boilerplate.
If you remove or rename a service, the SDK's next boot will recreate it with the new name. This means renaming through the API only sticks if you also update your deployment configuration.
Limits
Services are stored as context instances and share that pool:
- 10,000 context instances per account. This includes services and any other context types you create.
There's no separate "services per account" cap.
Who can do what
| Action | OWNER | ADMIN | MEMBER | VIEWER |
|---|---|---|---|---|
| Read services | ✓ | ✓ | ✓ | ✓ |
| Create, rename, delete | ✓ | ✓ | ✓ | |
| Auto-register (via SDK) | ✓ | ✓ | ✓ | ✓ |
The SDK auto-registration runs as the API key's role, which is OWNER for the bootstrap flow and whatever the minting role chose otherwise. Service auto-registration is a write, so the API key must have at least MEMBER permissions.

