Aller au contenu
Tutorial

API de métadonnées d'URL : créez des aperçus de liens comme Slack en un seul appel

| 6 min read

Extrayez les balises Open Graph, les données de la carte Twitter, les favicons et les titres de page de n'importe quelle URL avec une seule requête POST. Créez des cartes d'aperçu de liens en moins de 20 lignes de code.

Social media cards displayed on a phone screen
Photo by Rami Al-zayat on Unsplash

Un utilisateur colle une URL dans votre application de chat. Vous souhaitez afficher une carte d'aperçu enrichi avec le titre de la page, description et image miniature ; la même carte Slack, Discord et iMessage s'affiche. Tu pourrais récupérez la page, analysez le HTML et extrayez vous-même les balises Open Graph. Ou tu pourrais en envoyer un Requête POST.

Le botoï /v1/url-metadata le point de terminaison récupère n'importe quelle URL, lit son <meta> balises et renvoie du JSON structuré : titre OG, description OG, image OG, Données de la carte Twitter, favicon, URL canonique, langue, etc. Un appel remplace la récupération, le Analyseur HTML et logique de secours.

Le point final

curl -X POST https://api.botoi.com/v1/url-metadata \\
  -H "Content-Type: application/json" \\
  -d '{ "url": "https://github.com/anthropics/claude-code" }'

Réponse:

{
  "success": true,
  "data": {
    "url": "https://github.com/anthropics/claude-code",
    "status": 200,
    "content_type": "text/html",
    "title": "anthropics/claude-code: Claude Code is an agentic coding tool",
    "description": "Claude Code is an agentic coding tool that lives in your terminal",
    "og": {
      "title": "anthropics/claude-code",
      "description": "Claude Code is an agentic coding tool that lives in your terminal",
      "image": "https://opengraph.githubassets.com/1/anthropics/claude-code",
      "type": "object",
      "url": "https://github.com/anthropics/claude-code",
      "site_name": "GitHub"
    },
    "twitter": {
      "card": "summary_large_image",
      "title": "anthropics/claude-code",
      "description": "Claude Code is an agentic coding tool that lives in your terminal",
      "image": null
    },
    "favicon": "https://github.com/favicon.ico",
    "canonical": "https://github.com/anthropics/claude-code",
    "language": "en",
    "author": null,
    "keywords": [],
    "theme_color": null
  }
}

La réponse vous donne tout ce dont vous avez besoin pour afficher une carte d'aperçu de lien. Le og L’objet contient les balises Open Graph lues par Slack et Discord. Le twitter L'objet contient les balises de la carte Twitter. Lorsqu'une page définit les deux, vous obtenez les deux. Lorsqu'une page ne définit ni l'un ni l'autre, vous obtenez toujours le HTML title et description comme solutions de repli.

Créer un composant d'aperçu du lien d'application de chat

Ce composant Preact prend une URL, appelle l'API et restitue une carte avec l'image OG, le titre, description et nom du site. Il revient au titre HTML lorsque les balises OG sont manquantes.

import { useState, useEffect } from "preact/hooks";

interface LinkPreview {
  title: string | null;
  description: string | null;
  image: string | null;
  favicon: string | null;
  url: string;
  siteName: string | null;
}

function useLinkPreview(url: string) {
  const [preview, setPreview] = useState&lt;LinkPreview | null&gt;(null);
  const [loading, setLoading] = useState(false);

  useEffect(() =&gt; {
    if (!url) return;
    setLoading(true);

    fetch("https://api.botoi.com/v1/url-metadata", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ url }),
    })
      .then((res) =&gt; res.json())
      .then(({ data }) =&gt; {
        setPreview({
          title: data.og?.title || data.title,
          description: data.og?.description || data.description,
          image: data.og?.image || null,
          favicon: data.favicon,
          url: data.canonical || url,
          siteName: data.og?.site_name || null,
        });
      })
      .catch(() =&gt; setPreview(null))
      .finally(() =&gt; setLoading(false));
  }, [url]);

  return { preview, loading };
}

