Documentation API

Intégrez SiteRadar dans vos applications avec notre API REST complète.

Version 1.0 REST API JSON

Introduction

L'API SiteRadar permet d'automatiser la gestion de vos sites web et de leurs audits SEO. Elle est disponible pour les abonnés PRO.

Base URL de l'API :

https://siteradar.cloud/api/v1

Toutes les requêtes doivent utiliser HTTPS. Les requêtes HTTP seront rejetées.

Authentification

L'API utilise des clés API pour l'authentification. Vous pouvez créer et gérer vos clés dans les paramètres API.

Incluez votre clé API dans le header X-API-Key de chaque requête :

curl -X GET "https://siteradar.cloud/api/v1/sites" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"
$ch = curl_init('https://siteradar.cloud/api/v1/sites');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx',
        'Content-Type: application/json'
    ]
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
const response = await fetch('https://siteradar.cloud/api/v1/sites', {
  headers: {
    'X-API-Key': 'sr_live_xxxxxxxxxxxxxxxxxxxx'
  }
});
const data = await response.json();
import requests

response = requests.get(
    'https://siteradar.cloud/api/v1/sites',
    headers={'X-API-Key': 'sr_live_xxxxxxxxxxxxxxxxxxxx'}
)
data = response.json()

Codes d'erreur

L'API utilise les codes HTTP standard pour indiquer le succès ou l'échec d'une requête.

Code Signification
200Succès
201Ressource créée
400Requête invalide (paramètres manquants ou incorrects)
401Non authentifié (clé API manquante ou invalide)
403Accès refusé (quota dépassé ou ressource non autorisée)
404Ressource non trouvée
429Trop de requêtes (rate limiting)
500Erreur serveur

Format des erreurs

{
  "success": false,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "Le paramètre 'url' est requis"
  }
}

Limites de requêtes

Les limites de requêtes sont incluses dans les headers de chaque réponse :

Header Description
X-RateLimit-LimitNombre maximum de requêtes par heure
X-RateLimit-RemainingRequêtes restantes
X-RateLimit-ResetTimestamp de réinitialisation

Compte

Informations sur votre compte et votre utilisation

GET /account/me

Récupère les informations de votre compte.

Réponse

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Inc",
    "preferredLocale": "fr",
    "emailVerified": true,
    "createdAt": "2024-01-15T10:30:00+00:00"
  }
}
GET /account/usage

Récupère les statistiques d'utilisation et les quotas de votre compte.

Réponse

{
  "success": true,
  "data": {
    "plan": "pro",
    "usage": {
      "websites": {
        "used": 12,
        "limit": 50,
        "remaining": 38
      },
      "scansThisMonth": {
        "used": 45,
        "limit": -1,
        "remaining": -1
      },
      "apiRequests": {
        "total": 1234
      }
    },
    "features": {
      "api": true,
      "whiteLabel": true,
      "webhooks": true,
      "scheduledScans": true,
      "teamSharing": true
    },
    "subscription": {
      "status": "active",
      "currentPeriodEnd": "2024-02-15T00:00:00+00:00"
    }
  }
}

Sites

Gérez vos sites web

GET /sites

Liste tous vos sites.

Exemple de requête

curl -X GET "https://siteradar.cloud/api/v1/sites" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"

Réponse

{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "url": "https://example.com",
      "domain": "example.com",
      "displayName": "Mon Site",
      "verified": true,
      "createdAt": "2024-01-10T08:00:00+00:00",
      "lastScanAt": "2024-01-20T14:30:00+00:00",
      "lastScore": 85
    }
  ],
  "meta": {
    "total": 1
  }
}
POST /sites

Ajoute un nouveau site à surveiller.

Paramètres

Paramètre Type Requis Description
urlstringOuiURL complète du site (https://)
namestringNonNom d'affichage du site

Exemple de requête

curl -X POST "https://siteradar.cloud/api/v1/sites" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "name": "Mon Nouveau Site"
  }'

Réponse

{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "url": "https://example.com",
    "domain": "example.com",
    "displayName": "Mon Nouveau Site",
    "verified": false,
    "verificationToken": "siteradar-verify-abc123xyz",
    "createdAt": "2024-01-20T15:00:00+00:00"
  }
}
GET /sites/{id}/dns-record

Récupère l'enregistrement DNS TXT à ajouter pour vérifier le site.

Réponse

