Référence
Référence
Contrats RPC
Chaque endpoint NATS request-reply d'ItsBagelBot : sujets, formes JSON des requêtes/réponses, propriétaires et délais.
Sur cette page
Tous les appels interservices utilisent NATS request-reply avec des corps JSON. Les services
s’abonnent avec un groupe de files afin que toute réplique puisse répondre et que la charge
se répartisse dans le parc. Cette page répertorie les endpoints principaux ; les sujets peuvent
être remplacés par l’environnement, mais les valeurs ci-dessous sont celles livrées.
Les listes d’imports par compte de deploy/messaging/accounts.yaml indiquent quel
service peut appeler quel sujet.
Conventions
- Encodage : JSON, UTF-8. Les corps vides sont autorisés lorsque le tableau indique
{}. - Identifiants : les identifiants Twitch sont envoyés comme chaînes décimales, jamais comme nombres.
- Erreurs : les réponses contiennent un champ texte
error. Il est vide ou omis en cas de succès ; une valeur non vide signifie que l’appel a échoué (le transport a néanmoins abouti). La plupart des réponses des services de données portent aussi uncode(invalid,not_found,forbidden,conflict,unavailable,internal) défini dansinternal/domain/rpc/code.go. Les réponses du projector et de la gestion outgress ne portent queerror. - Niveau et statut :
statusest l’enum brut de la base (free|paid|vip).tierest dérivé :premiumsi le statut est paid ou vip, sinonstandard. - Délais : les appelants fixent une échéance (souvent 5 s) ; les handlers limitent leur travail avec un délai de contexte (2 s par défaut,
pkg/bus/rpcwiring.go; indiqué par groupe). Un délai du handler plus court que celui de l’appelant est volontaire afin que ce dernier reçoive une réponse d’erreur.
Carte des sujets
| Sujet (défaut) | Propriétaire | Type |
|---|---|---|
bagel.rpc.dashboard.* |
users | request-reply |
bagel.rpc.commands.* |
commands | request-reply |
bagel.rpc.admin.user.* |
users | request-reply |
bagel.rpc.broadcaster.{status,live,stream_info}.get |
projector | request-reply |
bagel.rpc.projector.dashboard.* |
projector | request-reply |
bagel.rpc.internal.projection.users.get |
users | request-reply |
bagel.rpc.internal.projection.modules.get |
modules | request-reply |
bagel.rpc.internal.projection.commands.get |
commands | request-reply |
bagel.rpc.internal.tokens.* |
users | request-reply |
bagel.rpc.outgress.* |
outgress | request-reply |
twitch.ingress.admin.shards.get |
ingress | request-reply |
bagel.cache.invalidate.<scope> |
users, projector, autres (pub) | événement (fire-and-forget) |
Autres familles, chacune documentée dans le fichier de contexte de son application sous app/ :
| Préfixe de sujet | Propriétaire |
|---|---|
bagel.rpc.modules.* (dont .govee, .spotify) |
modules |
bagel.rpc.loyalty.* |
loyalty |
bagel.rpc.transactions.*, bagel.rpc.admin.giveaways.*, bagel.rpc.internal.billing.apply |
transactions (billing apply est servi par users) |
bagel.rpc.notifications.*, bagel.rpc.admin.notifications.* |
notifications |
bagel.rpc.delegation.* |
users |
bagel.rpc.discord-data.* |
discord-data |
bagel.rpc.discord-outgress.*, bagel.rpc.dingress.* (ancien nom conservé pour la compatibilité du protocole) |
discord outgress |
bagel.rpc.gossip.* |
gossip |
bagel.rpc.health.<service> |
chaque service |
Tableau de bord (users) : bagel.rpc.dashboard.*
Libre-service de la chaîne, appelé par le tableau de bord. Groupe de files users-rpc.
Délai du handler 3 s. broadcaster_user_id est une chaîne décimale.
| Verbe | Requête | Réponse |
|---|---|---|
upsert_user |
{user_id, username, display_name, email?} |
{ok:true} ou {error} ; publie une invalidation de statut |
grant_save |
{broadcaster_user_id, access_token, refresh_token} |
{ok:true} ou {error} ; publie aussi une invalidation du cache |
grant_has |
{broadcaster_user_id} |
{has_grant:bool} |
active_set |
{broadcaster_user_id, active:bool} |
{ok:true} ou {error} ; publie une invalidation du cache |
active_get |
{broadcaster_user_id} |
{active:bool} |
status_get |
{broadcaster_user_id} |
{status, onboarded} (status est l’enum brut) |
state_get |
{broadcaster_user_id} |
état du compte : active, username, display_name, status, onboarded, locale, custom_cursor, commands_page_hidden, creator_code, plus les champs d’abonnement |
login_resolve |
{login} |
{user_id, username} |
onboarded_set |
{broadcaster_user_id, onboarded} |
{ok:true} ou {error} |
locale_set |
{broadcaster_user_id, locale} |
{ok:true} ou {error} |
cursor_set |
{broadcaster_user_id, custom_cursor} |
{ok:true} ou {error} |
commands_page_set |
{broadcaster_user_id, commands_page_hidden} |
{ok:true} ou {error} ; masque ou affiche la page publique des commandes |
delete_self |
{user_id} |
{ok:true} ou {error} |
Commandes : bagel.rpc.commands.*
Commandes de chat personnalisées d’une chaîne, appelées par le tableau de bord. Groupe de files
commands-rpc. Délai du handler 2 s (valeur par défaut). user_id est une chaîne décimale.
Champs de requête : user_id, name, aliases, response, is_active,
stream_online_only, perm, cooldown, user_cooldown, allowed_user_id,
bump_counter, original_name?, restore_uses?. Structure de réponse :
{ "commands": [ /* CommandView */ ], "restored": false, "error": "", "code": "" }
| Verbe | Requête | Notes |
|---|---|---|
list |
{user_id} |
renvoie l’ensemble actuel des commandes |
upsert |
les champs ci-dessus | write-behind (~2 s). La réponse ne contient pas de liste ; lisez l’ensemble via les sujets du tableau de bord du projector. Si original_name est défini et diffère de name, la ligne est renommée sur place (identité conservée), immédiatement et sans write-behind. Si restore_uses est positif et original_name vide, une commande supprimée est restaurée avec son nombre d’utilisations et restored est activé |
delete |
{user_id, name} |
immédiat ; invalide le cache et publie un événement de suppression |
Sources : app/db/commands/rpc/dashboard.go, internal/domain/rpc/commands/commands.go.
Utilisateurs administrateurs : bagel.rpc.admin.user.*
Gestion des utilisateurs par le personnel, appelée par la console d’administration. Groupe de files users-rpc.
Délai du handler 3 s. Chaque verbe prend actor_id ; le service détermine lui-même le rôle de l’acteur
et refuse avec forbidden sous le rôle minimal du verbe. Le champ actor_role est fourni par le client et
n’est jamais cru. La console n’ouvre jamais la base ; c’est son seul accès.
Champs de réponse (présents selon le verbe) : user, users, stats, enrollment, token,
champs de pagination (page, page_size, max_pages, has_more), plus error et code.
| Verbe | Rôle minimal | Résultat |
|---|---|---|
get, list, stats, enrollment, overview, token_status |
modérateur | user, users, stats, enrollment ou token (présence uniquement, jamais la valeur du jeton) |
ban, unban |
modérateur | user |
set_status, set_active, set_creator_code, test.set, reset |
admin | user |
token_set, token_clear |
admin | token |
bot_token_set |
contrôle du grant | token ; installe le jeton du compte bot après validation par Twitch de son identité et de ses scopes |
delete |
propriétaire | réponse vide ; supprime l’utilisateur en cascade |
La liste du personnel et le journal d’audit se trouvent sous bagel.rpc.admin.user.auth.* et
bagel.rpc.admin.user.audit.*. Sources : app/db/users/rpc/admin.go,
internal/domain/rpc/users/users.go.
Statut de la chaîne : bagel.rpc.broadcaster.{status,live,stream_info}.get
Recherches sur le chemin critique, servies par le projector. Groupe de files projector-rpc.
Délai du handler 1,5 s (app/projector/rpc/).
Ordre de résolution de status.get : cache en processus (TTL 30 s), puis projection Valkey, puis
repli avec chargement différé via le RPC de projection users (résultat mis en cache). Une chaîne
inconnue ou invalide devient standard ; un échec de recherche donne aussi standard, avec une
chaîne error.
| Sujet | Requête | Réponse |
|---|---|---|
...status.get |
{broadcaster_id} |
{broadcaster_id, tier, banned, error?} où tier vaut premium ou standard |
...live.get |
{broadcaster_id} |
{broadcaster_id, live, known, version?, error?} |
...stream_info.get |
{broadcaster_id} |
{broadcaster_id, title, game_name, viewer_count, peak_viewers, started_at, ended_at, live, known, error?} |
Le projector sert aussi le modèle de lecture du tableau de bord sous
bagel.rpc.projector.dashboard.{commands,modules}.{get,replace} (délai du handler 2 s).
Projections internes : bagel.rpc.internal.projection.*
Vues read-through utilisées par le projector pour hydrater la projection des réglages.
Délai du handler 2 s. user_id est une chaîne décimale.
| Sujet | Propriétaire / groupe de files | Réponse |
|---|---|---|
...projection.users.get |
users / users-rpc |
{user_id, status, is_active, banned, locale?, commands_page_hidden?, state_revision?, account_created_at?, error} |
...projection.modules.get |
modules / modules-rpc |
{user_id, modules: [ModuleView], error} |
...projection.commands.get |
commands / commands-rpc |
{user_id, commands: [CommandView], error} |
...projection.commands.fetches.get |
commands / commands-rpc |
définitions de fetch personnalisé de l’utilisateur |
Formes des vues (jeu complet de champs dans internal/domain/rpc/projection/projection.go) :
CommandView { "name": "...", "aliases": [], "response": "...", "is_active": true, "perm": "everyone", "cooldown": 0 }
ModuleView { "name": "...", "is_enabled": true, "configs": { /* JSON brut, omis si vide */ }, "revision": 0 }
Jetons internes : bagel.rpc.internal.tokens.*
Cycle de vie des jetons utilisateur Twitch. Outgress lit le refresh token lors du renouvellement
et réécrit le jeton renouvelé afin qu’un redémarrage ne ressuscite jamais une valeur obsolète.
Les jetons en clair transitent par ces sujets ; l’autorisation NATS limite les abonnés possibles.
Propriétaire users, groupe de files users-rpc, délai du handler 2 s.
| Verbe | Requête | Réponse |
|---|---|---|
get |
{user_id} |
{access_token, refresh_token, access_token_expires_at?, error, code} |
save |
{user_id, access_token, refresh_token, access_token_expires_at?} |
{} ou {error, code} |
Gestion outgress : bagel.rpc.outgress.*
Contrôle opérateur de l’expéditeur et recherches Twitch. Groupe de files outgress-rpc.
Délai du handler 1,5 s ; les recherches qui appellent Twitch (followage, uptime,
streaminfo, channelcounts) utilisent 4 s, et channel.get utilise 6 s pour qu’une vérification en direct tienne.
Chaque sujet tourne sur un pool de workers concurrent borné : un appel lent ne bloque jamais les autres.
Source : app/twitch/outgress/rpc/manage.go.
| Sujet | Requête | Réponse |
|---|---|---|
...channel.get |
{broadcaster_id, verify?, repair?} |
{channel, found, error, verification?} ; verify liste les abonnements Twitch en direct de la chaîne (au plus une fois par 30 s et par chaîne), enregistre ok ou failing, et repair réinscrit en arrière-plan ceux qui manquent ; délai côté appelant 8 s en vérification |
...channel.set |
{broadcaster_id, enabled?, is_mod?} |
{channel, found, error} ; crée la chaîne si elle manque ; une surcharge is_mod compte comme vérification |
...channel.list |
{} |
{channels, error} |
...system.status |
{} |
{paused, app_token_expires_in_seconds, has_user_token, error} |
...system.pause |
{paused:bool} |
{paused, error} (kill switch) |
...followage.get, ...accountage.get, ...uptime.get, ...streaminfo.get, ...channelcounts.get |
propre à la recherche | valeurs issues de Twitch pour les commandes et variables de chat |
Les récompenses de points de chaîne et les listes de chatteurs sont servies sous le même préfixe
(app/twitch/outgress/rpc/channelpoints.go, chatters.go). L’appel d’abonnement d’essai utilise
bagel.rpc.outgress.trial_subscription avec son propre groupe de files.
enabled et is_mod sont facultatifs (pointeur/omis) sur channel.set ; leur absence
signifie qu’ils restent inchangés. channel :
{ "broadcaster_id": "...", "enabled": true, "is_mod": false,
"mod_checked_at": "...", "updated_at": "...",
"sub_state": "...", "sub_error": "", "sub_checked_at": "...",
"blocked_at": "...", "grant_state": "", "grant_checked_at": "..." }
verification :
{ "expected": 9, "present": 8, "missing": ["channel.cheer"],
"repairing": false, "throttled": false, "error": "" }
Instantané des shards ingress : twitch.ingress.admin.shards.get
Vue en direct du parc de shards EventSub, servie par n’importe quelle réplique ingress (Elixir). Délai de l’appelant 5 s (ingress limite le travail par shard à ~2 s). Le corps de la requête est vide.
Le même service répond aussi à twitch.ingress.admin.shards.scale,
twitch.ingress.admin.shards.autoscale, twitch.ingress.admin.trials.{list,add,remove,set_enabled}
et bagel.rpc.ingress.conduit.get (app/twitch/ingress/config/runtime.exs,
app/twitch/ingress/lib/ingress/application.ex).
Réponse (Snapshot), abrégée. Les autres champs de premier niveau sont capacity,
desired_count, target, min_shards, max_shards, autoscale, max_load,
max_load_shard_id, trial_loads, trial_burst_loads et trial_sockets
(app/twitch/ingress/lib/ingress/admin_rpc.ex) :
{
"generated_at": "2026-06-15T00:00:00Z",
"reporter": "ingress-node1",
"nodes": ["node1", "node2"],
"shard_count": 2,
"conduit_manager": { "state": "...", "node": "...", "conduit_id": "..." },
"inventory": "available",
"shards": [
{
"shard_id": 0, "state": "connected", "node": "node1",
"session_id": "...", "bound": true, "handshake_in_flight": false,
"keepalive_ms": 10000, "attempts": 0,
"bound_at": "...", "last_frame_at": "..."
}
]
}
state du shard vaut l’une des valeurs suivantes : connected, migrating, binding, connecting,
backoff, unregistered, unresponsive, unknown. conduit_manager décrit le reconciler du conduit,
singleton du cluster. inventory vaut unavailable quand la liste des enfants du superviseur
a expiré ; chaque shard sans autre information est alors unknown.
Invalidation du cache : bagel.cache.invalidate.<scope>
Ce n’est pas un request-reply : il s’agit d’une publication fire-and-forget. Le suffixe du sujet est
la portée, par exemple status, grant, modules, delegation, notifications ou la portée de
l’état en direct. Les propriétaires publient après tout changement affectant l’état mis en cache
d’une chaîne (statut, activation, octroi ou suppression de jeton, écritures de projection).
Corps : {broadcaster_id, keys?} (broadcaster_id est une chaîne décimale,
internal/domain/invalidate/invalidate.go). Les abonnés (projector, ingress, sesame et autres)
abandonnent leur vue en cache et la rechargent avec chargement différé.