Annexe B · API
Contrat complet de l'API du serveur Vivra : routes, entrées, sorties et erreurs. Il complète la liste de routes de la page Analyse des projets.
Généralités#
- Base :
https://api.<domaine-vivra>/v1. JSON partout, en UTF-8. - Montants on-chain renvoyés en chaîne de caractères (unités brutes) ; montants en USD en nombre à 2 décimales ; dates en ISO 8601 UTC.
- Pagination par curseur :
?limit=50&cursor=<opaque>, réponse{ "items": [...], "next_cursor": "..." | null }.limitmax 100. - Auth des votants : cookie
vivra_session(HttpOnly, Secure, SameSite=Lax), valeur = id de session. Pas de jeton dans le stockage du navigateur. - Auth du back-office : signature d'un message par un wallet de
team_wallets(SIWE, EIP-4361), puis cookievivra_adminde 12 h. - CORS : seulement le domaine de la dapp et celui du back-office.
Format d'erreur#
{ "error": { "code": "vote_closed", "message": "Voting for cycle 4 closed at 2026-11-12T18:00:00Z." } }| HTTP | code | Quand |
|---|---|---|
| 400 | invalid_input | champ manquant ou mal formé (détail dans message) |
| 401 | unauthenticated | pas de session valide |
| 403 | forbidden | compte de l'équipe, compte bloqué, wallet hors liste blanche |
| 403 | account_too_new | compte plus récent que min_account_age_hours |
| 404 | not_found | ressource inconnue |
| 409 | vote_closed / vote_not_open | hors fenêtre de vote |
| 422 | disposable_email | domaine d'email jetable |
| 422 | invalid_code | code faux ou expiré |
| 429 | rate_limited | trop de requêtes ; en-tête Retry-After |
| 500 | internal | erreur serveur, avec un request_id |
Limites de débit#
| Route | Limite |
|---|---|
POST /auth/email | 3 par email par heure, 10 par IP par heure |
POST /auth/verify | 5 essais par code, puis le code est invalidé |
POST /votes, POST /supports | 20 par session par minute |
| Routes publiques en lecture | 120 par IP par minute |
Routes publiques (sans session)#
GET /stats#
{ "takeovers": 4, "revived": 2, "verdicts": 3,
"vivra_burned": "1250000000000000000000000", "cto_fees_received_usd": 4210.55 }GET /tokens/:address#
Seulement pour un token publié (candidat d'un cycle publié ou token repris) ; 404 sinon. Renvoie l'en-tête du token, le dernier instantané, les vérifications de contrat, les socials, le dernier score de reprise avec son détail, et l'historique quotidien sur 90 jours (history: [{ "date", "price_usd", "volume_usd", "holders" }]).
GET /cycles/current#
{ "id": 4, "number": 4, "status": "voting",
"vote_opens_at": "2026-11-10T18:00:00Z", "vote_closes_at": "2026-11-12T18:00:00Z",
"voters": 3812,
"candidates": [ {
"id": 17, "position": 1, "token": "0x…", "ticker": "GRANDPA",
"tagline": "…", "thesis_short": "…", "votes": 1290, "share_pct": 33.8,
"supports": 412, "recovery_score": 68 } ],
"my_vote": 17 }my_vote vaut null sans session ou sans vote.
GET /candidates/:id#
La fiche complète : tous les blocs de « Contenu d'une fiche candidat », plus recovery_score (note figée à la publication et son détail) et recovery_score_live.
GET /cycles et GET /cycles/:id#
Historique des cycles, avec le gagnant et les résultats finaux.
GET /operations/:id#
{ "id": 4, "token": "0x…", "ticker": "GRANDPA", "status": "running", "j0": "…",
"steps": [ { "key": "designer.directions", "agent": "designer", "due_at": "…", "status": "done" } ],
"spent_usd": 38.12,
"buyback": { "envelope_wei": "…", "cap_wei": "…", "slices_done": 9, "slices_total": 23, "burned": "…" },
"links": { "site": "https://grandpa.<domaine-vivra>", "x": "https://x.com/…", "telegram": "https://t.me/…" },
"cto_request": { "status": "submitted" },
"verdict": null }GET /operations/:id/log#
Pagination par curseur. Chaque ligne : { "id", "at", "agent", "kind", "summary", "cost_usd" }. Jamais payload.
GET /ledger#
Paramètres : type, from, to, limit, cursor. Chaque ligne : type, jeton, montant brut et en USD, wallets source et destination, hash, date.
GET /wallets#
Les adresses publiques de tous les wallets de la table wallets, avec leur solde courant.
Connexion#
POST /auth/email#
Entrée : { "email": "a@b.com", "captcha_token": "…" }. Normalise l'email (minuscules, sans espaces), refuse les domaines jetables, crée ou retrouve l'utilisateur, envoie un email contenant un code à 6 chiffres et un lien magique (/auth/magic?token=…). Réponse 204, toujours la même, que l'email existe ou non.
POST /auth/verify#
Entrée : { "email": "a@b.com", "code": "482913" } ou { "magic_token": "…" }. Crée la session (30 jours) et pose le cookie. Réponse { "user": { "id": 812, "created_at": "…", "can_vote_at": "…" } }.
POST /auth/logout#
Révoque la session courante. 204.
GET /me#
{ "id", "created_at", "can_vote_at", "votes": [{ "cycle_id", "candidate_id" }], "supports": [candidate_id] }.
Vote et soutien (session requise)#
POST /votes#
Entrée : { "candidate_id": 17 }. Crée ou remplace le vote de l'utilisateur pour le cycle de ce candidat. Erreurs : vote_not_open, vote_closed, account_too_new, forbidden. Réponse : le même objet que GET /cycles/current, à jour.
POST /supports et DELETE /supports/:candidate_id#
Entrée : { "candidate_id": 17 }. Idempotent. Réponse { "supports": 413 }.
Temps réel (WebSocket)#
Connexion : wss://api.<domaine-vivra>/v1/ws. Le client envoie { "subscribe": "cycle:4" } ou { "subscribe": "operation:4" }. Messages reçus :
type | Canal | Contenu |
|---|---|---|
vote.tally | cycle:* | { candidates: [{ id, votes, share_pct }], voters }, au plus 1 par seconde |
step.updated | operation:* | { key, status, at } |
log.appended | operation:* | une ligne du journal public |
slice.executed | operation:* | { index, amount_in_wei, amount_burned, tx_hash, at } |
trade | operation:* | { price_usd, amount_usd, side, at } pour le chart live |
verdict | operation:* | { verdict, at } |
Ping toutes les 25 s, reconnexion automatique côté client avec reprise depuis le dernier id reçu ({ "subscribe": "operation:4", "since": 1532 }).
Back-office (préfixe /admin, session admin requise)#
| Méthode et route | Rôle |
|---|---|
POST /admin/auth/nonce, POST /admin/auth/verify | connexion SIWE d'un wallet de l'équipe |
POST /admin/projects | soumettre une adresse (et des liens optionnels) ; lance l'analyse du Scanner |
GET /admin/projects?status=analyzed | projets analysés, avec cible, sécurité et score de reprise |
POST /admin/projects/:address/review | { "decision": "validate" | "reject" | "rerun", "reason": "…" } |
POST /admin/cycles | créer un cycle (numéro, horaires, délai minimal du compte) |
PUT /admin/cycles/:id/candidates | fixer les 5 candidats et leurs textes ; refusé si le cycle est publié |
POST /admin/candidates/:id/prereport | lancer le pré-rapport du Scanner |
POST /admin/cycles/:id/publish | publier les 5 fiches d'un coup ; fige les scores |
GET /admin/validations?status=pending | file de validation |
POST /admin/validations/:id | { "decision": "approve" | "reject", "reason": "…" } |
POST /admin/operations/:id/pause, /resume, /stop | pilotage et arrêt d'urgence d'une opération |
POST /admin/buyback/stop | arrêt d'urgence de toutes les tranches |
PUT /admin/cto-requests/:id | mettre à jour le statut de la demande Pons |
GET /admin/settings, PUT /admin/settings/:key | seuils et poids, avec audit |
GET /admin/audit | journal des actions de l'équipe |
Chaque appel /admin en écriture ajoute une ligne à admin_audit.