{
  "success": true,
  "data": {
    "type": "TXT",
    "host": "_siteradar",
    "value": "siteradar-verify-abc123xyz",
    "fullRecord": "_siteradar.example.com TXT siteradar-verify-abc123xyz"
  }
}
POST /sites/{id}/verify

Lance la vérification de propriété du site.

Paramètres

Paramètre Type Description
methodstringdns ou file (défaut: dns)

Réponse (succès)

{
  "success": true,
  "data": {
    "verified": true,
    "message": "Site vérifié avec succès"
  }
}

Réponse (échec)

{
  "success": false,
  "error": {
    "code": "VERIFICATION_FAILED",
    "message": "Enregistrement DNS non trouvé"
  }
}
DELETE /sites/{id}

Supprime un site et toutes ses données.

Réponse

{
  "success": true,
  "data": {
    "message": "Site supprimé"
  }
}

Scans

Lancez et consultez les audits de vos sites

POST /sites/{id}/scans

Lance un nouveau scan sur le site. Le site doit être vérifié.

Exemple de requête

curl -X POST "https://siteradar.cloud/api/v1/sites/550e8400.../scans" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"

Réponse

{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "status": "running",
    "startedAt": "2024-01-20T15:30:00+00:00",
    "pagesCrawled": 0,
    "issuesCount": 0
  }
}

Utilisez les webhooks pour être notifié quand le scan est terminé, plutôt que de faire du polling.

GET /scans/{id}

Récupère les résultats complets d'un scan.

Réponse

{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "website": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "url": "https://example.com",
      "displayName": "Mon Site"
    },
    "status": "completed",
    "trigger": "api",
    "startedAt": "2024-01-20T15:30:00+00:00",
    "completedAt": "2024-01-20T15:35:00+00:00",
    "duration": 300,
    "pagesCrawled": 42,
    "issuesCount": 15,
    "score": {
      "global": 78,
      "seo": 82,
      "performance": 71,
      "accessibility": 85,
      "security": 90,
      "mobile": 68
    },
    "summary": {
      "critical": 2,
      "important": 5,
      "moderate": 6,
      "minor": 2
    }
  }
}
GET /scans/{id}/issues

Liste tous les problèmes détectés lors du scan.

Paramètres de requête (optionnels)

Paramètre Type Description
categorystringFiltrer par catégorie (seo, performance, accessibility, security, mobile)
severitystringFiltrer par sévérité (critical, important, moderate, minor)
pageintNuméro de page (défaut: 1)
limitintNombre de résultats par page (défaut: 50, max: 100)

Exemple de requête

curl -X GET "https://siteradar.cloud/api/v1/scans/770e.../issues?category=seo&severity=critical" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"

Réponse

