URL Inspection API : automatiser le diagnostic d'indexation

Un e-commerce de 22 000 pages produit migre vers un nouveau CMS. Trois semaines après la mise en production, le trafic organique chute de 31 %. L'équipe SEO ouvre Search Console, inspecte manuellement une dizaine d'URLs, constate des problèmes d'indexation — mais sur 22 000 pages, l'inspection manuelle n'est pas une option. C'est exactement le cas d'usage pour lequel Google a ouvert l'URL Inspection API.

Ce que l'API expose (et ce qu'elle ne dit pas)

L'URL Inspection API est un endpoint de la Google Search Console API, disponible depuis janvier 2022. Elle retourne, pour une URL donnée, les mêmes données que l'outil d'inspection d'URL dans l'interface web — mais de façon programmatique.

Les données exploitables

L'API renvoie un objet InspectionResult contenant trois blocs principaux :

  • indexStatusResult : le statut d'indexation (SUBMITTED_AND_INDEXED, CRAWLED_NOT_INDEXED, DISCOVERED_NOT_INDEXED, etc.), la dernière date de crawl, la page canonical détectée par Google, le verdict de couverture.
  • mobileUsabilityResult : le verdict mobile (PASS, FAIL) et les issues détectées (texte trop petit, éléments cliquables trop proches, etc.).
  • richResultsResult : la détection des données structurées et les éventuelles erreurs de validation.

Ce qui manque : l'API ne retourne pas les données de performance (clics, impressions, position). Elle ne donne pas non plus le contenu rendu de la page (le HTML post-JavaScript). Pour le rendu, il faut passer par d'autres méthodes — tester ce que Google voit réellement reste un exercice complémentaire.

Ce que le statut d'indexation vous dit vraiment

Le champ coverageState est l'information la plus précieuse. Voici les statuts critiques à surveiller en priorité :

Statut Signification Action
SUBMITTED_AND_INDEXED URL dans le sitemap, indexée RAS
CRAWLED_CURRENTLY_NOT_INDEXED Googlebot a crawlé mais n'indexe pas Problème de qualité ou de signal
DISCOVERED_CURRENTLY_NOT_INDEXED URL connue, jamais crawlée Problème de crawl budget ou de priorisation
PAGE_WITH_REDIRECT Redirection détectée Vérifier la chaîne de redirections
DUPLICATE_WITHOUT_USER_SELECTED_CANONICAL Duplicate, Google choisit une autre canonical Revoir la stratégie canonical

Le statut CRAWLED_CURRENTLY_NOT_INDEXED est souvent le plus traître. Google a vu votre page, l'a évaluée, et a décidé qu'elle ne méritait pas l'index. C'est un signal fort de problème de contenu, de duplication ou de thin content. Si vous observez ce statut sur des pages stratégiques, c'est un red flag — pas un simple retard d'indexation. Nous avons couvert les causes profondes dans pourquoi Google n'indexe pas vos pages.

Authentification et setup technique

L'API utilise OAuth 2.0 avec un service account. Vous devez lier ce service account à la propriété Search Console concernée.

Étape 1 : créer le service account

Dans la Google Cloud Console, activez l'API Search Console (searchconsole.googleapis.com), créez un service account, et téléchargez la clé JSON.

# Activer l'API via gcloud CLI
gcloud services enable searchconsole.googleapis.com --project=mon-projet-seo

# Créer le service account
gcloud iam service-accounts create seo-inspector \
  --display-name="SEO URL Inspector" \
  --project=mon-projet-seo

# Générer la clé JSON
gcloud iam service-accounts keys create ./credentials.json \
  --iam-account=seo-inspector@mon-projet-seo.iam.gserviceaccount.com

Étape 2 : donner accès dans Search Console

L'email du service account (ex : [email protected]) doit être ajouté comme utilisateur de la propriété Search Console avec au minimum le rôle "Restreint". C'est un détail souvent oublié qui provoque des erreurs 403 silencieuses.

Étape 3 : premier appel

Voici un appel minimal en TypeScript avec la bibliothèque officielle Google :

import { google } from 'googleapis';
import { readFileSync } from 'fs';

const CREDENTIALS_PATH = './credentials.json';
const SITE_URL = 'https://www.mon-ecommerce.fr/'; // Propriété SC exacte