function LinkPreviewCard({ url }: { url: string }) {
  const { preview, loading } = useLinkPreview(url);

  if (loading) {
    return (
      &lt;div class="rounded-lg border border-gray-200 p-4 animate-pulse"&gt;
        &lt;div class="h-4 bg-gray-100 rounded w-3/4 mb-2"&gt;&lt;/div&gt;
        &lt;div class="h-3 bg-gray-100 rounded w-full"&gt;&lt;/div&gt;
      &lt;/div&gt;
    );
  }

  if (!preview) return null;

  return (
    &lt;a
      href={preview.url}
      target="_blank"
      rel="noopener noreferrer"
      class="block rounded-lg border border-gray-200 overflow-hidden
             hover:border-gray-400 transition-colors no-underline"
    &gt;
      {preview.image &amp;&amp; (
        &lt;img
          src={preview.image}
          alt=""
          class="w-full h-40 object-cover"
        /&gt;
      )}
      &lt;div class="p-4"&gt;
        &lt;div class="flex items-center gap-2 mb-2"&gt;
          {preview.favicon &amp;&amp; (
            &lt;img src={preview.favicon} alt="" class="w-4 h-4" /&gt;
          )}
          &lt;span class="text-xs text-gray-500"&gt;
            {preview.siteName || new URL(preview.url).hostname}
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class="font-semibold text-sm text-gray-900 mb-1"&gt;
          {preview.title}
        &lt;/p&gt;
        &lt;p class="text-xs text-gray-600 line-clamp-2"&gt;
          {preview.description}
        &lt;/p&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  );
}

La useLinkPreview le hook gère la récupération et mappe la réponse de l'API à un plat objet que votre interface utilisateur peut consommer. La chaîne de repli (data.og?.title || data.title) signifie que vous avez toujours quelque chose à afficher, même lorsqu'une page n'a aucune balise OG. Le composant restitue un squelette de chargement pendant que l'appel API est en cours, puis échange la carte d'aperçu.

Remplissage automatique des métadonnées SEO dans un CMS

Les éditeurs de contenu collent les URL de référence lors de la rédaction d'articles. Au lieu de les faire copier-coller le titre et la description à la main, votre CMS peut extraire ces données de l'URL et pré-remplir le Champs de référencement.