{
  "success": true,
  "data": [
    {
      "id": "880e8400-e29b-41d4-a716-446655440000",
      "category": "seo",
      "severity": "critical",
      "issueKey": "missing_title",
      "message": "La balise title est manquante",
      "url": "https://example.com/page",
      "context": {
        "recommendation": "Ajoutez une balise  unique et descriptive"
      }
    }
  ],
  "meta": {
    "total": 15,
    "page": 1,
    "limit": 50,
    "pages": 1
  }
}</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/scans/{id}/pages</code>
                    </div>
                    <p class="text-gray-600 mb-4">Liste toutes les pages crawlées lors du scan.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "url": "https://example.com/",
      "title": "Accueil - Mon Site",
      "statusCode": 200,
      "responseTimeMs": 245,
      "contentSizeBytes": 52480,
      "depth": 0,
      "issuesCount": 3
    }
  ],
  "meta": {
    "total": 42,
    "page": 1,
    "limit": 50,
    "pages": 1
  }
}</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/scans/{id}/pages/{pageId}</code>
                    </div>
                    <p class="text-gray-600 mb-4">Récupère les détails complets d'une page crawlée.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "url": "https://example.com/",
    "title": "Accueil - Mon Site",
    "metaDescription": "Description de mon site...",
    "statusCode": 200,
    "responseTimeMs": 245,
    "contentSizeBytes": 52480,
    "contentType": "text/html",
    "depth": 0,
    "headings": {
      "h1": ["Mon Site"],
      "h2": ["Services", "Contact"],
      "h3": []
    },
    "images": [
      {"src": "/images/logo.png", "alt": "Logo", "width": 200, "height": 50}
    ],
    "links": {
      "internal": 15,
      "external": 3
    },
    "issues": [
      {
        "category": "seo",
        "severity": "moderate",
        "issueKey": "meta_description_too_short",
        "message": "La méta description est trop courte"
      }
    ]
  }
}</code></pre>
                    </div>
                </div>
            </section>

                        <section id="exports">
                <div class="border-b border-gray-200 pb-4 mb-8">
                    <h2 class="text-2xl font-bold text-gray-900">Exports</h2>
                    <p class="text-gray-600 mt-2">Exportez les résultats de vos scans</p>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/scans/{id}/export/pdf</code>
                    </div>
                    <p class="text-gray-600 mb-4">Génère un rapport PDF du scan.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Paramètres optionnels</h4>
                    <div class="overflow-hidden border border-gray-200 rounded-lg mb-4">
                        <table class="min-w-full divide-y divide-gray-200">
                            <thead class="bg-gray-50">
                                <tr>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Paramètre</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-gray-200">
                                <tr><td class="px-4 py-3"><code>whiteLabel</code></td><td class="px-4 py-3 text-sm">boolean</td><td class="px-4 py-3 text-sm text-gray-600">Utiliser les paramètres marque blanche (si configurés)</td></tr>
                            </tbody>
                        </table>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X GET "https://siteradar.cloud/api/v1/scans/770e.../export/pdf?whiteLabel=true" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx" \
  -o rapport.pdf</code></pre>
                    </div>

                    <div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
                        <p class="text-sm text-blue-800">
                            <strong>Content-Type:</strong> application/pdf<br>
                            La réponse est directement le fichier PDF binaire.
                        </p>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/scans/{id}/export/csv</code>
                    </div>
                    <p class="text-gray-600 mb-4">Exporte les données du scan au format CSV.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Paramètres optionnels</h4>
                    <div class="overflow-hidden border border-gray-200 rounded-lg mb-4">
                        <table class="min-w-full divide-y divide-gray-200">
                            <thead class="bg-gray-50">
                                <tr>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Paramètre</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-gray-200">
                                <tr><td class="px-4 py-3"><code>type</code></td><td class="px-4 py-3 text-sm">string</td><td class="px-4 py-3 text-sm text-gray-600"><code>detailed</code> (défaut) : Export complet avec pages | <code>issues</code> : Seulement les problèmes</td></tr>
                            </tbody>
                        </table>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X GET "https://siteradar.cloud/api/v1/scans/770e.../export/csv?type=issues" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx" \
  -o issues.csv</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Format du CSV (type=issues)</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>URL,Catégorie,Sévérité,Problème,Message