async function inspectUrl(urlToInspect: string) {
  const auth = new google.auth.GoogleAuth({
    keyFile: CREDENTIALS_PATH,
    scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
  });

  const searchconsole = google.searchconsole({ version: 'v1', auth });

  const response = await searchconsole.urlInspection.index.inspect({
    requestBody: {
      inspectionUrl: urlToInspect,
      siteUrl: SITE_URL,
    },
  });

  const result = response.data.inspectionResult;

  return {
    url: urlToInspect,
    coverageState: result?.indexStatusResult?.coverageState,
    robotsTxtState: result?.indexStatusResult?.robotsTxtState,
    indexingState: result?.indexStatusResult?.indexingState,
    lastCrawlTime: result?.indexStatusResult?.lastCrawlTime,
    googleCanonical: result?.indexStatusResult?.googleCanonical,
    userCanonical: result?.indexStatusResult?.userCanonical,
    mobileVerdict: result?.mobileUsabilityResult?.verdict,
    richResultsVerdict: result?.richResultsResult?.verdict,
  };
}

// Usage
inspectUrl('https://www.mon-ecommerce.fr/chaussures/running-homme-nike-air-zoom')
  .then(console.log)
  .catch(console.error);

Point critique : le champ siteUrl doit correspondre exactement à la propriété déclarée dans Search Console. Si votre propriété est sc-domain:mon-ecommerce.fr, utilisez cette chaîne. Si c'est https://www.mon-ecommerce.fr/, utilisez exactement cela, slash final inclus. Une erreur ici retourne un 403 opaque.

Limites de quota et stratégie de requêtage

L'API impose une limite de 2 000 requêtes par jour par propriété. C'est le plafond documenté par Google dans la documentation officielle de l'API. Pas de possibilité d'augmentation via les quotas GCP standards.

Pour un site de 22 000 pages, cela signifie 11 jours minimum pour inspecter l'intégralité du catalogue. C'est une contrainte structurante qui impose de prioriser intelligemment.

Stratégie de priorisation

Inspecter toutes les URLs de façon séquentielle est un gaspillage. La bonne approche :

1. Prioriser par valeur business. Commencez par les pages qui génèrent du trafic ou du chiffre d'affaires. Croisez les données de Google Analytics 4 avec votre liste d'URLs pour extraire les pages avec trafic organique > 0 sur les 90 derniers jours.

2. Cibler les pages suspectes. Les pages récemment modifiées, les nouvelles URLs, les pages avec des signaux d'alerte dans le rapport de couverture de Search Console.

3. Échantillonner par template. Sur un e-commerce, les pages produit partagent souvent le même template. Si 50 pages produit sur 50 sont indexées correctement, les 15 000 autres suivent probablement le même pattern. Inspectez un échantillon statistiquement significatif (300-400 pages) plutôt que l'intégralité.

4. Rotation continue. Mettez en place un cron qui inspecte ~2 000 URLs par jour en rotation, avec re-inspection plus fréquente des pages prioritaires.

import { readFileSync } from 'fs';

interface UrlPriority {
  url: string;
  priority: number; // 1 = critique, 5 = faible
  lastInspected: Date | null;
}

function selectUrlsForToday(
  urls: UrlPriority[],
  dailyQuota: number = 2000
): string[] {
  const now = new Date();

  // Trier par priorité, puis par ancienneté d'inspection
  const sorted = urls.sort((a, b) => {
    if (a.priority !== b.priority) return a.priority - b.priority;

    const aAge = a.lastInspected
      ? now.getTime() - a.lastInspected.getTime()
      : Infinity;
    const bAge = b.lastInspected
      ? now.getTime() - b.lastInspected.getTime()
      : Infinity;

    return bAge - aAge; // Les plus anciens d'abord
  });

  // Réserver 20% du quota pour les URLs jamais inspectées
  const neverInspected = sorted.filter((u) => !u.lastInspected);
  const alreadyInspected = sorted.filter((u) => u.lastInspected);

  const newQuota = Math.min(
    Math.floor(dailyQuota * 0.2),
    neverInspected.length
  );
  const refreshQuota = dailyQuota - newQuota;

  return [
    ...neverInspected.slice(0, newQuota).map((u) => u.url),
    ...alreadyInspected.slice(0, refreshQuota).map((u) => u.url),
  ];
}

Gérer le rate limiting

Au-delà du quota journalier, l'API impose un rate limit par seconde. Un burst de requêtes simultanées provoque des erreurs 429. Implémentez un délai de 600-800 ms entre chaque appel, ou utilisez une queue avec backoff exponentiel. En pratique, 1 requête par seconde est un rythme sûr qui permet de consommer les 2 000 requêtes quotidiennes en ~33 minutes.

