Group settings over HTTP, admin-gated
every /config setting plus the rules and welcome text, readable and writable by a group's admins from the Mini App. groups:read / groups:write scopes, 404 (not 403) for a group you do not administer, PATCH semantics so two admins do not overwrite each other, and the same cb_core.group_texts upsert the Telegram handlers use
- v1 scenarios
- —
- Ported
- —
- Green
- —
- Failing
- —
every /config setting plus the rules and welcome text, readable and writable by a group's admins from the Mini App. groups:read / groups:write scopes, 404 (not 403) for a group you do not administer, PATCH semantics so two admins do not overwrite each other, and the same cb_core.group_texts upsert the Telegram handlers use
Configurations.py:150-211What it does
Everything the /config menu can do in a private chat, an admin can now do over
HTTP: read the group's settings, change some of them, set the rules and the
welcome message. The writes land in the same tables through the same upsert the
Telegram handlers use (cb_core.group_config, cb_core.group_texts), so a
change made in the Mini App is live in the chat immediately and vice versa.
GET /me lists the groups the caller administers, which is how a Mini App knows
what to show before any group has been chosen.
Behaviour that must not change
The authorisation boundary is the group. group_admins membership, or
ownership of the tenant — nobody else. A caller who does not administer the
group gets 404, the same answer an unknown group gets, so a logged-in
stranger cannot walk chat ids. Membership is checked before scope, so a scope
error never confirms that a group exists.
Reading and writing are separate scopes, and a token with no scopes can only read.
PATCH, not PUT. Absent fields are left alone: a form that round-tripped every column would silently overwrite whatever another admin changed while it was open.
The bounds match the menu. A value the Telegram menu would refuse to parse is a 422 here, and an unknown field is a 422 rather than a silent no-op.
How to verify it
packages/cb-api/tests/test_group_endpoints.py drives the boundary with four
callers (an admin, a tenant owner, a stranger, and an admin holding a legacy
scope-less token) plus the validation table. The shared upsert is exercised
against a real database by qa/integration/test_group_config.py and the
handlers' own suites, which still pass unchanged — that is what proves the
refactor kept one write path rather than growing a second.
Per-group audit trail
net-new: v1 kept no trail at all. group_audit_events is distributed on group_id (migration 0010) with a UUIDv7 key, so the page is a keyset read on one shard. Written by both surfaces - the Mini App's config endpoints and the Telegram config menu, /newrules and /newwelcome - and a failed audit write never fails the action it describes
OAuth2 token issuer for the Telegram Mini App
net-new: v1 had no Mini App. /oauth2/token with three grants (Telegram initData, the login widget, refresh_token), the same RS256 keys and JWKS /login already publishes, plus scopes, a short access-token life and rotating refresh tokens stored as hashes (migration 0010). A replayed refresh token revokes its whole family. /login is untouched and its scope-less token stays read-only