Documentation
Le site repose sur une API publique avec dix-huit endpoints instantanés. Chacun est documenté ci-dessous : ce qu'il fait, ses limites et un exemple requête/réponse. Sans compte ; les résultats instantanés ne sont pas stockés. L'APIs de gestion en fin de page (surveillance de disponibilité, pages de statut, liens courts) sont l'exception avec état : elles persistent sous leurs propres clés de gestion.
Codes de statut
- 301 — Redirection permanente
Redirection permanente. Les moteurs de recherche transfèrent les signaux de classement vers la destination.
- 302 — Redirection temporaire
Redirection temporaire. L'URL d'origine reste canonique.
- 307 — Redirection temporaire
Redirection temporaire qui préserve la méthode et le corps de la requête.
- 308 — Redirection permanente
Redirection permanente qui préserve la méthode et le corps de la requête.
- 200 — Succès
Destination finale atteinte. La chaîne s'arrête ici.
- 404 — Erreur client
La destination n'existe pas — une cible de redirection cassée.
- 500 — Erreur serveur
La destination a été résolue mais le serveur n'a pas répondu correctement.
Résultats classifiés, pas des erreurs
Sur les quatre endpoints instantanés, les cibles bloquées, les domaines insolubles et les URL invalides ne sont pas des erreurs : toute requête bien formée renvoie HTTP 200 avec un statut qui classe le résultat. Seules les requêtes malformées, les corps trop volumineux et la limitation de débit produisent des erreurs.
Limites communes
- Protocoles
- http, https
- Méthode
- GET
- Limites de débit
- 30 requêtes/minute et 500/jour par IP — partagées par tous les endpoints
- Corps de la requête
- jusqu'à 10 Kio, JSON strict
Annuaire de services & signalements communautaires (Phase 14)
Un annuaire public et curaté de services en ligne populaires avec signalements communautaires anonymes. Les lectures sont publiques et mises en cache; le POST de signalement est l'exception stateful — il persiste et porte limite de débit, déduplication et modération. Signal communautaire et vérification automatique sont toujours des indicateurs séparés; rien ici n'affirme une panne confirmée.
GET /api/v1/catalog/services?sort=reports_24h&page=1&page_size=20
→ 200
{ "items": [ { "slug": "chatgpt", "name": "ChatGPT",
"category": { "slug": "ai-assistants", "name": "AI Assistants" },
"community": { "signal": "reports_increasing", "reports_15m": 3,
"reports_1h": 11, "reports_24h": 42,
"last_report_at": "2026-10-20T09:58:12Z" },
"automated_check": { "state": "operational",
"checked_at": "2026-10-20T09:55:00Z" } } ],
"page": 1, "page_size": 20, "total": 1 }
POST /api/v1/catalog/services/chatgpt/reports
{ "problem_type": "site_unreachable", "comment": "Login loop on mobile" }
→ 201
{ "service_slug": "chatgpt", "problem_type": "site_unreachable",
"comment": "Login loop on mobile", "created_at": "2026-10-20T10:01:33Z" }
Reads are public and cached (ETag + max-age=30); unknown/draft/hidden
slugs all answer 404 (no existence oracle); merged slugs 301 to the
target. Reports are anonymous: no account, no network fetch on
submission, rate limited per IP, comments plain text (≤500 chars).
Community signal and the automated check are always separate blocks —
no value on this API means "confirmed down".- Pagination
- page ≥1, page_size par défaut 20, max 100 ; ordre déterministe (départage par slug)
- Limites de signalement
- dédup 1 par service toutes les 10 min par IP, puis 10/h et 30/jour par IP ; 429 + Retry-After ; fail-closed
- Modération et confidentialité
- politique auto-publish ou hold-all ; interrupteurs d'arrêt ; seul un hachage pseudonyme à rotation quotidienne est stocké — jamais l'IP brute ni le user-agent
Redirect Checker — POST /api/v1/check
Suit toute la chaîne de redirections et stocke le résultat.
POST /api/v1/check
Content-Type: application/json
{ "url": "https://bit.ly/example" }
{
"id": "9pr2kxq4",
"status": "completed",
"input_url": "https://bit.ly/example",
"final_url": "https://example.com/product",
"final_domain": "example.com",
"final_status": 200,
"redirect_count": 3,
"duration_ms": 482,
"redirects": [
{ "sequence": 0, "requested_url": "https://bit.ly/example",
"status_code": 301, "next_url": "https://tracker.example/click",
"duration_ms": 83 },
{ "sequence": 3, "requested_url": "https://example.com/product",
"status_code": 200, "duration_ms": 184 }
],
"errors": null
}
GET /api/v1/check/{id} — récupère un résultat enregistré par son ID public.- Redirections max
- 20 sauts
- Délai de requête
- 15 secondes
GET /api/v1/check/{id} — récupère un résultat enregistré par son ID public.
Uptime Checker — POST /api/v1/uptime-check
Vérifie si un site répond. Le résultat est classé : UP pour une réponse saine, HTTP_ERROR si le site renvoie 4xx/5xx, plus TIMEOUT, DNS_ERROR, TLS_ERROR, CONNECTION_ERROR et BLOCKED_TARGET.
POST /api/v1/uptime-check
Content-Type: application/json
{ "url": "https://example.com" }
{
"url": "https://example.com",
"status": "UP",
"reachable": true,
"httpStatus": 200,
"httpStatusText": "OK",
"responseTimeMs": 184,
"finalUrl": "https://example.com/",
"redirectCount": 1,
"tls": { "enabled": true, "valid": true }
}- Redirections max
- 10 sauts
- Délai de requête
- 10 secondes
HTTP Status Checker — POST /api/v1/http-status-check
Renvoie le code de statut final d'une URL, les en-têtes de la réponse finale, un résumé compact de la chaîne de redirections et l'état TLS.
POST /api/v1/http-status-check
Content-Type: application/json
{ "url": "https://example.com" }
{
"url": "https://example.com",
"status": "UP",
"reachable": true,
"httpStatus": 200,
"httpStatusText": "OK",
"finalUrl": "https://example.com/",
"redirectCount": 1,
"headers": {
"Content-Type": ["text/html; charset=utf-8"],
"Server": ["nginx"]
},
"headers_analysis": {
"security_missing": ["Strict-Transport-Security", "Content-Security-Policy"],
"caching": { "Cache-Control": "max-age=604800" }
},
"tls": { "enabled": true, "valid": true }
}- Redirections max
- 10 sauts
- Délai de requête
- 10 secondes
Response Time Checker — POST /api/v1/response-time-check
Mesure 1 à 5 échantillons et classe un verdict de vitesse à partir de la médiane du time to first byte. Renvoie la durée de chaque échantillon, min/médiane/max/moyenne et la répartition des temps du dernier saut.
POST /api/v1/response-time-check
Content-Type: application/json
{ "url": "https://example.com", "samples": 3 }
{
"url": "https://example.com",
"samples": 3,
"status": "UP",
"verdict": "FAST",
"completed": 3,
"timesMs": [184, 201, 176],
"stats": { "minMs": 176, "medianMs": 184, "maxMs": 201, "meanMs": 187 },
"breakdown": { "dnsMs": 12, "connectMs": 23, "tlsMs": 45, "ttfbMs": 184, "totalMs": 190 },
"httpStatus": 200,
"httpStatusText": "OK",
"finalUrl": "https://example.com/",
"redirectCount": 1,
"tls": { "enabled": true, "valid": true }
}- Échantillons
- 1–5 (défaut 3)
- Délai par échantillon
- 10 secondes
- Délai global
- 30 secondes
SSL Checker — POST /api/v1/ssl-check
Inspecte le certificat TLS présenté par un hôte : validité, émetteur, SAN, protocole et chiffrement négociés, chaîne. Sonde TLS uniquement — la page n'est jamais chargée ; un certificat défectueux est signalé avec un motif précis.
POST /api/v1/ssl-check
Content-Type: application/json
{ "url": "https://example.com" }
{
"url": "https://example.com",
"host": "example.com",
"status": "UP",
"reachable": true,
"handshakeMs": 45,
"tls": {
"version": "TLS 1.3",
"cipher": "TLS_AES_256_GCM_SHA384",
"verified": true,
"status": "valid",
"daysRemaining": 89,
"notBefore": "2026-08-01T00:00:00Z",
"notAfter": "2026-10-30T00:00:00Z",
"subject": "CN=example.com",
"issuer": "CN=R3,O=Let's Encrypt,C=US",
"sans": ["example.com", "www.example.com"],
"chain": ["CN=example.com", "CN=R3,O=Let's Encrypt,C=US"]
}
}- Port
- celui de l'URL (443 par défaut)
- Délai d'expiration de la sonde
- 10 secondes
DNS Checker — POST /api/v1/dns-check
Recherche les enregistrements A, AAAA, CNAME, MX, TXT et NS d'un hôte. Recherche et rapport uniquement : aucune connexion n'est établie et chaque adresse résolue est étiquetée publique ou non publique.
POST /api/v1/dns-check
Content-Type: application/json
{ "url": "example.com" }
{
"url": "example.com",
"host": "example.com",
"status": "UP",
"durationMs": 38,
"records": {
"a": ["93.184.216.34"],
"aaaa": ["2606:2800:220:1:248:1893:25c8:1946"],
"cname": [],
"mx": [{ "host": "mail.example.com", "pref": 10 }],
"txt": ["v=spf1 include:_spf.example.com ~all"],
"ns": ["ns1.example.com", "ns2.example.com"]
},
"addressLabels": [
{ "address": "93.184.216.34", "classification": "public", "reason": "public" }
]
}- Familles d'enregistrements
- A, AAAA, CNAME, MX, TXT, NS
- Délai de recherche
- 10 secondes au total, 3 secondes par famille
Vérificateur d'expiration de domaine — POST /api/v1/domain-expiry-check
Répond à la question « quand ce domaine expire-t-il ? » via le flux RDAP fixe de rdap.org : verdict, jours restants, dates d'enregistrement/expiration et statuts du registre. Premier outil du dépôt à interroger un tiers fixe : HTTPS seul à chaque saut, pas de proxy, redirections et taille bornés ; le domaine interrogé n'est jamais contacté. 404 devient un verdict not_found (non enregistré / registre sans RDAP / rien publié), 429 et 5xx un honnête UNKNOWN_ERROR.
POST /api/v1/domain-expiry-check
Content-Type: application/json
{ "url": "example.com" }
{
"url": "example.com",
"host": "example.com",
"status": "UP",
"verdict": "ok",
"daysRemaining": 214,
"registration": "2019-01-15T05:00:00Z",
"expiration": "2027-04-15T04:00:00Z",
"lastChanged": "2026-08-01T11:20:00Z",
"registryStatuses": ["ok"],
"source": "rdap",
"durationMs": 420
}- Verdicts
- ok, expiring (≤30 jours), expired, not_found, unknown
- Délai de la requête
- 10 secondes au total (DOMAIN_EXPIRY_TIMEOUT)
- Accès sortant
- rdap.org uniquement ; HTTPS à chaque saut ; proxy désactivé ; limite de 10 redirections ; plafond de 1 Mio ; le domaine n'est jamais contacté
Requête WHOIS — POST /api/v1/whois-check
Répond à la question « que sait le registre d'un domaine » : registrar, serveurs de noms, dates et statuts. RDAP d'abord via rdap.org, avec repli sur le WHOIS classique (TCP 43 brut) pour les zones sans RDAP — le premier outil du dépôt en TCP brut : chaque renvoi doit être une cible host[:43], résolu via le pipeline SSRF partagé et joint à l'adresse publique épinglée ; limite de 2 renvois, plafond de 64 Kio ; le domaine interrogé n'est jamais contacté. Un 404 du RDAP déclenche le repli ; une réponse définitive d'inexistence apparaît comme notFound.
POST /api/v1/whois-check
Content-Type: application/json
{ "url": "mail.ru" }
{
"url": "mail.ru",
"host": "mail.ru",
"status": "UP",
"source": "whois",
"registrar": "VK-TECH",
"nameservers": ["ns1.mail.ru", "ns2.mail.ru"],
"registration": "1997-10-01T00:00:00Z",
"expiration": "2026-10-01T00:00:00Z",
"statuses": ["REGISTERED, DELEGATED, VERIFIED"],
"durationMs": 640
}- Sources
- RDAP via rdap.org ; WHOIS classique (TCP 43) via la chaîne de renvois de whois.iana.org
- Délai de la requête
- 15 secondes au total (WHOIS_TIMEOUT)
- Accès sortant
- renvois host[:43] uniquement ; validés SSRF et épinglés avant connexion ; limite de 2 renvois ; plafond de 64 Kio ; le domaine n'est jamais contacté
Disponibilité de domaine — POST /api/v1/domain-availability
Répond, par TLD, à la question « ce nom est-il libre », avec une machine à états honnête : AVAILABLE uniquement si l'interrogation RDAP répond 404 et que les métadonnées TLD intégrées confirment que le registre exploite RDAP ; un introuvable WHOIS-only reste UNKNOWN (probablement disponible) ; RESTRICTED découle des métadonnées. Prise en charge IDN complète (Unicode en entrée, punycode canonique, displayDomain en sortie) et fan-out multi-TLD optionnel (1–10 par requête). Premium reste toujours unknown en MVP — la tarification est un futur sous-système séparé.
POST /api/v1/domain-availability
Content-Type: application/json
{ "domain": "пример.рф", "tlds": ["com", "io"] }
{
"url": "пример.рф",
"displayDomain": "пример.рф",
"host": "xn--e1afmkfd.xn--p1ai",
"status": "UP",
"results": [
{
"tld": "com",
"availability": "AVAILABLE",
"reason": "rdap_not_found",
"confidence": "high",
"source": "rdap",
"premium": "unknown",
"checkedAt": "2026-09-03T12:00:02Z"
},
{
"tld": "io",
"availability": "UNKNOWN",
"reason": "tld_rdap_unknown",
"confidence": "low",
"source": "tld-metadata",
"premium": "unknown",
"checkedAt": "2026-09-03T12:00:02Z"
}
],
"tldMetaVersion": "2026-09-02",
"durationMs": 1240
}- TLDs par requête
- 1–10 uniques (Unicode ou punycode) ; 4 recherches simultanées au maximum dans un budget de 15 secondes (DOMAIN_AVAILABILITY_TIMEOUT)
- Verdicts
- AVAILABLE | REGISTERED | RESTRICTED | INVALID | UNKNOWN | ERROR — UNKNOWN n'est jamais maquillé en AVAILABLE ; PREMIUM est inatteignable en MVP
- Cache
- di:avail 60s, di:reg 6h, fail-open ; les résultats négatifs ne sont jamais mis en cache
Âge de domaine — POST /api/v1/domain-age
Indique l’âge d’un domaine : la date de création exacte du registre et une répartition déterministe années/mois/jours. Projection légère sur les données WHOIS — une seule requête sert disponibilité, âge et registrar dans la fenêtre de cache.
POST /api/v1/domain-age
Content-Type: application/json
{ "domain": "example.com" }
{
"url": "example.com",
"host": "example.com",
"status": "UP",
"age": {
"known": true,
"createdAt": "2019-01-15T05:00:00Z",
"ageInDays": 2809,
"years": 7,
"months": 7,
"days": 20
},
"source": "rdap",
"durationMs": 390
}- Calcul de l’âge
- dates UTC uniquement ; années/mois pleins via une ancre de mois ajustée ; jours totaux arrondis à l’inférieur
- Âge inconnu
- age.known=false + raison bornée (no_data, inconsistent_data) — jamais une supposition
- Cache
- di:reg 6h partagé avec disponibilité/registrar (fail-open)
Vérificateur de registrar — POST /api/v1/domain-registrar
Indique auprès de quel registrar un domaine est enregistré, normalisé via une table d’alias curatée (GoDaddy, Namecheap, Cloudflare et plus) — les noms inconnus sont montrés exactement tels que publiés. Organisation du registre et provenance RDAP/WHOIS inclus ; partage le cache d’enregistrement avec âge/disponibilité.
POST /api/v1/domain-registrar
Content-Type: application/json
{ "domain": "example.com" }
{
"url": "example.com",
"host": "example.com",
"status": "UP",
"registrar": "GoDaddy.com, LLC",
"registrarCanonical": "GoDaddy",
"registrarIanaId": 625,
"registry": "Verisign, Inc.",
"source": "rdap",
"checkedAt": "2026-09-04T12:00:00Z",
"durationMs": 410
}- Registrar
- nom affiché + badge d’alias canonique en cas de correspondance ; l’ID IANA affiche « — » (les données du registre ne le portent pas encore)
- Non publié
- les enregistrements derrière proxy de confidentialité ou censurés répondent UP avec la raison no_data — jamais une coquille vide
Générateur de domaines — POST /api/v1/domain-generate
Génère des domaines candidats à partir de 1–3 mots-clés : formes directes, préfixes, suffixes, composés et formes courtes — dédupliqués, ordonnés et plafonnés. Pur et instantané : aucun appel au registre pendant la génération ; les vérifications par candidat sont des actions séparées et explicites.
POST /api/v1/domain-generate
Content-Type: application/json
{ "keywords": ["coffee", "map"], "tlds": ["com", "io"], "limit": 24 }
{
"status": "UP",
"candidates": [
{ "domain": "coffee.com", "strategy": "direct" },
{ "domain": "coffee.io", "strategy": "direct" },
{ "domain": "getcoffee.com", "strategy": "prefix" },
{ "domain": "coffeemap.com", "strategy": "compound" }
],
"total": 24
}- Candidats
- plafond strict 50, 24 affichés par défaut ; ≤10 TLDs filtrés par l’instantané de métadonnées TLD
- Aucun trafic registre
- la génération n’appelle aucun registre — les vérifications par candidat passent par l’endpoint de disponibilité avec ses propres limites
Aperçu de domaine — POST /api/v1/domain-overview
Un profil composé : disponibilité sur le TLD du domaine, date de création avec la répartition d’âge déterministe, registrar (normalisé, avec numéro IANA), projection d’expiration et les constats de configuration du registre. Les sections sont indépendantes — une requête échouée ne vide jamais les autres.
POST /api/v1/domain-overview
Content-Type: application/json
{ "domain": "example.com" }
{
"url": "example.com",
"host": "example.com",
"status": "UP",
"availability": {
"tld": "com", "availability": "REGISTERED", "reason": "registered",
"confidence": "high", "source": "rdap", "premium": "unknown",
"checkedAt": "2026-09-05T12:00:00Z"
},
"registration": {
"known": true, "createdAt": "2019-01-15T05:00:00Z",
"ageYears": 7, "ageMonths": 7, "ageDays": 21,
"registrar": "GoDaddy.com, LLC", "registrarCanonical": "GoDaddy",
"registrarIanaId": 625, "registry": "Verisign, Inc.", "source": "rdap",
"checkedAt": "2026-09-05T12:00:01Z"
},
"expiry": {
"host": "example.com", "source": "rdap", "verdict": "ok",
"daysRemaining": 222, "expiration": "2027-04-15T04:00:00Z",
"checkedAt": "2026-09-05T12:00:02Z"
},
"holds": [],
"dnssec": [{ "id": "dnssec-absent", "state": "info", "severity": "info", "detail": "unsigned" }],
"tldMetaVersion": "2026-09-02",
"durationMs": 1620
}- Sections
- disponibilité (avec porte de métadonnées) + enregistrement (di:reg, 6h partagé) + expiration (rdap.org) + constats holds/dnssec
- Les constats sont des faits
- holds/DNSSEC sont des signaux de configuration du registre — jamais des verdicts de sécurité ; DNS/SSL restent dans leurs outils autonomes
Vérificateur de sécurité — POST /api/v1/security-check
Revise passive et consultative de la configuration d'une réponse obtenue : en-têtes de sécurité (profondeur HSTS, points saillants CSP, COOP/COEP/CORP, X-XSS-Protection), flags de cookies, faits TLS, dégradations de chaîne et divulgation. Aucun sondage, aucune affirmation de vulnérabilité, aucune note ; les valeurs de cookies ne quittent jamais le backend. Le statut HTTP ne conditionne pas la revue (un 404/500 joignable est examiné).
POST /api/v1/security-check
Content-Type: application/json
{ "url": "https://example.com" }
{
"url": "https://example.com",
"finalUrl": "https://example.com/",
"status": "UP",
"durationMs": 610,
"summary": { "total": 14, "passed": 9, "warn": 3, "failed": 2 },
"findings": [
{ "id": "hsts-present", "state": "fail", "severity": "high" },
{ "id": "tls-cert", "state": "pass", "severity": "info", "detail": "valid" }
],
"cookies": [
{ "name": "session", "secure": false, "httpOnly": true, "sameSite": "Lax" }
],
"tls": { "enabled": true, "version": "TLS 1.3", "certStatus": "valid", "daysRemaining": 89 }
}- Périmètre
- ~23 IDs de vérification sur en-têtes/cookies/TLS/chaîne/divulgation ; requête passive uniquement
- Cookies
- Noms et flags uniquement (Secure/HttpOnly/SameSite) — les valeurs ne quittent jamais le backend
- Délai de la revue
- 15 secondes au total (SECURITY_CHECK_TIMEOUT) — 10 s par défaut
URL Analyzer — POST /api/v1/url-analyze
Analyse syntaxique pure d'une URL : identité canonique rédigée (schéma, hôte, formes IDN, domaine registrable naïf, port, chemin, requête, fragment), paramètres ordonnés avec doublons et classification de suivi versionnée, comptages de suivi, signaux d'information et notes de normalisation. Aucune requête, aucun DNS, aucune persistance — l'analyse est locale et prend des millisecondes.
POST /api/v1/url-analyze
Content-Type: application/json
{ "url": "https://xn--e1afmkfd.xn--p1ai/search?q=thing&utm_source=news&id=7&id=8" }
{
"status": "UP",
"identity": {
"hasCredentials": false,
"scheme": "https",
"host": "xn--e1afmkfd.xn--p1ai",
"displayHost": "пример.рф",
"registrableDomain": "пример.рф",
"isIdn": true,
"path": "/search",
"query": "q=thing&utm_source=news&id=7&id=8",
"normalized": "https://xn--e1afmkfd.xn--p1ai/search?q=thing&utm_source=news&id=7&id=8"
},
"parameters": [
{ "name": "q", "value": "thing", "classification": { "category": "unknown", "confidence": "none" } },
{ "name": "utm_source", "value": "news", "classification": { "category": "utm", "label": "utm_source", "confidence": "exact" } },
{ "name": "id", "value": "7", "occurrence": 1 },
{ "name": "id", "value": "8", "occurrence": 2 }
],
"tracking": { "utm": 1, "analytics": 0, "adsClickId": 0, "sessionSensitive": 0, "unknown": 3 },
"signals": ["idn_host"],
"normalization": { "schemeAssumed": false, "punycodeApplied": true, "defaultPortOmitted": false, "hostLowercased": false },
"registryVersion": "2026-09-1"
}- Portée
- Composants + paramètres ordonnés + classification du registre + signaux informatifs ; zéro réseau
- Rédaction
- Les mots de passe ne sont jamais renvoyés ; les valeurs des paramètres sensibles sont masquées (***) partout, les noms restent visibles
URL Expander — POST /api/v1/url-expand
Expansion instantanée et sans persistance d'une chaîne de redirections via le moteur partagé : chaque saut passe la même validation SSRF centrale, les boucles et la limite de 10 redirections sont distinguées par reason, et l'URL finale reçoit une analyse de suivi complète. Les URLs renvoyées sont rédigées — les mots de passe ne quittent jamais le backend. Sans cache : les chaînes sont sensibles au temps, la fraîcheur est la bonne réponse.
POST /api/v1/url-expand
Content-Type: application/json
{ "url": "https://bit.ly/3xyz" }
{
"url": "https://bit.ly/3xyz",
"status": "UP",
"finalUrl": "https://example.com/landing?utm_source=n",
"hopCount": 2,
"chain": [
{ "url": "https://bit.ly/3xyz", "status": 301, "location": "https://bit.ly/abc" },
{ "url": "https://bit.ly/abc", "status": 302, "location": "https://example.com/landing?utm_source=n" }
],
"analysis": { "status": "UP", "tracking": { "utm": 1, "analytics": 0, "adsClickId": 0, "sessionSensitive": 0, "unknown": 0 } },
"source": "engine",
"durationMs": 820
}- Portée
- Chaîne de sauts rédigée + statut classifié (boucle vs limite) + analyse de l'URL finale ; pas de DNS/TLS/chronométrage par saut (domaine du Redirect Checker)
- Budget d'expansion
- 10 secondes au total (URL_EXPAND_TIMEOUT) ; limite de 10 sauts
UTM Builder — POST /api/v1/utm-build
Construction déterministe et sans état d'URLs de campagne : les paramètres non liés sont conservés dans l'ordre, les UTM canoniques sont écrasés ou gardés par champ, les doublons se réduisent au dernier, le fragment est préservé, et les destinations avec identifiants sont refusées in-band. Une même entrée produit toujours une sortie identique à l'octet près.
POST /api/v1/utm-build
Content-Type: application/json
{ "url": "https://example.com/landing?foo=1&utm_source=old#pricing", "utmSource": "newsletter", "utmMedium": "email", "utmCampaign": "spring_sale", "utmContent": "header_cta" }
{
"status": "UP",
"generatedUrl": "https://example.com/landing?foo=1&utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale&utm_content=header_cta#pricing",
"warnings": ["existing_utm_overwritten:utm_source", "empty_field_skipped:utm_term"],
"parameters": { "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "spring_sale", "utm_content": "header_cta" }
}- Portée
- Ajout d'UTM dans un ordre fixe + politique preserve/overwrite/collapse + avertissements ; pur, sans réseau
- Déterminisme
- Sortie identique à l'octet près pour une entrée identique ; champs ≤256 caractères ; champs inconnus rejetés
Website Health — POST /api/v1/website-health
Le diagnostic composé : une requête normalise la cible et lance des lanes concurrentes bornées au-dessus des moteurs existants — un seul contrôle HTTP alimente cinq sections (disponibilité, statut, temps de réponse, en-têtes, configuration de sécurité) ; DNS, la sonde de certificat TLS et le profil de domaine s'exécutent comme des lanes distinctes. Le verdict est un état global agrégé avec le détail par contrôle et des liens vers les outils autonomes. Les constats décrivent uniquement les contrôles effectués — jamais une garantie de sécurité.
POST /api/v1/website-health
Content-Type: application/json
{ "url": "https://example.com" }
{
"target": {
"canonical": "https://example.com/",
"displayHost": "example.com",
"scheme": "https",
"hasCredentials": false,
"isDomain": true,
"isHttp": true,
"valid": true
},
"overall": "WARNING",
"summaryKey": "health.summary.warning",
"checks": [
{ "type": "http_availability", "state": "pass",
"summaryKey": "health.check.httpAvailability.pass",
"value": "200", "durationMs": 612, "applicable": true,
"detailUrl": "/uptime-checker" },
{ "type": "http_status", "state": "pass",
"summaryKey": "health.check.httpStatus.pass",
"value": "200 OK", "applicable": true,
"detailUrl": "/http-status-checker" },
{ "type": "response_time", "state": "pass",
"summaryKey": "health.check.responseTime.pass",
"value": "612 ms", "applicable": true,
"detailUrl": "/response-time-checker" },
{ "type": "headers", "state": "warn",
"summaryKey": "health.check.headers.warn",
"value": "2 missing", "reason": "findings", "applicable": true,
"detailUrl": "/security-checker" },
{ "type": "security", "state": "warn",
"summaryKey": "health.check.security.warn",
"value": "14 of 21 passed", "reason": "findings", "applicable": true,
"detailUrl": "/security-checker" },
{ "type": "tls", "state": "pass",
"summaryKey": "health.check.tls.pass",
"value": "TLS 1.3 · valid, 89 days", "durationMs": 140,
"applicable": true, "detailUrl": "/ssl-checker" },
{ "type": "dns", "state": "pass",
"summaryKey": "health.check.dns.pass",
"value": "A, AAAA, MX, NS", "durationMs": 95,
"applicable": true, "detailUrl": "/dns-checker" },
{ "type": "domain", "state": "pass",
"summaryKey": "health.check.domain.pass",
"value": "expires in 214 days", "durationMs": 730,
"applicable": true, "detailUrl": "/domain-overview" }
],
"meta": { "checksTotal": 8, "checksPassed": 6, "checksWarn": 2,
"checksFailed": 0, "checksUnknown": 0,
"tldMetaVersion": "2026-09-03", "durationMs": 1480 }
}- Portée
- Une exécution HTTP alimente cinq sections ; lanes DNS/TLS/domaine ; agrégat HEALTHY/WARNING/ERROR/PARTIAL/UNKNOWN
- Limites
- Budget global de 25 s + budgets par lane, ≤3 lanes simultanées ; cibles invalides in-band ; pas de cache — fraîcheur d'abord
Uptime Monitoring — l'API de gestion /api/v1/monitors
L'exception avec état par rapport aux outils instantanés : créez un moniteur pour une URL et le worker le vérifie selon un calendrier fixe — avec historique d'exécutions, statistiques de disponibilité, épisodes d'incidents et notifications webhook. La propriété est une clé de gestion affichée exactement une fois à la création ; chaque lecture et écriture s'authentifie avec elle, et une clé erronée est indiscernable d'un moniteur absent (404 uniforme).
POST /api/v1/monitors
Content-Type: application/json
{ "url": "https://example.com", "name": "Prod site", "interval": 300 }
201 Created
{
"monitor": { "id": "b1…", "name": "Prod site",
"normalizedTarget": "https://example.com/",
"state": "unknown", "interval": 300 },
"manageUrl": "/monitors/b1…?key=…",
"key": "…shown exactly once…"
}
GET /api/v1/monitors/{id}?key=… detail + stats window + incidents
GET /api/v1/monitors/{id}/history cursor-paginated executions
GET /api/v1/monitors/{id}/history/daily per-day buckets (30-day cap)
GET /api/v1/monitors/{id}/incidents incident episodes
POST /api/v1/monitors/{id}/check enqueue a priority manual check
PATCH /api/v1/monitors/{id} name / interval / webhook / pause
DELETE /api/v1/monitors/{id} cascade delete
Credentials in the target are rejected (credentials_not_allowed); the
target is SSRF-pre-validated at creation and re-validated on every run.- Périmètre
- Création / liste / détail / pause / suppression / vérification manuelle / historique / incidents ; intervalles fixes 60–3600s ; INVALID_URL et BLOCKED_TARGET in-band à la création
- Gestion des clés
- Clé 256 bits affichée une seule fois (sha256 stocké) ; les identifiants dans les cibles sont refusés ; SSRF prévalidé à la création et revalidé à chaque exécution
Pages de statut publiques — l'API de gestion /api/v1/status-pages
Composez vos moniteurs en une page de statut partageable : choisissez les composants, ordonnez-les, ajustez les noms affichés et publiez sous un slug stable. Les pages vivent sous la même clé de gestion que les moniteurs ; les brouillons sont privés, la publication est idempotente et réversible, et chaque mutation supprime l'entrée du cache public — les changements d'accès s'appliquent dans la requête même.
POST /api/v1/status-pages
Content-Type: application/json
X-Monitor-Key: <your key>
{ "name": "Prod API" }
201 Created
{
"page": { "publicId": "p1…", "name": "Prod API",
"slug": "prod-api", "published": false },
"manageUrl": "/status-pages/p1…?key=…"
}
GET /api/v1/status-pages/{id}?key=… editor payload (members + monitors)
PATCH /api/v1/status-pages/{id} name/description/slug + membership
POST /api/v1/status-pages/{id}/publish → live at /status/{slug}
POST /api/v1/status-pages/{id}/unpublish → removed within the request
DELETE /api/v1/status-pages/{id} cascade delete
GET /api/v1/public/status-pages/{slug} the public DTO (no key; cached 60s)
Membership is same-key only (≤25 monitors, replace-list semantics);
publish requires ≥1 visible member; the public DTO is an explicit
projection — target URLs, keys and internal ids never cross.- Périmètre
- Créer / lister / modifier / liste de membres de remplacement (≤25, même clé uniquement) / publier / dépublier / supprimer ; slugs de 3 à 63 caractères avec liste de mots réservés ; 5 pages max par clé
- Surface publique
- GET /api/v1/public/status-pages/{slug} + /status/{slug} ; DTO publique explicite uniquement — URLs cibles, clés, hôtes de notification et ids internes ne traversent jamais la frontière ; cache Redis 60s, fail-open
Raccourcisseur d'URL — l'API de gestion /api/v1/short-links
Transformez une longue URL en lien court /go/{code} que vous pouvez gérer : destination validée, code aléatoire cryptographique, statistiques de clics avec fenêtres aujourd'hui/7 jours/30 jours. Les liens vivent sous la même clé de gestion que vos moniteurs et pages de statut — la création s'attache à une clé existante et n'en génère jamais de nouvelle. Les destinations sont validées à la création et à la modification (syntaxe, identifiants refusés, classification SSRF) ; la redirection publique ne récupère jamais rien et répond 302 sans mise en cache.
POST /api/v1/short-links
Content-Type: application/json
X-Monitor-Key: <your key>
{ "destinationUrl": "https://example.com/launch?utm_source=newsletter" }
201 Created
{
"link": { "id": "sl1…", "code": "a3f9k2mz",
"shortUrl": "/go/a3f9k2mz",
"destination": "https://example.com/launch?utm_source=newsletter" }
}
GET /api/v1/short-links?key=… your links (newest first)
GET /api/v1/short-links/{id}?key=… full link + stats
PATCH /api/v1/short-links/{id}?key=… destination/title/expiry (re-validated)
POST /api/v1/short-links/{id}/disable public 404, stats kept (enable restores)
DELETE /api/v1/short-links/{id}?key=… soft delete (idempotent)
GET /api/v1/short-links/{id}/stats?key=… total + today/7d/30d clicks
GET /go/a3f9k2mz → 302 Found + Location (Cache-Control: no-store);
unknown/disabled/expired/deleted all answer 404.
Destinations are validated at create/update (http/https, credentials
rejected, SSRF classification); the redirect itself never fetches.- Portée
- Créer / lister / modifier / désactiver / activer / suppression logique / statistiques ; ≤100 liens actifs par clé, 30 créations par clé et par heure ; la destination est revalidée à chaque changement
- Redirection publique
- GET /go/{code} → 302 + no-store ; inconnu/désactivé/expiré/supprimé répondent le même 404 neutre ; la redirection est une pure recherche — elle n'accepte jamais de paramètre de destination et ne résout ni ne récupère la cible ; aucune IP, aucun user-agent stocké
Signaler un lien court — l'API de signalement anonyme
Si un lien /go/{code} ressemble à du phishing, un malware, du spam ou une arnaque, signalez-le — aucun compte requis, vous ne recevrez pas de réponse. Les signalements arrivent dans la file de révision de l'opérateur ; seul l'opérateur peut retirer un lien. Le point de terminaison répond identiquement à tout signalement bien formé : le signalant n'apprend rien sur l'existence du code.
POST /api/v1/short-link-reports
Content-Type: application/json
{ "code": "a3f9k2mz", "reason": "phishing",
"detail": "Imitates a login page" }
202 Accepted
{ "status": "received" }
Reasons: phishing | malware | spam | scam | other (detail ≤500 chars,
optional plain text). The SAME 202 comes back for unknown, disabled
and deleted codes — no existence oracle. Limits per IP: one report
per code per 10 min, 10/hour, 30/day (429 + Retry-After; Redis
failure = 503, fail-closed). Reports go to the operator queue; only
the operator can take a link down (the owner key cannot override).- Limites et confidentialité
- 1 signalement par code et 10 min par IP, puis 10/heure et 30/jour (429 + Retry-After ; en cas d'erreur Redis, 503 fail-closed) ; motifs phishing | malware | spam | scam | other ; sans compte, sans e-mail, IP brute jamais stockée — seulement une empreinte rotative quotidienne
- Réponse uniforme
- tout signalement bien formé reçoit le même 202 received — pour les codes vivants, désactivés, supprimés ou inconnus (pas d'oracle d'existence) ; un code malformé ou un motif inconnu répond 400 avant toute recherche
Comptes & clés API — /api/v1/auth/register et /api/v1/api-keys
Comptes navigateur (e-mail + mot de passe, inscription ouverte ; l'e-mail n'est pas vérifié dans cette version) avec des sessions serveur dans un cookie durci. Les clés API authentifient les appels serveur à serveur sur une liste explicite des familles de ressources. Les échecs de connexion sont génériques — e-mail inconnu et mot de passe erroné sont indiscernables, y compris par le temps de réponse.
POST /api/v1/auth/register
Content-Type: application/json
{ "email": "you@example.com", "password": "…", "displayName": "…" }
201 Created + Set-Cookie: wg_session (HttpOnly; Secure; SameSite=Lax)
{ "account": { "id": "acc…", "email": "you@example.com", … } }
POST /api/v1/auth/login 200 + Set-Cookie (401 generic on failure)
POST /api/v1/auth/logout deletes the server-side session
GET /api/v1/account the session's own profile
PATCH /api/v1/account update display name (displayName)
GET /api/v1/account/summary account-owned resource counts
POST /api/v1/account/claim management key → re-own key-held resources
POST /api/v1/api-keys (SESSION) create; secret shown ONCE
{ "name": "CI", "scopes": ["monitors:read"] }
GET /api/v1/api-keys (SESSION) list — no secrets
POST /api/v1/api-keys/{id}/revoke (SESSION) immediate 401 on next use
Bearer access (Authorization: Bearer wg_live_…) is an EXPLICIT
allowlist only: account GET/PATCH, monitors CRUD + reads, status-pages
GET, short-links CRUD + stats; everything else refuses API keys.
Missing/unknown/revoked → generic 401; wrong scope → 403; per-key and
per-account rate limits → 429 + Retry-After.- Liste Bearer
- account GET/PATCH (account:read), moniteurs (lecture/écriture), pages de statut GET (lecture), liens courts (lecture/écriture) ; tout le reste refuse les clés API. Les ressources détenues par une clé restent invisibles tant qu'elles ne sont pas rattachées via claim-by-key.
- Traitement du secret
- La clé wg_live_ complète n'est affichée qu'une seule fois à la création ; seul un hachage à sens unique est stocké. Clés absentes/inconnues/révoquées : un 401 générique unique ; portée manquante : 403 ; limites : 429 + Retry-After.
Identité e-mail du compte (confirmation et récupération)
Socle e-mail de la Phase J : confirmez votre adresse, récupérez un mot de passe perdu et prouvez une connaissance récente du mot de passe pour les actions sensibles.
POST /api/v1/auth/verification/request (SESSION) 202 {accepted:true}
POST /api/v1/auth/verification/confirm { "token": "…" } → 200 {verified:true}
POST /api/v1/auth/password-reset/request { "email": "you@example.com" } → 202 (always the same answer)
POST /api/v1/auth/password-reset/confirm { "token": "…", "password": "…" } → 200 {reset:true}
POST /api/v1/auth/reauthenticate (SESSION) { "password": "…" } → 200 {confirmed:true}
Reset revokes ALL sessions in the same transaction; API keys stay
active. Tokens ride mail links as URL fragments — never query strings —
and every mismatch (wrong account, replay, expiry, unknown) answers the
same generic 404.- Jetons
- à usage unique, 24 h (confirmation) / 30 min (réinitialisation), fragment uniquement, 404 générique à tout écart
- Réinitialisation
- ferme toutes les sessions de manière atomique ; les clés API restent valides
- Re-authentification
- 5 minutes, côté serveur, liée à la session
- Espaces de travail
- GET /api/v1/workspaces — personnel + organisations, no-store, session uniquement
- Organisations
- création/liste/lecture/renommage/suppression + transfert de propriété ; sessions vérifiées, versions optimistes, reauth pour les actions destructrices
- Invitations
- invitations par e-mail avec liens à usage unique de 7 jours, quatre rôles fixes, plafonds avec réservation de places et fenêtres de taux obligatoires
- Ressources d'organisation
- moniteurs/pages/liens partagés sous /organizations/{orgId} ; RBAC de session, mutations versionnées, clés legacy exclues au niveau SQL
- Transfert de ressources
- déplacer ses moniteurs/pages/liens personnels vers une organisation de manière atomique (fermeture ≤100, versions vérifiées, idempotent)
- Clés d'organisation
- clés de service (wg_live_…), affichées une seule fois, à périmètre limité, survivent au départ du créateur, révocation immédiate
- Audit d'organisation
- historique en ajout seul des changements validés (365 jours), lecture Owner/Admin, divulgation minimale des acteurs