Pipeline complet : du crawl au dashboard d'alertes

L'inspection ponctuelle n'a que peu de valeur. L'intérêt réel émerge quand vous construisez un pipeline de monitoring continu qui compare les résultats dans le temps.

Architecture recommandée

[Sitemap XML] → [URL Selector] → [Inspection API] → [PostgreSQL] → [Diff Engine] → [Alertes]
     ↑                                                                    |
     └──────────── [Screaming Frog / crawl interne] ─────────────────────┘

Le sitemap XML est votre source de vérité pour les URLs que vous voulez indexées. Le croisement avec un crawl Screaming Frog vous donne les URLs orphelines (présentes sur le site mais absentes du sitemap).

Stockage et historisation

Chaque inspection doit être stockée avec un timestamp pour permettre la comparaison temporelle. Voici un schéma PostgreSQL minimal :

CREATE TABLE url_inspections (
  id SERIAL PRIMARY KEY,
  url TEXT NOT NULL,
  inspected_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  coverage_state TEXT,
  indexing_state TEXT,
  robots_txt_state TEXT,
  last_crawl_time TIMESTAMP WITH TIME ZONE,
  google_canonical TEXT,
  user_canonical TEXT,
  mobile_verdict TEXT,
  rich_results_verdict TEXT,
  raw_response JSONB
);

CREATE INDEX idx_inspections_url_date ON url_inspections (url, inspected_at DESC);
CREATE INDEX idx_inspections_coverage ON url_inspections (coverage_state);

-- Vue pour détecter les régressions
CREATE VIEW indexation_regressions AS
SELECT
  curr.url,
  prev.coverage_state AS previous_state,
  curr.coverage_state AS current_state,
  prev.inspected_at AS previous_check,
  curr.inspected_at AS current_check
FROM url_inspections curr
JOIN LATERAL (
  SELECT coverage_state, inspected_at
  FROM url_inspections prev
  WHERE prev.url = curr.url
    AND prev.inspected_at < curr.inspected_at
  ORDER BY prev.inspected_at DESC
  LIMIT 1
) prev ON TRUE
WHERE curr.coverage_state != prev.coverage_state
  AND curr.inspected_at > NOW() - INTERVAL '48 hours';

La vue indexation_regressions est la pièce maîtresse : elle détecte toute URL dont le statut d'indexation a changé entre deux inspections consécutives. Une page qui passe de SUBMITTED_AND_INDEXED à CRAWLED_CURRENTLY_NOT_INDEXED déclenche une alerte immédiate.

Le diff engine : détecter les canonical hijacks

Un cas particulièrement vicieux : Google décide de remplacer votre canonical par une autre URL. Le champ googleCanonical de l'API est différent de userCanonical. C'est un signal de cannibalisation ou de contenu dupliqué non maîtrisé.

interface CanonicalMismatch {
  url: string;
  userCanonical: string;
  googleCanonical: string;
  detectedAt: Date;
}

function detectCanonicalMismatches(
  inspections: Array<{
    url: string;
    userCanonical?: string;
    googleCanonical?: string;
  }>
): CanonicalMismatch[] {
  return inspections
    .filter((i) => {
      if (!i.userCanonical || !i.googleCanonical) return false;
      // Normaliser les URLs avant comparaison (trailing slash, protocole)
      const normalize = (u: string) =>
        u.replace(/\/$/, '').replace(/^http:/, 'https:');
      return normalize(i.userCanonical) !== normalize(i.googleCanonical);
    })
    .map((i) => ({
      url: i.url,
      userCanonical: i.userCanonical!,
      googleCanonical: i.googleCanonical!,
      detectedAt: new Date(),
    }));
}

Ce type de divergence canonical est exactement le genre de régression silencieuse qui passe inaperçue pendant des semaines. Un outil de monitoring comme Seogard détecte automatiquement ces décalages, mais si vous construisez votre propre pipeline, ce diff est indispensable.

Scénario réel : migration CMS d'un e-commerce mode

Contexte : un site e-commerce mode avec 18 500 pages produit, 340 pages catégorie, et 85 pages éditoriales. Migration de Magento 2 vers une stack headless (Next.js + Commercetools). L'équipe technique a mis en place des redirections 301 systématiques, conservé la structure d'URLs, et déployé un sitemap XML à jour.

Semaine 1 post-migration : les premiers signaux

