Aller au contenu

API

L'interface de la console parle a une API JSON versionnee.

Le contrat

Le contrat complet est servi a https://overwatch.guillaume.ink/api/v1/openapi.json, en OpenAPI 3.1. Il est derive du code : chaque handler porte son attribut de chemin, chaque structure sa description, et un test echoue si une route montee n'y figure pas. Un document ecrit a la main deriverait en trois semaines.

C'est la source pour tout client : on genere les types, on ne les ecrit pas. - npx openapi-typescript https://overwatch.guillaume.ink/api/v1/openapi.json -o src/api.d.ts - puis openapi-fetch pour les appels, avec les chemins et les corps verifies a la compilation.

Le document est public, sans authentification : il ne dit rien de sensible et permet de generer un client avant de se connecter. Les donnees, elles, ne sortent qu'avec une session.

La suite de cette page decrit l'usage reel, les champs qui ne vont pas de soi et les pieges.

Base : https://overwatch.guillaume.ink/api/v1

Toutes les reponses portent Cache-Control: no-store. Aucune ne doit etre retenue par un intermediaire : ce sont des donnees de securite, par organisation.

Session

L'authentification se fait par cookie de session (ow_session, HttpOnly, Secure, SameSite=Lax). Les mutations exigent en plus un jeton anti-rejeu dans l'en-tete x-csrf-token.

POST /session avec {"email": "...", "password": "..."} rend l'identite et le jeton anti-rejeu, et pose le cookie.

{
  "id": "…", "email": "admin@exemple.fr",
  "organization_id": "…", "organization_slug": "acme",
  "role": "owner", "is_platform_admin": false,
  "csrf": "2480c3ff7e2a…"
}

GET /session rend la meme chose pour la session courante. DELETE /session ferme la session et efface le cookie, avec le jeton.

Sans session, toute route rend 401 avec {"error": "session requise"}.

Statistiques

GET /stats?range=1h|24h|7d|30d

{
  "range": "24h",
  "requests": 18422, "blocked": 402, "errors_5xx": 3,
  "avg_ms": 11.4, "p95_ms": 87, "agents": 61,
  "series": [{ "label": "13:00", "total": 812, "blocked": 12 }],
  "top_ips": [{ "label": "203.0.113.7", "total": 940, "blocked": 0 }],
  "top_paths": [{ "label": "/api/v1/login", "total": 120, "blocked": 4 }],
  "top_agents": [{ "label": "curl/8.14.1", "total": 61 }],
  "statuses": [{ "code": 200, "total": 17000 }],
  "hosts": [{ "label": "demo.exemple.fr", "total": 900, "blocked": 2 }],
  "flags": [{ "label": "UF", "total": 12 }]
}

series porte 24 tranches, la derniere representant l'instant courant.

Journaux

GET /logs avec, en parametres facultatifs : before (curseur arriere), since (curseur avant), host, ip, path, class (2, 4, 5), blocked, limit (100 par defaut, 500 au plus).

{
  "lines": [{
    "id": 599, "ts": "2026-10-20T14:03:21.318Z",
    "host": "demo.exemple.fr", "method": "GET", "path": "/tarifs",
    "status": 200, "bytes_sent": 4210, "duration_ms": 12,
    "client_ip": "203.0.113.7", "user_agent": "Mozilla/5.0 …",
    "upstream": "10.244.0.31:8080", "response_flags": "-",
    "blocked": false,
    "ja4": "t13d3013h2_1d37bd780c83_8537cf56674e",
    "ja4h": "exh1_chrome_10_e19a2c71"
  }],
  "next_before": 512,
  "has_more": true
}

La pagination se fait par curseur et non par numero de page : une ligne qui arrive pendant la lecture ne decale rien.

