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-8">
        <div class="grid grid-cols-1 md:grid-cols-4 gap-8">
                        <div class="col-span-1 md:col-span-2">
                <div class="flex items-center space-x-2 mb-4">
                    <img src="/images/logos/medium/logo-principal-bleu-256.png?v=3"
                         alt="SiteRadar"
                         width="40"
                         height="40"
                         class="h-10 w-10">
                    <span class="text-xl font-bold text-gray-900">SiteRadar</span>
                </div>
                <p class="text-gray-600 text-sm max-w-md">
                    Détecte, corrige, optimise ton site web
                </p>
            </div>

                        <div>
                <h3 class="font-semibold text-gray-900 mb-4">Produit</h3>
                <ul class="space-y-2 text-sm">
                    <li><a href="/pricing" class="text-gray-600 hover:text-primary-600">Tarifs</a></li>
                    <li><a href="#" class="text-gray-600 hover:text-primary-600">Fonctionnalités</a></li>
                    <li><a href="/api/documentation" class="text-gray-600 hover:text-primary-600">API</a></li>
                </ul>
            </div>

                        <div>
                <h3 class="font-semibold text-gray-900 mb-4">Légal</h3>
                <ul class="space-y-2 text-sm">
                    <li><a href="/privacy" class="text-gray-600 hover:text-primary-600">Politique de confidentialité</a></li>
                    <li><a href="/terms" class="text-gray-600 hover:text-primary-600">Conditions d'utilisation</a></li>
                </ul>
            </div>
        </div>

                <div class="border-t border-gray-200 mt-8 pt-8 text-center text-sm text-gray-500">
            <p>© 2026 SiteRadar. Tous droits réservés.</p>
        </div>
    </div>
</footer>
            