https://example.com/,seo,critical,missing_title,La balise title est manquante
https://example.com/contact,performance,moderate,large_image,Image trop volumineuse</code></pre>
                    </div>
                </div>
            </section>

                        <section id="webhooks">
                <div class="border-b border-gray-200 pb-4 mb-8">
                    <h2 class="text-2xl font-bold text-gray-900">Webhooks</h2>
                    <p class="text-gray-600 mt-2">Gérez vos webhooks et recevez des notifications en temps réel</p>
                </div>

                <div class="prose prose-gray max-w-none mb-8">
                    <p>Les webhooks vous permettent de recevoir des notifications automatiques quand certains événements se produisent. Vous pouvez les gérer via l'API ou dans les <a href="/settings/api" class="text-primary-600 hover:text-primary-700">paramètres API</a>.</p>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/webhooks</code>
                    </div>
                    <p class="text-gray-600 mb-4">Liste tous vos webhooks configurés.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X GET "https://siteradar.cloud/api/v1/webhooks" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": [
    {
      "id": "aa0e8400-e29b-41d4-a716-446655440000",
      "name": "Mon Webhook CI/CD",
      "url": "https://my-app.com/webhooks/siteradar",
      "events": ["scan.completed", "scan.failed"],
      "active": true,
      "createdAt": "2024-01-15T10:00:00+00:00",
      "lastTriggeredAt": "2024-01-20T14:30:00+00:00",
      "successCount": 42,
      "failureCount": 1,
      "lastError": null,
      "secretPrefix": "whsec_xxxx..."
    }
  ]
}</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-blue-100 text-blue-800 rounded">POST</span>
                        <code class="text-lg font-mono">/webhooks</code>
                    </div>
                    <p class="text-gray-600 mb-4">Crée un nouveau webhook.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Paramètres</h4>
                    <div class="overflow-hidden border border-gray-200 rounded-lg mb-4">
                        <table class="min-w-full divide-y divide-gray-200">
                            <thead class="bg-gray-50">
                                <tr>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Paramètre</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Requis</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-gray-200">
                                <tr><td class="px-4 py-3"><code>name</code></td><td class="px-4 py-3 text-sm">string</td><td class="px-4 py-3"><span class="text-red-600">Oui</span></td><td class="px-4 py-3 text-sm text-gray-600">Nom du webhook</td></tr>
                                <tr><td class="px-4 py-3"><code>url</code></td><td class="px-4 py-3 text-sm">string</td><td class="px-4 py-3"><span class="text-red-600">Oui</span></td><td class="px-4 py-3 text-sm text-gray-600">URL HTTPS du endpoint</td></tr>
                                <tr><td class="px-4 py-3"><code>events</code></td><td class="px-4 py-3 text-sm">array</td><td class="px-4 py-3"><span class="text-red-600">Oui</span></td><td class="px-4 py-3 text-sm text-gray-600">Liste des événements à écouter</td></tr>
                            </tbody>
                        </table>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X POST "https://siteradar.cloud/api/v1/webhooks" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mon Webhook CI/CD",
    "url": "https://my-app.com/webhooks/siteradar",
    "events": ["scan.completed", "scan.failed"]
  }'</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440000",
    "name": "Mon Webhook CI/CD",
    "url": "https://my-app.com/webhooks/siteradar",
    "events": ["scan.completed", "scan.failed"],
    "active": true,
    "createdAt": "2024-01-20T15:00:00+00:00",
    "secret": "whsec_abc123xyz789..."
  }
}</code></pre>
                    </div>

                    <div class="mt-4 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
                        <div class="flex">
                            <svg class="w-5 h-5 text-yellow-600 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
                            </svg>
                            <p class="text-sm text-yellow-800"><strong>Important :</strong> Le <code>secret</code> n'est retourné qu'à la création. Conservez-le précieusement pour vérifier les signatures.</p>
                        </div>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/webhooks/{id}</code>
                    </div>
                    <p class="text-gray-600 mb-4">Récupère les détails d'un webhook.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440000",
    "name": "Mon Webhook CI/CD",
    "url": "https://my-app.com/webhooks/siteradar",
    "events": ["scan.completed", "scan.failed"],
    "active": true,
    "createdAt": "2024-01-15T10:00:00+00:00",
    "lastTriggeredAt": "2024-01-20T14:30:00+00:00",
    "successCount": 42,
    "failureCount": 1,
    "lastError": null,
    "secret": "whsec_abc123xyz789..."
  }
}</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-orange-100 text-orange-800 rounded">PUT</span>
                        <code class="text-lg font-mono">/webhooks/{id}</code>
                    </div>
                    <p class="text-gray-600 mb-4">Met à jour un webhook existant. Seuls les champs fournis seront modifiés.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Paramètres (tous optionnels)</h4>
                    <div class="overflow-hidden border border-gray-200 rounded-lg mb-4">
                        <table class="min-w-full divide-y divide-gray-200">
                            <thead class="bg-gray-50">
                                <tr>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Paramètre</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
                                    <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-gray-200">
                                <tr><td class="px-4 py-3"><code>name</code></td><td class="px-4 py-3 text-sm">string</td><td class="px-4 py-3 text-sm text-gray-600">Nouveau nom du webhook</td></tr>
                                <tr><td class="px-4 py-3"><code>url</code></td><td class="px-4 py-3 text-sm">string</td><td class="px-4 py-3 text-sm text-gray-600">Nouvelle URL du endpoint</td></tr>
                                <tr><td class="px-4 py-3"><code>events</code></td><td class="px-4 py-3 text-sm">array</td><td class="px-4 py-3 text-sm text-gray-600">Nouvelle liste d'événements</td></tr>
                                <tr><td class="px-4 py-3"><code>active</code></td><td class="px-4 py-3 text-sm">boolean</td><td class="px-4 py-3 text-sm text-gray-600">Activer/désactiver le webhook</td></tr>
                            </tbody>
                        </table>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X PUT "https://siteradar.cloud/api/v1/webhooks/aa0e8400..." \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "active": false,
    "events": ["scan.completed"]
  }'</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440000",
    "name": "Mon Webhook CI/CD",
    "url": "https://my-app.com/webhooks/siteradar",
    "events": ["scan.completed"],
    "active": false,
    "...": "..."
  }
}</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-red-100 text-red-800 rounded">DELETE</span>
                        <code class="text-lg font-mono">/webhooks/{id}</code>
                    </div>
                    <p class="text-gray-600 mb-4">Supprime un webhook.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X DELETE "https://siteradar.cloud/api/v1/webhooks/aa0e8400..." \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>HTTP 204 No Content</code></pre>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-blue-100 text-blue-800 rounded">POST</span>
                        <code class="text-lg font-mono">/webhooks/{id}/regenerate-secret</code>
                    </div>
                    <p class="text-gray-600 mb-4">Régénère le secret d'un webhook. L'ancien secret sera immédiatement invalidé.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Exemple de requête</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
                        <pre class="text-gray-100 text-sm"><code>curl -X POST "https://siteradar.cloud/api/v1/webhooks/aa0e8400.../regenerate-secret" \
  -H "X-API-Key: sr_live_xxxxxxxxxxxxxxxxxxxx"</code></pre>
                    </div>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "secret": "whsec_newSecret123...",
    "message": "Secret regenerated successfully"
  }
}</code></pre>
                    </div>

                    <div class="mt-4 bg-red-50 border border-red-200 rounded-lg p-4">
                        <div class="flex">
                            <svg class="w-5 h-5 text-red-600 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
                            </svg>
                            <p class="text-sm text-red-800"><strong>Attention :</strong> Mettez à jour votre endpoint immédiatement après cette opération, sinon les vérifications de signature échoueront.</p>
                        </div>
                    </div>
                </div>

                                <div class="mb-12">
                    <div class="flex items-center gap-3 mb-4">
                        <span class="px-2 py-1 text-xs font-bold bg-green-100 text-green-800 rounded">GET</span>
                        <code class="text-lg font-mono">/webhooks/events</code>
                    </div>
                    <p class="text-gray-600 mb-4">Liste tous les événements disponibles pour les webhooks.</p>

                    <h4 class="font-semibold text-gray-900 mb-2">Réponse</h4>
                    <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
                        <pre class="text-gray-100 text-sm"><code>{
  "success": true,
  "data": {
    "events": [
      {"name": "scan.started", "description": "Triggered when a scan starts"},
      {"name": "scan.completed", "description": "Triggered when a scan completes successfully"},
      {"name": "scan.failed", "description": "Triggered when a scan fails"},
      {"name": "site.verified", "description": "Triggered when a site is verified"}
    ]
  }
}</code></pre>
                    </div>
                </div>

                <div class="border-t border-gray-200 pt-8 mt-8">
                    <h3 class="text-xl font-bold text-gray-900 mb-4">Réception des webhooks</h3>
                    <p class="text-gray-600 mb-4">Cette section explique comment recevoir et traiter les webhooks sur votre serveur.</p>
                </div>

                <h3 class="text-lg font-semibold text-gray-900 mb-4">Événements disponibles</h3>
                <div class="overflow-hidden border border-gray-200 rounded-lg mb-8">
                    <table class="min-w-full divide-y divide-gray-200">
                        <thead class="bg-gray-50">
                            <tr>
                                <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Événement</th>
                                <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-gray-200">
                            <tr><td class="px-4 py-3"><code>scan.started</code></td><td class="px-4 py-3 text-sm text-gray-600">Un scan a démarré</td></tr>
                            <tr><td class="px-4 py-3"><code>scan.completed</code></td><td class="px-4 py-3 text-sm text-gray-600">Un scan s'est terminé avec succès</td></tr>
                            <tr><td class="px-4 py-3"><code>scan.failed</code></td><td class="px-4 py-3 text-sm text-gray-600">Un scan a échoué</td></tr>
                            <tr><td class="px-4 py-3"><code>site.verified</code></td><td class="px-4 py-3 text-sm text-gray-600">Un site a été vérifié</td></tr>
                        </tbody>
                    </table>
                </div>

                <h3 class="text-lg font-semibold text-gray-900 mb-4">Format des webhooks</h3>
                <p class="text-gray-600 mb-4">Chaque webhook est envoyé en POST avec un corps JSON :</p>
                <div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-8">
                    <pre class="text-gray-100 text-sm"><code>{
  "event": "scan.completed",
  "timestamp": "2024-01-20T15:35:00+00:00",
  "data": {
    "scan": {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "pagesCrawled": 42,
      "issuesCount": 15,
      "score": 78
    },
    "website": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "url": "https://example.com",
      "displayName": "Mon Site"
    }
  }
}</code></pre>
                </div>

                <h3 class="text-lg font-semibold text-gray-900 mb-4">Vérification de la signature</h3>
                <p class="text-gray-600 mb-4">Chaque webhook inclut des headers de sécurité pour vérifier son authenticité :</p>
                <div class="overflow-hidden border border-gray-200 rounded-lg mb-4">
                    <table class="min-w-full divide-y divide-gray-200">
                        <thead class="bg-gray-50">
                            <tr>
                                <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Header</th>
                                <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-gray-200">
                            <tr><td class="px-4 py-3"><code>X-SiteRadar-Signature</code></td><td class="px-4 py-3 text-sm text-gray-600">Signature HMAC SHA256</td></tr>
                            <tr><td class="px-4 py-3"><code>X-SiteRadar-Timestamp</code></td><td class="px-4 py-3 text-sm text-gray-600">Timestamp Unix de l'envoi</td></tr>
                            <tr><td class="px-4 py-3"><code>X-SiteRadar-Event</code></td><td class="px-4 py-3 text-sm text-gray-600">Type d'événement</td></tr>
                        </tbody>
                    </table>
                </div>

                <h4 class="font-semibold text-gray-900 mb-3">Exemples de vérification</h4>

                <div x-data="{ tab: 'php' }">
                    <div class="flex border-b border-gray-200">
                        <button @click="tab = 'php'" :class="tab === 'php' ? 'border-primary-500 text-primary-600' : 'border-transparent text-gray-500 hover:text-gray-700'" class="px-4 py-2 text-sm font-medium border-b-2 -mb-px">PHP</button>
                        <button @click="tab = 'js'" :class="tab === 'js' ? 'border-primary-500 text-primary-600' : 'border-transparent text-gray-500 hover:text-gray-700'" class="px-4 py-2 text-sm font-medium border-b-2 -mb-px">Node.js</button>
                        <button @click="tab = 'python'" :class="tab === 'python' ? 'border-primary-500 text-primary-600' : 'border-transparent text-gray-500 hover:text-gray-700'" class="px-4 py-2 text-sm font-medium border-b-2 -mb-px">Python</button>
                    </div>
                    <div class="bg-gray-900 rounded-b-lg p-4 overflow-x-auto">
                        <pre x-show="tab === 'php'" class="text-gray-100 text-sm"><code><?php
