Client and an asyncio-compatible AsyncClient for resolving feature flag values. Both clients poll the Foff API in the background, cache configs in memory, and expose the same get_feature_config method. Use the sync client for standard Python applications and the async client when running an async framework such as FastAPI or asyncio.
Requirements
Python 3.10 or later.Installation
Quick start
- Sync
- Async
Store your API key in an environment variable and read it with
os.environ["FOFF_API_KEY"]. Never hard-code it in source files.Configuration
Configure the SDK using thefoff.Config dataclass.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
api_key | str | Yes | Your Foff API key. | |
base_url | str | Yes | Base URL of the Foff API. Use https://foff.twospoon.ai/live. | |
scope | str | Yes | The scope to fetch configs for, such as production or staging. | |
polling_interval | int | No | 30 | How often, in seconds, to poll for config updates. Use 0 to disable polling. |
Validation
Both
Client and AsyncClient validate the config on construction. If api_key, base_url, or scope is empty, a ValueError is raised immediately before any network request is made.Creating a client
Sync client
UseClient as a context manager. The with block handles startup and shutdown automatically.
Client performs the following steps on entry:
Fetch all configs (blocking)
Makes a
GET request to {base_url}/api/v1/scopes/{scope}/configs and populates the in-memory cache.Async client
UseAsyncClient as an async context manager in any asyncio-based application.
AsyncClient follows the same steps as Client, but uses asyncio primitives for the background polling loop instead of a thread.
Resolving feature configs
get_feature_config(feature_name, ordered_hierarchy)
AsyncClient) and reads from the in-memory cache — no network request is made.
Hierarchy resolution algorithm
The SDK resolves from most-specific to least-specific:- It checks the full combination first —
org-1 + team-a + user-123. - It then tries shorter prefixes —
org-1 + team-a, thenorg-1. - If no override matches, it returns the feature’s
"default"value. - If the feature does not exist at all, it returns
None.
Polling
Whenpolling_interval > 0, the client refreshes configs in the background on every interval.
- Polling errors are silently ignored. The SDK continues serving the last successfully fetched config.
- Calling
close()onClient, orawait close()onAsyncClient, stops background polling and releases resources.
Recommended polling intervals
| Use case | Interval |
|---|---|
| Near-real-time | 10s |
| Standard | 30–60s |
| Low-traffic / batch | 300–600s |
Client lifecycle
Use the context manager form wherever possible — it handles startup and shutdown automatically.- Sync
- Async