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.
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.
DELETE /domains/{id} retire le domaine. Rend 204.
PATCH /domains/{id}/features/{fonction} bascule une fonction et rend son
nouvel etat.
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.
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.