Aicscan Documentation

Temps réel

Le flux temps réel pousse les alertes de votre espace sur une connexion WebSocket, dès leur création, sans serveur public à exposer. Il convient aux tableaux de bord, aux robots internes et aux scripts de surveillance.

Text
wss://aicscan.io/api/ws/alertes[?last_id=<id>]

Authentification#

Envoyez votre clé d’API dans l’en-tête Authorization de la requête d’ouverture :

HTTP
GET /api/ws/alertes?last_id=1234 HTTP/1.1
Host: aicscan.io
Upgrade: websocket
Connection: Upgrade
Authorization: Bearer aic_k3J9xQ2a_...
  • La clé doit avoir la portée read ; les règles de l’API v1 s’appliquent : offre comprenant l’API, espace actif, débit par clé. Chaque ouverture compte pour une requête du débit et pour un appel du quota quotidien.
  • La connexion ne reçoit que les alertes de l’espace de la clé.
  • L’API WebSocket des navigateurs ne permet pas d’envoyer d’en-tête Authorization : ce flux par clé est destiné à vos serveurs. (L’espace client d’Aicscan utilise le même flux avec la session du navigateur.)

Refus à l’ouverture#

Un refus est une réponse HTTP ordinaire, avec le même corps d’erreur que l’API v1 :

HTTP Code
401 missing_api_key, invalid_api_key, api_key_revoked, api_key_expired
403 plan_required, workspace_suspended, account_under_review, insufficient_scope
429 rate_limited, daily_quota_exceeded
429 too_many_connections : plus de 5 connexions ouvertes avec cette clé, ou plus de 20 pour l’espace. Corps { "error": "…", "code": "too_many_connections" }.

L’accès est revérifié toutes les 60 secondes : une clé révoquée ou expirée, une offre qui ne comprend plus l’API ou un espace suspendu ferment la connexion avec le code 4401. Ne vous reconnectez pas automatiquement après un 4401.

Messages du serveur#

Tous les messages sont des objets JSON en texte, distingués par type.

hello#

Premier message de chaque connexion :

JSON
{ "type": "hello", "workspace_id": "0193a1b2-0c1d-7e4f-8a9b-1c2d3e4f5a01", "last_id": 1234, "ping_secs": 25 }

last_id est le point de départ du flux : la valeur demandée, ou la dernière alerte existante si la connexion n’en demandait pas (seules les alertes suivantes seront envoyées).

alert#

JSON
{
  "type": "alert",
  "id": 1235,
  "alert": {
    "id": 1235,
    "notification_id": "0193a1c9-41aa-7b2e-9c0d-1e2f3a4b5c6d",
    "type": "alert",
    "stage": "confirmed",
    "severity": "critical",
    "priority": 30,
    "rule_id": "0193a1b4-2e7f-7a10-9b3c-4d5e6f708192",
    "match_id": "0193a1c9-3f10-7d42-8a1b-2c3d4e5f6a7b",
    "title": "🔴 CRITICAL · Treasury: large USDT outflows · outgoing",
    "text": "🔴 CRITICAL · Treasury: large USDT outflows · outgoing\n✅ Confirmed · 15 confirmations\n…",
    "created_at": "2026-11-14T09:30:13.611Z"
  }
}
  • Une alerte et une étape (unconfirmed, confirmed, cancelled, escalation) donnent un seul message, même si l’espace a plusieurs canaux. Le texte est celui du premier canal servi (sa langue).
  • id est strictement croissant pour un espace : c’est la valeur à mémoriser pour la reprise.
  • Les alertes regroupées dans un récapitulatif et celles retenues par les heures calmes d’un canal sont diffusées ici tout de suite.
  • match_id permet de lire l’alerte complète : GET /v1/matches (événement, montant brut, hash de transaction).

gap#

JSON
{ "type": "gap", "from": 3234, "to": 9876 }

À la reprise, au plus 2 000 alertes manquées sont rejouées. S’il en manque davantage, le serveur envoie les 2 000 plus anciennes, puis ce message : les alertes d’identifiant from + 1 à to ne sont pas envoyées, et le direct reprend après to. Comblez l’intervalle avec GET /v1/matches si besoin.

pong#

JSON
{ "type": "pong", "last_id": 1235 }

Réponse à un ping applicatif du client (voir plus bas).

Messages du client#