<!-- START of Symfony Web Debug Toolbar --><div id="sfwdtab7163" class="sf-toolbar sf-toolbar-opened" role="region" aria-label="Symfony Web Debug Toolbar">    <div id="sfToolbarClearer-ab7163" class="sf-toolbar-clearer"></div><div id="sfToolbarMainContent-ab7163" class="sf-toolbarreset notranslate clear-fix" data-no-turbolink data-turbo="false">                                                    <div class="sf-toolbar-block sf-toolbar-block-request sf-toolbar-status-normal " >    <a href="https://app.siteradar.cloud/_profiler/ab7163?panel=request">        <div class="sf-toolbar-icon">        <svg xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Symfony" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg>        <span class="sf-toolbar-value sf-toolbar-ajax-request-counter">            Loading…        </span>    </div>    </a>        <div class="sf-toolbar-info">        <div class="sf-toolbar-info-piece">            <b>Loading the web debug toolbar…</b>        </div>        <div class="sf-toolbar-info-piece">            Attempt #<span id="sfLoadCounter-ab7163"></span>        </div>        <div class="sf-toolbar-info-piece">            <b>                <button class="sf-cancel-button" type="button" id="sfLoadCancel-ab7163" title="Cancel loading">Cancel</button>            </b>        </div>    </div></div>                                <button class="sf-toolbar-toggle-button" type="button" id="sfToolbarToggleButton-ab7163" accesskey="D" aria-expanded="true" aria-controls="sfToolbarMainContent-ab7163">        <i class="sf-toolbar-icon-opened" title="Close Toolbar"><svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">    <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>    <line x1="18" y1="6" x2="6" y2="18"></line>    <line x1="6" y1="6" x2="18" y2="18"></line></svg></i>        <i class="sf-toolbar-icon-closed" title="Open Toolbar"><svg xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Symfony" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg></i>    </button></div></div><link rel="stylesheet" nonce="912ca8a89205275f5251bc630c6ef195" href="https://app.siteradar.cloud/_wdt/styles" /><script nonce="7a8ceab15139b5460db4b2705cca7237">/*<![CDATA[*/    if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') {        Sfjs = (function() {            "use strict";            if ('classList' in document.documentElement) {                var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };                var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };                var addClass = function(el, cssClass) { el.classList.add(cssClass); };                var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };            } else {                var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };                var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };                var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };                var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };            }            var noop = function() {};            var profilerStorageKey = 'symfony/profiler/';            var addEventListener;            var el = document.createElement('div');            if (!('addEventListener' in el)) {                addEventListener = function (element, eventName, callback) {                    element.attachEvent('on' + eventName, callback);                };            } else {                addEventListener = function (element, eventName, callback) {                    element.addEventListener(eventName, callback, false);                };            }            var request = function(url, onSuccess, onError, payload, options, tries) {                url = new URL(url);                url.searchParams.set('XDEBUG_IGNORE', '1');                url = url.toString();                var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');                options = options || {};                options.retry = options.retry || false;                tries = tries || 1;                /* this delays for 125, 375, 625, 875, and 1000, ... */                var delay = tries < 5 ? (tries - 0.5) * 250 : 1000;                xhr.open(options.method || 'GET', url, true);                xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');                xhr.onreadystatechange = function(state) {                    if (4 !== xhr.readyState) {                        return null;                    }                    if (xhr.status == 404 && options.retry && !options.stop) {                        setTimeout(function() {                            if (options.stop) {                                return;                            }                            request(url, onSuccess, onError, payload, options, tries + 1);                        }, delay);                        return null;                    }                    if (200 === xhr.status) {                        (onSuccess || noop)(xhr);                    } else {                        (onError || noop)(xhr);                    }                };                if (options.onSend) {                    options.onSend(tries);                }                xhr.send(payload || '');            };            var getPreference = function(name) {                if (!window.localStorage) {                    return null;                }                return localStorage.getItem(profilerStorageKey + name);            };            var setPreference = function(name, value) {                if (!window.localStorage) {                    return null;                }                localStorage.setItem(profilerStorageKey + name, value);            };            var requestStack = [];            var extractHeaders = function(xhr, stackElement) {                /* Here we avoid to call xhr.getResponseHeader in order to */                /* prevent polluting the console with CORS security errors */                var allHeaders = xhr.getAllResponseHeaders();                var ret;                if (ret = allHeaders.match(/^x-debug-token:\s+(.*)$/im)) {                    stackElement.profile = ret[1];                }                if (ret = allHeaders.match(/^x-debug-token-link:\s+(.*)$/im)) {                    stackElement.profilerUrl = ret[1];                }                if (ret = allHeaders.match(/^Symfony-Debug-Toolbar-Replace:\s+(.*)$/im)) {                    stackElement.toolbarReplaceFinished = false;                    stackElement.toolbarReplace = '1' === ret[1];                }            };            var successStreak = 4;            var pendingRequests = 0;            var renderAjaxRequests = function() {                var requestCounter = document.querySelector('.sf-toolbar-ajax-request-counter');                if (!requestCounter) {                    return;                }                requestCounter.textContent = requestStack.length;                var infoSpan = document.querySelector(".sf-toolbar-ajax-info");                if (infoSpan) {                    infoSpan.textContent = requestStack.length + ' AJAX request' + (requestStack.length !== 1 ? 's' : '');                }                var ajaxToolbarPanel = document.querySelector('.sf-toolbar-block-ajax');                if (requestStack.length) {                    ajaxToolbarPanel.style.display = '';                } else {                    ajaxToolbarPanel.style.display = 'none';                }                if (pendingRequests > 0) {                    addClass(ajaxToolbarPanel, 'sf-ajax-request-loading');                } else if (successStreak < 4) {                    addClass(ajaxToolbarPanel, 'sf-toolbar-status-red');                    removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading');                } else {                    removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading');                    removeClass(ajaxToolbarPanel, 'sf-toolbar-status-red');                }            };            var startAjaxRequest = function(index) {                var tbody = document.querySelector('.sf-toolbar-ajax-request-list');                if (!tbody) {                    return;                }                var nbOfAjaxRequest = tbody.rows.length;                if (nbOfAjaxRequest >= 100) {                    tbody.deleteRow(0);                }                var request = requestStack[index];                pendingRequests++;                var row = document.createElement('tr');                request.DOMNode = row;                var requestNumberCell = document.createElement('td');                requestNumberCell.textContent = index + 1;                row.appendChild(requestNumberCell);                var profilerCell = document.createElement('td');                profilerCell.textContent = 'n/a';                row.appendChild(profilerCell);                var methodCell = document.createElement('td');                methodCell.textContent = request.method;                row.appendChild(methodCell);                var typeCell = document.createElement('td');                typeCell.textContent = request.type;                row.appendChild(typeCell);                var statusCodeCell = document.createElement('td');                var statusCode = document.createElement('span');                statusCode.textContent = 'n/a';                statusCodeCell.appendChild(statusCode);                row.appendChild(statusCodeCell);                var pathCell = document.createElement('td');                pathCell.className = 'sf-ajax-request-url';                if ('GET' === request.method) {                    var pathLink = document.createElement('a');                    pathLink.setAttribute('href', request.url);                    pathLink.textContent = request.url;                    pathCell.appendChild(pathLink);                } else {                    pathCell.textContent = request.url;                }                pathCell.setAttribute('title', request.url);                row.appendChild(pathCell);                var durationCell = document.createElement('td');                durationCell.className = 'sf-ajax-request-duration';                durationCell.textContent = 'n/a';                row.appendChild(durationCell);                request.liveDurationHandle = setInterval(function() {                    durationCell.textContent = (new Date() - request.start) + ' ms';                }, 100);                row.className = 'sf-ajax-request sf-ajax-request-loading';                tbody.insertBefore(row, null);                var toolbarInfo = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info');                toolbarInfo.scrollTop = toolbarInfo.scrollHeight;                renderAjaxRequests();            };            var finishAjaxRequest = function(index) {                var request = requestStack[index];                clearInterval(request.liveDurationHandle);                if (!request.DOMNode) {                    return;                }                if (request.toolbarReplace && !request.toolbarReplaceFinished && request.profile) {                    /* Flag as complete because finishAjaxRequest can be called multiple times. */                    request.toolbarReplaceFinished = true;                    /* Search up through the DOM to find the toolbar's container ID. */                    for (var elem = request.DOMNode; elem && elem !== document; elem = elem.parentNode) {                        if (elem.id.match(/^sfwdt/)) {                            Sfjs.loadToolbar(elem.id.replace(/^sfwdt/, ''), request.profile);                            break;                        }                    }                }                pendingRequests--;                var row = request.DOMNode;                /* Unpack the children from the row */                var profilerCell = row.children[1];                var methodCell = row.children[2];                var statusCodeCell = row.children[4];                var statusCodeElem = statusCodeCell.children[0];                var durationCell = row.children[6];                if (request.error) {                    row.className = 'sf-ajax-request sf-ajax-request-error';                    methodCell.className = 'sf-ajax-request-error';                    successStreak = 0;                } else {                    row.className = 'sf-ajax-request sf-ajax-request-ok';                    successStreak++;                }                if (request.statusCode) {                    if (request.statusCode < 300) {                        statusCodeElem.setAttribute('class', 'sf-toolbar-status');                    } else if (request.statusCode < 400) {                        statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-yellow');                    } else {                        statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red');                    }                    statusCodeElem.textContent = request.statusCode;                } else {                    statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red');                }                if (request.duration) {                    durationCell.textContent = request.duration + ' ms';                }                if (request.profilerUrl) {                    profilerCell.textContent = '';                    var profilerLink = document.createElement('a');                    profilerLink.setAttribute('href', request.profilerUrl);                    profilerLink.textContent = request.profile;                    profilerCell.appendChild(profilerLink);                }                renderAjaxRequests();            };                        if (window.EventSource) {                var oldEventSource = window.EventSource;                window.EventSource = function (url, options) {                    var es = new oldEventSource(url, options);                    if (!url.toString().match(new RegExp("^\/((index|app(_[\\w]+)?)\\.php\/)?_wdt"))) {                        var stackElement = {                            error: false,                            url: url,                            method: 'GET',                            type: 'event-stream',                            start: new Date()                        };                        var idx = requestStack.push(stackElement) - 1;                        startAjaxRequest(idx);                        addEventListener(es, 'error', function () {                            stackElement.error = true;                            finishAjaxRequest(idx);                        });                        addEventListener(es, 'open', function () {                            stackElement.statusCode = 200;                            stackElement.toolbarReplaceFinished = false;                            stackElement.toolbarReplace = true;                        });                        addEventListener(es, 'symfony:debug:started', function (event) {                            var items = event.data.split('\n');                            stackElement.profile = items[0];                            stackElement.profilerUrl = items[1];                        });                        addEventListener(es, 'symfony:debug:error', function (event) {                            stackElement.error = true;                            stackElement.statusCode = event.data;                            finishAjaxRequest(idx);                        });                        addEventListener(es, 'symfony:debug:finished', function () {                            stackElement.duration = new Date() - stackElement.start;                            stackElement.toolbarReplaceFinished = false;                            stackElement.toolbarReplace = true;                            finishAjaxRequest(idx);                        });                    }                    return es;                };            }            if (window.fetch && window.fetch.polyfill === undefined) {                var oldFetch = window.fetch;                window.fetch = function () {                    var promise = oldFetch.apply(this, arguments);                    var url = arguments[0];                    var params = arguments[1];                    var paramType = Object.prototype.toString.call(arguments[0]);                    if (paramType === '[object Request]') {                        url = arguments[0].url;                        params = {                            method: arguments[0].method,                            credentials: arguments[0].credentials,                            headers: arguments[0].headers,                            mode: arguments[0].mode,                            redirect: arguments[0].redirect                        };                    } else {                        url = String(url);                    }                    if (!url.match(new RegExp("^\/((index|app(_[\\w]+)?)\\.php\/)?_wdt"))) {                        var method = 'GET';                        if (params && params.method !== undefined) {                            method = params.method;                        }                        var stackElement = {                            error: false,                            url: url,                            method: method,                            type: 'fetch',                            start: new Date()                        };                        var idx = requestStack.push(stackElement) - 1;                        promise.then(function (r) {                            stackElement.duration = new Date() - stackElement.start;                            stackElement.error = r.status < 200 || r.status >= 400;                            stackElement.statusCode = r.status;                            stackElement.profile = r.headers.get('x-debug-token');                            stackElement.profilerUrl = r.headers.get('x-debug-token-link');                            stackElement.toolbarReplaceFinished = false;                            stackElement.toolbarReplace = '1' === r.headers.get('Symfony-Debug-Toolbar-Replace');                            finishAjaxRequest(idx);                        }, function (e){                            stackElement.error = true;                            finishAjaxRequest(idx);                        });                        startAjaxRequest(idx);                    }                    return promise;                };            }            if (window.XMLHttpRequest && XMLHttpRequest.prototype.addEventListener) {                var proxied = XMLHttpRequest.prototype.open;                XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {                    var self = this;                    /* prevent logging AJAX calls to static and inline files, like templates */                    var path = url;                    if (url.slice(0, 1) === '/') {                        if (0 === url.indexOf('')) {                            path = url.slice(0);                        }                    }                    else if (0 === url.indexOf('https\u003A\/\/app.siteradar.cloud')) {                        path = url.slice(27);                    }                    if (!path.match(new RegExp("^\/((index|app(_[\\w]+)?)\\.php\/)?_wdt"))) {                        var stackElement = {                            error: false,                            url: url,                            method: method,                            type: 'xhr',                            start: new Date()                        };                        var idx = requestStack.push(stackElement) - 1;                        this.addEventListener('readystatechange', function() {                            if (self.readyState == 4) {                                stackElement.duration = new Date() - stackElement.start;                                stackElement.error = self.status < 200 || self.status >= 400;                                stackElement.statusCode = self.status;                                extractHeaders(self, stackElement);                                finishAjaxRequest(idx);                            }                        }, false);                        startAjaxRequest(idx);                    }                    proxied.apply(this, Array.prototype.slice.call(arguments));                };            }                        return {                hasClass: hasClass,                removeClass: removeClass,                addClass: addClass,                toggleClass: toggleClass,                getPreference: getPreference,                setPreference: setPreference,                addEventListener: addEventListener,                request: request,                renderAjaxRequests: renderAjaxRequests,                getSfwdt: function(token) {                    return document.getElementById('sfwdt' + token);                },                load: function(selector, url, onSuccess, onError, options) {                    var el = document.getElementById(selector);                    if (el && el.getAttribute('data-sfurl') !== url) {                        request(                            url,                            function(xhr) {                                el.innerHTML = xhr.responseText;                                el.setAttribute('data-sfurl', url);                                removeClass(el, 'loading');                                var pending = pendingRequests;                                for (var i = 0; i < requestStack.length; i++) {                                    startAjaxRequest(i);                                    if (requestStack[i].duration || requestStack[i].error) {                                        finishAjaxRequest(i);                                    }                                }                                /* Revert the pending state in case there was a start called without a finish above. */                                pendingRequests = pending;                                (onSuccess || noop)(xhr, el);                            },                            function(xhr) { (onError || noop)(xhr, el); },                            '',                            options                        );                    }                    return this;                },                showToolbar: function(token) {                    var sfwdt = this.getSfwdt(token);                    if ('closed' === getPreference('toolbar/displayState')) {                        addClass(sfwdt, 'sf-toolbar-closed');                        removeClass(sfwdt, 'sf-toolbar-opened');                    } else {                        addClass(sfwdt, 'sf-toolbar-opened');                        removeClass(sfwdt, 'sf-toolbar-closed');                    }                },                hideToolbar: function(token) {                    var sfwdt = this.getSfwdt(token);                    addClass(sfwdt, 'sf-toolbar-closed');                    removeClass(sfwdt, 'sf-toolbar-opened');                },                initToolbar: function(token) {                    this.showToolbar(token);                    var toggleButton = document.querySelector(`#sfToolbarToggleButton-${token}`);                    addEventListener(toggleButton, 'click', function (event) {                        event.preventDefault();                        const newState = 'opened' === getPreference('toolbar/displayState') ? 'closed' : 'opened';                        setPreference('toolbar/displayState', newState);                        'opened' === newState ? Sfjs.showToolbar(token) : Sfjs.hideToolbar(token);                    });                },                loadToolbar: function(token, newToken) {                    var that = this;                    var triesCounter = document.getElementById('sfLoadCounter-' + token);                    var options = {                        retry: true,                        onSend: function (count) {                            if (count === 3) {                                that.initToolbar(token);                            }                            if (triesCounter) {                                triesCounter.textContent = count;                            }                        },                    };                    var cancelButton = document.getElementById('sfLoadCancel-' + token);                    if (cancelButton) {                        addEventListener(cancelButton, 'click', function (event) {                            event.preventDefault();                            options.stop = true;                            that.hideToolbar(token);                        });                    }                    newToken = (newToken || token);                    this.load(                        'sfwdt' + token,                        'https\u003A\/\/app.siteradar.cloud\/_wdt\/xxxxxx'.replace(/xxxxxx/, newToken),                        function(xhr, el) {                            var toolbarContent = document.getElementById('sfToolbarMainContent-' + newToken);                            /* Do nothing in the edge case where the toolbar has already been replaced with a new one */                            if (!toolbarContent) {                                return;                            }                            /* Replace the ID, it has to match the new token */                            toolbarContent.parentElement.id = 'sfwdt' + newToken;                            /* Evaluate in global scope scripts embedded inside the toolbar */                            var i, scripts = [].slice.call(el.querySelectorAll('script'));                            for (i = 0; i < scripts.length; ++i) {                                if (scripts[i].firstChild) {                                    eval.call({}, scripts[i].firstChild.nodeValue);                                }                            }                            el.style.display = -1 !== xhr.responseText.indexOf('sf-toolbarreset') ? 'block' : 'none';                            if (el.style.display == 'none') {                                return;                            }                            that.initToolbar(newToken);                            /* Handle toolbar-info position */                            var toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block'));                            for (i = 0; i < toolbarBlocks.length; ++i) {                                toolbarBlocks[i].onmouseover = function () {                                    var toolbarInfo = this.querySelectorAll('.sf-toolbar-info')[0];                                    var pageWidth = document.body.clientWidth;                                    var elementWidth = toolbarInfo.offsetWidth;                                    var leftValue = (elementWidth + this.offsetLeft) - pageWidth;                                    var rightValue = (elementWidth + (pageWidth - this.offsetLeft)) - pageWidth;                                    /* Reset right and left value, useful on window resize */                                    toolbarInfo.style.right = '';                                    toolbarInfo.style.left = '';                                    if (elementWidth > pageWidth) {                                        toolbarInfo.style.left = 0;                                    }                                    else if (leftValue > 0 && rightValue > 0) {                                        toolbarInfo.style.right = (rightValue * -1) + 'px';                                    } else if (leftValue < 0) {                                        toolbarInfo.style.left = 0;                                    } else {                                        toolbarInfo.style.right = '0px';                                    }                                };                            }                            renderAjaxRequests();                            addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() {                                requestStack = [];                                renderAjaxRequests();                                successStreak = 4;                                document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = '';                            });                            addEventListener(document.querySelector('.sf-toolbar-block-ajax'), 'mouseenter', function (event) {                                var elem = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info');                                elem.scrollTop = elem.scrollHeight;                            });                            addEventListener(document.querySelector('.sf-toolbar-block-ajax > .sf-toolbar-icon'), 'click', function (event) {                                event.preventDefault();                                toggleClass(this.parentNode, 'hover');                            });                            var dumpInfo = document.querySelector('.sf-toolbar-block-dump .sf-toolbar-info');                            if (null !== dumpInfo) {                                addEventListener(dumpInfo, 'sfbeforedumpcollapse', function () {                                    dumpInfo.style.minHeight = dumpInfo.getBoundingClientRect().height+'px';                                });                                addEventListener(dumpInfo, 'mouseleave', function () {                                    dumpInfo.style.minHeight = '';                                });                            }                        },                        function(xhr) {                            if (xhr.status !== 0 && !options.stop) {                                var sfwdt = that.getSfwdt(token);                                sfwdt.innerHTML = '\                                <div class="sf-toolbarreset notranslate">\                                    <div class="sf-toolbar-icon"><svg width="26" height="28" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 26 28" enable-background="new 0 0 26 28" xml:space="preserve"><path fill="#FFFFFF" d="M13 0C5.8 0 0 5.8 0 13c0 7.2 5.8 13 13 13c7.2 0 13-5.8 13-13C26 5.8 20.2 0 13 0z M20 7.5 c-0.6 0-1-0.3-1-0.9c0-0.2 0-0.4 0.2-0.6c0.1-0.3 0.2-0.3 0.2-0.4c0-0.3-0.5-0.4-0.7-0.4c-2 0.1-2.5 2.7-2.9 4.8l-0.2 1.1 c1.1 0.2 1.9 0 2.4-0.3c0.6-0.4-0.2-0.8-0.1-1.3C18 9.2 18.4 9 18.7 8.9c0.5 0 0.8 0.5 0.8 1c0 0.8-1.1 2-3.3 1.9 c-0.3 0-0.5 0-0.7-0.1L15 14.1c-0.4 1.7-0.9 4.1-2.6 6.2c-1.5 1.8-3.1 2.1-3.8 2.1c-1.3 0-2.1-0.6-2.2-1.6c0-0.9 0.8-1.4 1.3-1.4 c0.7 0 1.2 0.5 1.2 1.1c0 0.5-0.2 0.6-0.4 0.7c-0.1 0.1-0.3 0.2-0.3 0.4c0 0.1 0.1 0.3 0.4 0.3c0.5 0 0.9-0.3 1.2-0.5 c1.3-1 1.7-2.9 2.4-6.2l0.1-0.8c0.2-1.1 0.5-2.3 0.8-3.5c-0.9-0.7-1.4-1.5-2.6-1.8c-0.8-0.2-1.3 0-1.7 0.4C8.4 10 8.6 10.7 9 11.1 l0.7 0.7c0.8 0.9 1.3 1.7 1.1 2.7c-0.3 1.6-2.1 2.8-4.3 2.1c-1.9-0.6-2.2-1.9-2-2.7c0.2-0.6 0.7-0.8 1.2-0.6 c0.5 0.2 0.7 0.8 0.6 1.3c0 0.1 0 0.1-0.1 0.3C6 15 5.9 15.2 5.9 15.3c-0.1 0.4 0.4 0.7 0.8 0.8c0.8 0.3 1.7-0.2 1.9-0.9 c0.2-0.6-0.2-1.1-0.4-1.2l-0.8-0.9c-0.4-0.4-1.2-1.5-0.8-2.8c0.2-0.5 0.5-1 0.9-1.4c1-0.7 2-0.8 3-0.6c1.3 0.4 1.9 1.2 2.8 1.9 c0.5-1.3 1.1-2.6 2-3.8c0.9-1 2-1.7 3.3-1.8C20 4.8 21 5.4 21 6.3C21 6.7 20.8 7.5 20 7.5z"/></svg></div>\                                    An error occurred while loading the web debug toolbar. <a href="https\u003A\/\/app.siteradar.cloud\/_profiler\/' + newToken + '">Open the web profiler.</a>\                                </div>\                            ';                                sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar');                            }                        },                        options                    );                    return this;                },                toggle: function(selector, elOn, elOff) {                    var tmp = elOn.style.display,                        el = document.getElementById(selector);                    elOn.style.display = elOff.style.display;                    elOff.style.display = tmp;                    if (el) {                        el.style.display = 'none' === tmp ? 'none' : 'block';                    }                    return this;                },            };        })();    }    Sfjs.loadToolbar('ab7163');/*]]>*/</script><!-- END of Symfony Web Debug Toolbar -->
</body>
</html>