async function autoFillSeoFields(referenceUrl: string) {
  const res = await fetch("https://api.botoi.com/v1/url-metadata", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: \`Bearer \${process.env.BOTOI_API_KEY}\`,
    },
    body: JSON.stringify({ url: referenceUrl }),
  });

  const { data } = await res.json();

  return {
    seoTitle: data.og?.title || data.title || "",
    seoDescription: data.og?.description || data.description || "",
    ogImage: data.og?.image || "",
    canonical: data.canonical || referenceUrl,
    favicon: data.favicon || "",
  };
}

// Usage in a CMS admin panel
const fields = await autoFillSeoFields("https://stripe.com/docs/payments");
// fields.seoTitle       → "Payments | Stripe Documentation"
// fields.seoDescription → "Accept payments online..."
// fields.ogImage        → "https://images.stripe.com/..."

Lorsqu'un éditeur colle une URL dans le champ "référence", le CMS appelle autoFillSeoFields, remplit le titre SEO, la description et les entrées d'image OG, et permet à l'éditeur d'ajuster à partir de là. La même approche fonctionne pour les gestionnaires de favoris, les applications à lecture ultérieure et les outils wiki internes qui générer automatiquement des cartes à partir de liens collés.

Fonction Node.js avec délai d'attente et gestion des erreurs

En production, vous souhaitez un délai d'attente afin qu'une page cible lente ne bloque pas votre demande indéfiniment. Cette fonction encapsule l'appel API avec un délai de 5 secondes AbortController délai d'attente et retours null sur l'échec au lieu de lancer.

async function getLinkPreview(url: string) {
  const controller = new AbortController();
  const timeout = setTimeout(() =&gt; controller.abort(), 5000);

  try {
    const res = await fetch("https://api.botoi.com/v1/url-metadata", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: \`Bearer \${process.env.BOTOI_API_KEY}\`,
      },
      body: JSON.stringify({ url }),
      signal: controller.signal,
    });

    if (!res.ok) {
      throw new Error(\`API returned \${res.status}\`);
    }

    const { success, data } = await res.json();

    if (!success) {
      return null;
    }

    return {
      title: data.og?.title || data.title,
      description: data.og?.description || data.description,
      image: data.og?.image,
      favicon: data.favicon,
      siteName: data.og?.site_name,
      canonical: data.canonical,
      twitterCard: data.twitter?.card,
    };
  } catch (err) {
    console.error(\`Failed to fetch preview for \${url}:\`, err);
    return null;
  } finally {
    clearTimeout(timeout);
  }
}

La fonction renvoie un objet propre avec les champs dont votre interface utilisateur a besoin. Les appelants ne touchent pas au brut Réponse API. Si la page cible est en panne ou si la demande expire, la fonction renvoie null et votre application peut afficher une solution de secours au lieu de planter.

Gestion des cas extrêmes

Toutes les URL ne coopèrent pas. Certaines pages n'ont pas de balises OG. Certains sont derrière des chaînes de redirection. Certains prenez 10 secondes pour répondre. Voici comment gérer chaque cas.

// 1. Pages with no OG tags: fall back to title + description
const preview = await getLinkPreview(url);
const displayTitle = preview?.title || "Untitled page";
const displayDesc = preview?.description || url;
const displayImage = preview?.image || "/fallback-thumbnail.png";

// 2. Detect redirects by comparing input URL to canonical
const inputUrl = "https://bit.ly/3xYzAbc";
const result = await getLinkPreview(inputUrl);
if (result?.canonical !== inputUrl) {
  console.log(\`Redirected to: \${result?.canonical}\`);
}

// 3. Batch multiple URLs with Promise.allSettled
const urls = [
  "https://github.com",
  "https://stripe.com",
  "https://vercel.com",
];

const previews = await Promise.allSettled(
  urls.map((u) =&gt; getLinkPreview(u))
);

const results = previews.map((p, i) =&gt; ({
  url: urls[i],
  preview: p.status === "fulfilled" ? p.value : null,
}));

Pas de balises AND : Revenir à title et description. Si ceux-ci sont également vides, affichent l'URL brute. Afficher une image d'espace réservé lorsque og.image est nul.

Redirections : L'API suit les redirections et renvoie les métadonnées de la page finale. Comparez l'URL d'entrée avec canonical pour détecter quand une redirection s'est produite.

Pages lentes : Définissez un délai d'attente de votre côté (5 secondes fonctionnent dans la plupart des cas). Le L'API elle-même a un délai d'attente interne, mais vous devez appliquer le vôtre afin qu'une cible lente ne le fasse pas. bloquer l'expérience de votre utilisateur.

Récupération par lots : Utiliser Promise.allSettled pour récupérer des aperçus pour plusieurs URL en parallèle. Retour des demandes ayant échoué null sans bloquer le reste.

Points clés

  • POST /v1/url-metadata renvoie les balises OG, les balises de la carte Twitter, les favicon, les canoniques URL, langue et mots-clés dans une seule réponse JSON.
  • La réponse reflète les données utilisées par Slack, Discord et iMessage pour afficher les aperçus des liens. Vous obtenez les mêmes champs sans écrire d'analyseur HTML.
  • L'accès anonyme fonctionne à 5 requêtes par minute sans clé API. Assez pour le développement et applications à faible trafic.
  • Revenir à title et description lorsque les balises OG sont manquantes. L'API les renvoie toujours à partir du HTML <head> quand ils existent.
  • Pour une utilisation en production, ajoutez un délai d'attente, mettez en cache les résultats par URL et gérez null réponses avec grâce. Vérifiez le Documents sur l'API pour la référence complète des paramètres.

FAQ

Qu'est-ce qu'une API de métadonnées d'URL ?
Une API de métadonnées d'URL récupère une page Web et extrait les données structurées de son code HTML : le titre de la page, la méta description, les balises Open Graph (og:title, og:image, og:description), les balises de la carte Twitter, l'URL du favicon, l'URL canonique et la langue. Vous envoyez une URL et l'API renvoie tout cela au format JSON. Cela vous évite de récupérer la page vous-même et d'analyser le HTML brut.
Comment Slack, Discord et iMessage génèrent-ils des aperçus de liens ?
Lorsqu'un utilisateur colle une URL, ces applications récupèrent la page en arrière-plan et lisent ses balises méta Open Graph (og:title, og:description, og:image). Ils restituent une carte d'aperçu à partir de ces valeurs. Si les balises OG sont manquantes, elles reviennent au titre HTML et à la méta description. Le point de terminaison botoi /v1/url-metadata renvoie les mêmes données lues par ces applications, afin que vous puissiez créer des cartes d'aperçu identiques dans votre propre application.
Que se passe-t-il si une page n'a pas de balises Open Graph ?
L'API renvoie toujours le titre HTML, la méta description, le favicon, l'URL canonique et la langue. Les champs og dans la réponse seront nuls. Votre interface doit revenir au titre et à la description lorsque og.title et og.image sont manquants.
L'API suit-elle les redirections ?
Oui. L'API suit les redirections HTTP 301/302/307/308 et renvoie les métadonnées de l'URL de destination finale. La réponse inclut l'URL résolue et son code d'état HTTP, afin que vous puissiez détecter les chaînes de redirection.
L'API de métadonnées d'URL est-elle gratuite ?
L'accès anonyme ne nécessite aucune clé API et autorise 5 requêtes par minute plus 100 par jour. Cela couvre les cas de développement et d’utilisation à faible trafic. Les forfaits payants commencent à 9 $/mois pour des limites tarifaires plus élevées.

Commencez a construire avec botoi

150+ endpoints API pour la recherche, le traitement de texte, la generation d'images et les utilitaires pour developpeurs. Offre gratuite, sans carte bancaire.