Per-person settings

A decision about the person should not be an environment variable.

Which country their music searches resolve against. How long an idle workspace of theirs stays up. Which model answers their questions. What “delete” means for their notes. One place to decide, and one place to look.

Python 3.12+ Serves on :8003 Tokens from keyring MIT licensed
GET /v1/settings/spotify
  {"default_market": "PT", "timezone": "Europe/Lisbon", ...}

PUT /v1/settings/spotify/default_market
  {"value": "PT"}

# from another service: four lines, no HTTP call written by hand
resolved = await settings.resolve("spotify", user_token=caller.token)
market = resolved["default_market"]
A catalogue, not a key-value storeEvery setting has a type, bounds and a description a model can read.
Sparse storageA row exists only where somebody chose, so defaults can move.
Outages are declaredEach setting says whether to use its default or refuse.
No administrative surfaceNo route lets an operator read somebody's settings.

How it works

Two surfaces, one account, and nothing guessed.

Tokens are verified locally against the keys keyring publishes; nothing about this needed a change there.

01

The person decides

/v1/settings is for the person, or an assistant acting for them: read, describe, write, reset, export, import, read the change log, and destroy everything.

02

A service resolves

/v1/internal takes two credentials, the calling service's own token and the person's, and answers with that person's effective values for one namespace.

03

An operator narrows, visibly

A policy file can narrow a bound or pin a value. A pinned setting reports itself as pinned, so a write to it is a 409 that says why.

The API

Every setting is declared before it can be chosen.

The catalogue, with each setting's type, bounds, default and outage rule, is generated from the code: docs/catalogue.md.

GET /v1/settings/{namespace}

Read

A person's effective values for one namespace, or one key.

GET /v1/settings/schema

Describe

What can be set, with the description written for a model to read.

PUT /v1/settings/{namespace}/{key}

Write and reset

One value per account, or one per profile where the setting says so. DELETE puts the default back.

GET /v1/settings/export

Export and import

Everything a person has chosen, in one document, and back again.

GET /v1/settings/events

The change log

What was changed, and when.

/v1/internal/settings/{namespace}

For services

Resolve a namespace, or write one setting on the person's behalf, with both credentials.

Run it

Three commands.

You need Python 3.12 or newer and uv. A consuming service uses the settings-client library, which handles caching, revalidation and outages.

  1. Install.
    make install
  2. Run the gate.
    make check
  3. Serve it on :8003; the interactive docs are at /docs.
    make run

A service that does not have a namespace yet starts with adding a service; the per-service wiring is in integration.

What this is not

What this is not.

Is it where credentials go?

No. Keyring is. A value that even looks like an API key is refused with a 422 naming it, with no override.

Is it a feature-flag service?

No. A flag is the operator's decision, and everything here is the person's.

Is it a key-value store?

No. Every setting is declared in the catalogue with a type, bounds and a description, because a setting nobody described is a setting nobody can choose.

What happens when it is down?

Each setting declares it. use_default means the default is the most conservative value it has; refuse means the consuming service must fail the operation rather than guess.

Docs

Written for the person who has to run it.