Seul { "type": "ping" } est interprété ; le serveur répond pong avec le dernier identifiant envoyé. Tout autre message est ignoré. Un message de plus de 4 Kio ferme la connexion ; plus de 10 messages par seconde la ferment avec le code 1008.

Maintien de la connexion#

  • Le serveur envoie une trame WebSocket Ping toutes les 25 secondes. Les bibliothèques y répondent seules par un Pong.
  • Sans aucun message du client (un Pong suffit) pendant 60 secondes, le serveur ferme la connexion avec le code 1001.

Reprise après coupure#

  1. Mémorisez l’id de la dernière alerte traitée (durablement, si votre programme peut redémarrer).
  2. Reconnectez-vous avec ?last_id=<id> (ou l’en-tête Last-Event-ID: <id>) : le serveur envoie d’abord les alertes manquées, dans l’ordre, puis reprend le direct.
  3. Les alertes restent rejouables pendant 7 jours.
  4. Espacez les tentatives : 1 s, 2 s, 5 s, 10 s, puis 30 s.
  5. Une alerte peut arriver deux fois autour d’une reprise : ignorez tout id inférieur ou égal au dernier traité.

Clients complets#

Les deux clients appliquent toutes les règles ci-dessus : authentification, reprise avec last_id, élimination des doublons, message gap, reconnexion avec délai croissant, arrêt sur 4401 ou sur une clé refusée.

Node.js
// Client temps réel Aicscan (Node.js 18+, paquet « ws » : npm install ws).
import WebSocket from 'ws';

const URL = 'wss://aicscan.io/api/ws/alertes';
const DELAYS = [1, 2, 5, 10, 30]; // secondes entre deux reconnexions
let lastId = null; // à enregistrer durablement pour reprendre après un redémarrage
let failures = 0;
let stop = false; // clé refusée : inutile de réessayer

function connect() {
  const url = lastId == null ? URL : `${URL}?last_id=${lastId}`;
  const ws = new WebSocket(url, {
    headers: { Authorization: `Bearer ${process.env.AICSCAN_API_KEY}` },
  });

  ws.on('message', (data) => {
    const msg = JSON.parse(data.toString());
    if (msg.type === 'hello') {
      failures = 0;
      lastId ??= msg.last_id;
    } else if (msg.type === 'alert') {
      if (lastId != null && msg.id <= lastId) return; // déjà reçue
      lastId = msg.id;
      const a = msg.alert;
      console.log(a.severity, a.stage, a.title);
    } else if (msg.type === 'gap') {
      console.warn(`alertes ${msg.from + 1} à ${msg.to} non rejouées : voir GET /v1/matches`);
    }
  });

  // Refus à l'ouverture : 401, 403, 429… avec un corps JSON.
  ws.on('unexpected-response', (_req, res) => {
    let body = '';
    res.on('data', (c) => (body += c));
    res.on('end', () => console.error('connexion refusée', res.statusCode, body));
    stop = res.statusCode === 401 || res.statusCode === 403;
  });

  ws.on('close', (code) => {
    if (stop) return;
    if (code === 4401) return console.error('accès révoqué ou expiré : reconnexion abandonnée');
    const delay = DELAYS[Math.min(failures++, DELAYS.length - 1)];
    setTimeout(connect, delay * 1000);
  });
  ws.on('error', () => {}); // l'événement « close » suit toujours
}

connect();

Ces deux clients ont été exécutés contre un serveur de test qui reproduit le protocole (message hello, rejeu avec doublon, gap, fermetures 1001 et 4401, refus 401).

Limites#

Limite Valeur
Connexions simultanées par clé d’API 5
Connexions simultanées par espace 20
Message entrant 4 Kio au plus, 10 par seconde au plus
Ping du serveur toutes les 25 s
Fermeture sans réponse après 60 s
Contrôle de l’accès toutes les 60 s
Rejeu à la reprise 2 000 alertes, 7 jours

Codes de fermeture#

Code Sens Que faire
1001 Client sans réponse depuis 60 s. Se reconnecter avec last_id.
1008 Plus de 10 messages par seconde envoyés par le client. Corriger le client, puis se reconnecter.
4401 Accès révoqué ou expiré (clé, offre, espace). Ne pas se reconnecter : vérifier la clé et l’offre.
autre, ou coupure sans code Coupure réseau, redémarrage du service. Se reconnecter avec last_id, avec un délai croissant.

Aicscan ne vous demandera jamais de vous connecter à un wallet, de signer une transaction ni d’envoyer des fonds.

aicscan.io · Statut · openapi.yaml