API Documentation

Integrate SiteRadar into your applications with our complete REST API.

Version 1.0 REST API JSON

Introduction

The SiteRadar API allows you to automate the management of your websites and their SEO audits. It is available for PRO subscribers.

API Base URL:

https://siteradar.cloud/api/v1

All requests must use HTTPS. HTTP requests will be rejected.

Authentication

The API uses API keys for authentication. You can create and manage your keys in the API settings.

Include your API key in the X-API-Key header of each request:

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()

Error Codes

The API uses standard HTTP codes to indicate the success or failure of a request.

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"
  }
}

Rate Limiting

Rate limits are included in the headers of each response:

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

Account

Information about your account and usage

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

Manage your websites

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

Run and view audits of your 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">Export your scan results</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">Manage your webhooks and receive real-time notifications</p>
                </div>

                <div class="prose prose-gray max-w-none mb-8">
                    <p>Webhooks allow you to receive automatic notifications when certain events occur. You can manage them via the API or in the <a href="/settings/api" class="text-primary-600 hover:text-primary-700">API settings</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">Need help?</h2>
                <p class="text-gray-600 mb-4">If you have questions about the API or encounter issues, feel free to contact us.</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>
                        Contact 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">Privacy Policy</a>
                <a href="/terms" class="text-gray-600 hover:text-primary-600">Terms of Service</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">
                        We use cookies to improve your experience. By continuing, you accept our use of cookies.
                        <a href="//siteradar.cloud/en/privacy" class="text-primary-400 hover:text-primary-300 underline ml-1">
                            Learn more about our privacy policy
                        </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">
                        Reject
                    </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">
                        Accept
                    </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>