GET /logs/stream ouvre un flux text/event-stream. Chaque evenement porte id (l'identifiant de la ligne) et event: log, avec la ligne complete en data. L'en-tete Last-Event-ID rejoue ce qui a manque : un onglet qui revient ne perd rien.

id: 599
event: log
data: {"id":599,"host":"demo.exemple.fr","path":"/tarifs", …}

Cote serveur, une seule connexion a la base veille sur les insertions : le cout ne depend pas du nombre de consoles ouvertes. Une notification accelere la relecture, un delai plafond de 500 ms garantit qu'aucune ligne ne reste en rade si une notification se perd.

Domaines

GET /domains rend les domaines de l'organisation, avec la liste des fonctions actives.

POST /domains declare un domaine a partir d'un preset, qui donne le port, la posture, le cache et les fonctions de depart.

{ "hostname": "www.exemple.fr", "backend_host": "10.0.0.12",
  "backend_port": 8080, "preset": "wordpress" }

Rend 201 avec le domaine cree. 409 si le nom est deja declare dans l'organisation, 400 si le nom d'hote porte un schema, un port ou un chemin.

PATCH /domains/{id} change l'amont ou le preset. Le nom d'hote ne bouge pas : c'est lui qui porte le certificat et les journaux. Un changement de preset remet les fonctions a leur etat de depart ; un changement d'amont seul les conserve, pour qu'une bascule manuelle ne soit pas perdue.

{ "backend_host": "10.0.0.99", "backend_port": 8443 }

DELETE /domains/{id} retire le domaine. Rend 204.

PATCH /domains/{id}/features/{fonction} bascule une fonction et rend son nouvel etat.

{ "id": "…", "feature": "block_tor", "enabled": true }

Organisations

GET /organizations rend toutes les organisations pour un administrateur de plateforme, la sienne sinon. Chaque entree porte le nombre de domaines et de comptes qu'elle contient.

POST /organizations cree une organisation. Reserve a la plateforme.

{ "slug": "acme", "name": "ACME SARL", "hosts": ["acme.fr", "www.acme.fr"] }

L'identifiant est normalise : minuscules, chiffres et tirets, deux a quarante caracteres. Les noms d'hote servent au routage des journaux quand le collecteur presente le jeton de la plateforme.

PATCH /organizations/{id} change le nom et les hotes.

DELETE /organizations/{id} supprime une organisation vide. Rend 409 si des domaines ou des comptes y sont encore rattaches : les tables partent en cascade, et une suppression d'un clic emporterait des journaux d'acces. C'est volontaire, et c'est la seule facon de ne pas perdre de donnees par accident.

Comptes

GET /users rend les comptes de l'organisation.

POST /users cree un compte. Douze caracteres de mot de passe au minimum, role parmi owner, admin, viewer. Un administrateur ne peut pas nommer un proprietaire : c'est un acte de proprietaire, et cela l'empecherait de s'elever lui-meme.

PATCH /users/{id} change le role.

DELETE /users/{id} supprime un compte.

Deux refus valent d'etre connus : on ne supprime pas son propre compte depuis la console (409), et on ne retire pas le dernier proprietaire d'une organisation (409), sans quoi plus personne ne pourrait la gerer.

Catalogue

GET /features rend les sections et les fonctions, avec applied qui distingue une fonction posee au bord d'une fonction seulement enregistree.

GET /presets rend les profils d'onboarding : port, posture, duree de cache, fonctions activees, point a surveiller.

Erreurs

Code Sens
401 session absente ou expiree
403 role insuffisant, ou jeton anti-rejeu absent ou invalide
400 entree invalide, avec le message qui dit quoi corriger
404 objet absent, ou hors de l'organisation de la session
409 conflit : identifiant deja pris, derniere ressource protegee
500 erreur interne, journalisee cote serveur

Le corps est toujours {"error": "…"}. Les messages sont en anglais : ils s'affichent tels quels dans la console, et un message traduit a l'affichage finirait par diverger du journal.

Un piege a connaitre

Le WAF du bord filtre la console elle-meme. La regle CRS 911100 n'autorise par defaut que GET, HEAD, POST et OPTIONS : toute mutation en PATCH ou DELETE etait refusee en 403 avant d'atteindre l'application, avec un corps vide et sans explication. Les methodes completes sont desormais autorisees dans les directives du WAF. Si vous branchez une API REST derriere la plateforme, verifiez ce reglage en premier.

Politique de WAF

Route Methode Effet
/api/v1/waf GET politique de chaque domaine, et son etat d'application
/api/v1/waf/catalog GET familles de regles, niveau de paranoia, seuil
/api/v1/waf/apply POST recalcule et pousse toute la politique
/api/v1/operator/sites GET sites de l'operateur : ce qui est declare et ce qui est pose
/api/v1/operator/sites/{name}/features/{feature} PATCH bascule une fonction du site
/api/v1/operator/caches GET caches partages : memoire, sites montes, taux de succes constate, empreinte de la politique
/api/v1/domains/{id}/waf PUT politique d'un domaine, appliquee dans la foulee
/api/v1/allowlists GET etat des trois listes de trafic legitime
/api/v1/allowlists POST ajoute une plage a une liste
/api/v1/allowlists/{kind} GET plages d'une liste, avec provenance
/api/v1/allowlists/{kind}?cidr= DELETE retire une plage d'une liste
/api/v1/pressure GET pression par site : debit, part hostile, niveau retenu
/api/v1/pressure POST force un niveau pour un site, ou rend la main a la mesure

Le corps d'une mise a jour tient en trois champs : mode (blocking ou detection), disabled (familles retirees) et exclusions (identifiants de regles). L'enregistrement pousse la configuration au bord et rend {"applied":true,"hash":"..."}. Un echec de poussee n'annule pas la politique : il est rendu dans error, la console montre ce qui reste a appliquer.

Jetons de collecte

Route Methode Effet
/api/v1/ingest-tokens GET jetons de l'organisation
/api/v1/ingest-tokens POST creation, le secret n'est rendu qu'une fois
/api/v1/ingest-tokens/{id} DELETE revocation immediate

Journal d'administration

GET /api/v1/audit rend les deux cents dernieres actions de l'organisation : auteur, action, cible, detail. Toute mutation d'administration y laisse une ligne, y compris les changements de politique de WAF et la creation ou la revocation d'un jeton.

Sites de l'operateur

GET /api/v1/operator/sites rend, pour chaque EdgeSite, la specification (spec.domains, spec.posture, spec.security) et ce que l'operateur dit avoir pose (status.features, fonction par fonction, avec la raison de chaque ecart). Un site n'est visible que si l'un de ses domaines appartient a l'organisation de la session.

La bascule ecrit dans spec.security, jamais dans le statut : l'operateur reste le seul a ecrire status.features. Trois fonctions sont pilotables par ce chemin, waf, challenge et sensitiveFiles ; les demander sur une autre nom est refusee avec un message explicite plutot que silencieusement ignore.