L'équipe SEO lance l'inspection API sur les 340 pages catégorie (les plus stratégiques en termes de trafic). Résultat :

  • 287 pages : SUBMITTED_AND_INDEXED
  • 38 pages : CRAWLED_CURRENTLY_NOT_INDEXED
  • 15 pages : DISCOVERED_CURRENTLY_NOT_INDEXED

Les 38 pages crawlées mais non indexées partagent un point commun : elles utilisent un composant de filtres à facettes rendu côté client. Le contenu principal (la liste de produits) n'apparaît pas dans le HTML initial — il est injecté par JavaScript après hydratation React. Google a crawlé la page, n'a vu qu'un skeleton loader, et a décidé que le contenu était insuffisant pour l'indexation.

C'est un cas classique de dynamic rendering mal implémenté. La solution : s'assurer que le SSR Next.js pré-rend bien la liste de produits dans le HTML initial. L'équipe front corrige le composant, vérifie avec Chrome DevTools (onglet Network, option "Disable JavaScript" pour simuler un crawler), et relance l'inspection 72h plus tard.

Semaine 2-3 : échantillonnage produit

Avec 2 000 requêtes/jour, l'équipe lance l'inspection sur un échantillon aléatoire stratifié de 1 500 pages produit (400 best-sellers, 400 mid-range, 700 long tail). Le script de priorisation réserve les 500 requêtes restantes pour réinspecter les 38 pages catégorie corrigées.

Résultats produit :

  • 1 312 pages : SUBMITTED_AND_INDEXED
  • 143 pages : CRAWLED_CURRENTLY_NOT_INDEXED
  • 45 pages : canonical Google ≠ canonical déclarée

Les 45 divergences canonical pointent toutes vers l'ancienne URL Magento. Les redirections 301 sont en place, mais Google a conservé l'ancienne URL comme canonical. Ce phénomène est normal durant les premières semaines post-migration — Google met du temps à consolider les signaux. L'équipe programme une réinspection de ces URLs à J+30 pour vérifier la convergence.

Les 143 pages non indexées sont principalement des produits en rupture de stock avec très peu de contenu (titre + "Rupture de stock"). L'équipe décide d'ajouter des produits similaires, des avis clients, et un contenu descriptif enrichi pour renforcer le signal de qualité. Ce type de page thin est un candidat classique pour l'index bloat quand elles sont indexées, et pour le non-indexing quand elles ne le sont pas — un dilemme que seule la qualité du contenu résout.

Semaine 6 : stabilisation

Le pipeline d'inspection tourne en continu. La vue indexation_regressions ne remonte plus que 2-3 changements par jour (des pages produit supprimées ou en rupture longue). Le taux d'indexation des pages catégorie est remonté à 98 %. Le trafic organique a récupéré son niveau pré-migration et progresse de 8 % grâce à l'amélioration des Core Web Vitals du nouveau front — un gain mesuré sur les métriques LCP et INP.

Compléter l'API avec d'autres sources de données

L'URL Inspection API ne couvre qu'une partie du diagnostic. Un pipeline robuste croise plusieurs sources.

Crawl interne : Screaming Frog en mode headless

Screaming Frog peut tourner en ligne de commande sans interface graphique, ce qui permet de l'intégrer dans un pipeline CI/CD ou un cron serveur :

# Crawl headless Screaming Frog sur Linux
/opt/ScreamingFrogSEOSpider/ScreamingFrogSEOSpider \
  --crawl https://www.mon-ecommerce.fr/ \
  --headless \
  --save-crawl \
  --output-folder /data/crawls/$(date +%Y-%m-%d) \
  --export-tabs "Internal:All,Response Codes:All" \
  --config /opt/configs/ecommerce-config.seospiderconfig

Le crawl Screaming Frog vous donne les codes HTTP, les meta robots, les meta descriptions, les title tags, la profondeur de crawl — tout ce que l'API d'inspection ne fournit pas. Le croisement des deux datasets révèle les incohérences : une page en noindex côté crawl mais que Google tente d'indexer, ou une page avec un 200 côté serveur mais en CRAWLED_CURRENTLY_NOT_INDEXED côté Google.

Search Console Performance API

Pour corréler les statuts d'indexation avec l'impact trafic, la Search Console API propose aussi un endpoint searchAnalytics.query qui retourne clics, impressions, CTR et position par URL. En croisant ces données avec les résultats d'inspection, vous identifiez les pages indexées mais qui ne génèrent aucune impression — signe potentiel d'un problème de contenu ou de cannibalisation.

Logs serveur