$secret = 'votre_secret_webhook';
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_SITERADAR_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SITERADAR_SIGNATURE'] ?? '';

// Vérifier que le timestamp n'est pas trop ancien (5 minutes)
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    die('Timestamp expired');
}

// Calculer la signature attendue
$signedPayload = "{$timestamp}.{$payload}";
$expected = 'v1=' . hash_hmac('sha256', $signedPayload, $secret);

// Comparer de manière sécurisée
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

// Traiter l'événement
$event = json_decode($payload, true);
// ... votre logique ici</code></pre>
                        <pre x-show="tab === 'js'" class="text-gray-100 text-sm"><code>const crypto = require('crypto');

function verifyWebhook(req, secret) {
    const payload = JSON.stringify(req.body);
    const timestamp = req.headers['x-siteradar-timestamp'];
    const signature = req.headers['x-siteradar-signature'];

    // Vérifier le timestamp
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp)) > 300) {
        throw new Error('Timestamp expired');
    }

    // Calculer la signature
    const signedPayload = `${timestamp}.${payload}`;
    const expected = 'v1=' + crypto
        .createHmac('sha256', secret)
        .update(signedPayload)
        .digest('hex');

    // Comparer de manière sécurisée
    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
        throw new Error('Invalid signature');
    }

    return req.body;
}</code></pre>
                        <pre x-show="tab === 'python'" class="text-gray-100 text-sm"><code>import hmac