Les logs d'accès Nginx ou Apache restent la source la plus fiable pour savoir quand Googlebot a réellement crawlé une page. L'API Inspection donne lastCrawlTime, mais les logs serveur permettent de voir la fréquence de crawl, le user-agent exact, et les codes de réponse servis — des données essentielles pour comprendre comment Google alloue son crawl budget sur votre site. La documentation de Google sur l'architecture de crawl détaille les différents user-agents utilisés.

Pièges et edge cases

URLs avec paramètres et fragments

L'API traite chaque URL comme une chaîne distincte. https://site.fr/page?color=red et https://site.fr/page sont deux inspections séparées qui consomment chacune une unité de quota. Si votre site génère des variantes paramétrées, inspectez uniquement les URLs canoniques.

Propriétés domain vs. URL-prefix

Si vous utilisez une propriété de type sc-domain:, le champ siteUrl de l'API doit être exactement sc-domain:mon-ecommerce.fr. L'API supporte les deux types de propriétés, mais le format diffère. Mélanger les deux provoque des erreurs 403 sans message explicite.

Données en cache côté Google

Les résultats de l'API reflètent l'état de l'index Google au moment de l'appel, mais avec un délai. Si vous venez de publier une page, l'API peut retourner URL_NOT_ON_GOOGLE pendant plusieurs heures, voire jours. Ne prenez pas de décision corrective sur une URL de moins de 72h. Référez-vous à la documentation officielle de l'URL Inspection API pour les détails sur la fraîcheur des données.

Le piège du robots.txt temporaire

Pendant une migration, il arrive qu'un Disallow temporaire dans le robots.txt bloque Googlebot sur certaines sections. L'API retournera robotsTxtState: DISALLOWED — vérifiez toujours ce champ avant de diagnostiquer un problème de contenu.

Sites avec rendering JavaScript lourd

Pour les Single Page Applications, l'API reflète le résultat après le rendering par le Web Rendering Service de Google. Si votre page est mal pré-rendue côté serveur, le statut d'indexation peut fluctuer selon que Google a réussi ou non à exécuter votre JavaScript au moment du crawl. Les limites de taille de page documentées par Google ajoutent une variable supplémentaire — un point couvert en détail dans notre article sur les limites de Googlebot.

Automatiser les alertes : quand intervenir, quand ignorer

Toutes les régressions ne méritent pas une alerte Slack à 3h du matin. Définissez des seuils :

Alerte critique (immédiate) : une page catégorie ou une landing page stratégique passe de INDEXED à NOT_INDEXED. Ou plus de 5 % des URLs inspectées changent de statut en 24h — signe d'un problème systémique (déploiement défectueux, robots.txt mal configuré, erreur SSR).

Alerte warning (digest quotidien) : divergence canonical détectée, page en DISCOVERED_CURRENTLY_NOT_INDEXED depuis plus de 14 jours, verdict mobile FAIL.

Ignorer : fluctuations sur des pages individuelles peu stratégiques, statut URL_NOT_ON_GOOGLE sur des pages publiées il y a moins de 72h, pages en soft-404 intentionnel.

La difficulté est de calibrer ces seuils pour votre site spécifique. Un site média avec 500 publications par jour aura un taux de changement naturellement élevé. Un site e-commerce stable ne devrait voir que quelques dizaines de changements par semaine.


L'URL Inspection API transforme le diagnostic d'indexation d'un exercice ponctuel et manuel en un système de monitoring continu. La contrainte des 2 000 requêtes/jour impose une approche stratégique — prioriser, échantillonner, historiser, comparer. Combinée à un crawl technique (Screaming Frog), aux logs serveur, et à un outil de détection de régressions comme Seogard, elle devient une brique essentielle d'un pipeline SEO technique mature.

Articles connexes

Crawl5 avril 2026

Crawl budget : mythe ou réalité pour votre site

Quand le crawl budget devient un vrai problème SEO, comment le diagnostiquer et l'optimiser. Guide technique avec cas concrets et configurations serveur.

Crawl5 avril 2026

Robots.txt : syntaxe, erreurs courantes et cas avancés

Guide technique complet du robots.txt : syntaxe, wildcards, erreurs de config fréquentes, cas d'usage avancés et stratégies de crawl budget.

Crawl5 avril 2026

Sitemap XML : bonnes pratiques techniques pour l'indexation

Génération, soumission et erreurs critiques à éviter sur vos sitemaps XML. Guide technique avec code, scénarios réels et config serveur.