import hashlib
import time

def verify_webhook(payload: bytes, timestamp: str, signature: str, secret: str) -> bool:
    # Vérifier le timestamp
    if abs(time.time() - int(timestamp)) > 300:
        raise ValueError('Timestamp expired')

    # Calculer la signature
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    expected = 'v1=' + hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Comparer de manière sécurisée
    if not hmac.compare_digest(expected, signature):
        raise ValueError('Invalid signature')

    return True</code></pre>
                    </div>
                </div>

                <div class="mt-8 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
                    <div class="flex">
                        <svg class="w-5 h-5 text-yellow-600 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
                        </svg>
                        <div class="text-sm text-yellow-800">
                            <p class="font-medium">Bonnes pratiques</p>
                            <ul class="mt-2 list-disc list-inside space-y-1">
                                <li>Répondez toujours avec un code 200 dans les 30 secondes</li>
                                <li>Traitez les webhooks de manière asynchrone si le traitement est long</li>
                                <li>Implémentez une idempotence pour gérer les duplications éventuelles</li>
                                <li>Conservez le secret de manière sécurisée (variable d'environnement)</li>
                            </ul>
                        </div>
                    </div>
                </div>
            </section>

                        <section class="mt-16 bg-gray-50 rounded-xl p-8">
                <h2 class="text-xl font-bold text-gray-900 mb-4">Besoin d'aide ?</h2>
                <p class="text-gray-600 mb-4">Si vous avez des questions sur l'API ou rencontrez des problèmes, n'hésitez pas à nous contacter.</p>
                <div class="flex items-center gap-4">
                    <a href="mailto:support@siteradar.cloud" class="inline-flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors">
                        <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                        </svg>
                        Contacter le support
                    </a>
                                    </div>
            </section>
        </div>
    </div>
</div>
        </main>

                            <footer class="bg-white border-t border-gray-200 mt-auto">
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
        <div class="flex flex-col md:flex-row justify-between items-center gap-4">
                        <div class="flex items-center space-x-2">
                <img src="/images/logos/medium/logo-principal-bleu-256.png?v=3"
                     alt="SiteRadar"
                     width="32"
                     height="32"
                     class="h-8 w-8">
                <span class="text-lg font-bold text-gray-900">SiteRadar</span>
            </div>

                        <div class="flex items-center gap-6 text-sm">
                <a href="/api/documentation" class="text-gray-600 hover:text-primary-600">API</a>
                <a href="/privacy" class="text-gray-600 hover:text-primary-600">Politique de confidentialité</a>
                <a href="/terms" class="text-gray-600 hover:text-primary-600">Conditions d'utilisation</a>
            </div>

                        <p class="text-sm text-gray-500">© 2026 SiteRadar</p>
        </div>
    </div>
</footer>
        
                <div id="cookie-consent"
     class="fixed bottom-0 left-0 right-0 z-50 transform translate-y-full transition-transform duration-500 ease-out"
     x-data="cookieConsent()"
     x-init="init()"
     x-show="showBanner"
     x-transition:enter="transition ease-out duration-500"
     x-transition:enter-start="translate-y-full"
     x-transition:enter-end="translate-y-0"
     x-transition:leave="transition ease-in duration-300"
     x-transition:leave-start="translate-y-0"
     x-transition:leave-end="translate-y-full"
     style="display: none;">

    <div class="bg-gray-900 border-t border-gray-700 shadow-2xl">
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
            <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
                                <div class="flex-1">
                    <p class="text-sm text-gray-300">
                        Nous utilisons des cookies pour améliorer votre expérience. En continuant, vous acceptez notre utilisation des cookies.
                        <a href="//siteradar.cloud/fr/privacy" class="text-primary-400 hover:text-primary-300 underline ml-1">
                            En savoir plus sur notre politique de confidentialité
                        </a>
                    </p>
                </div>

                                <div class="flex flex-col sm:flex-row gap-2 sm:gap-3">
                    <button @click="rejectAll()"
                            class="px-4 py-2 text-sm font-medium text-gray-300 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors">
                        Refuser
                    </button>
                    <button @click="acceptAll()"
                            class="px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 rounded-lg transition-colors">
                        Accepter
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
function cookieConsent() {
    return {
        showBanner: false,

        init() {
            const consent = localStorage.getItem('cookie_consent');
            if (!consent) {
                setTimeout(() => {
                    this.showBanner = true;
                }, 1000);
            }
        },

        acceptAll() {
            localStorage.setItem('cookie_consent', 'accepted');
            localStorage.setItem('cookie_consent_date', new Date().toISOString());
            this.showBanner = false;
        },

        rejectAll() {
            localStorage.setItem('cookie_consent', 'rejected');
            localStorage.setItem('cookie_consent_date', new Date().toISOString());
            this.showBanner = false;
        }
    }
}
</script>
    </body>
</html>