Ir al contenido
Tutorial

API de metadatos de URL: cree vistas previas de enlaces como Slack en una sola llamada

| 6 min read

Extraiga etiquetas Open Graph, datos de tarjetas de Twitter, favicons y títulos de páginas de cualquier URL con una solicitud POST. Cree tarjetas de vista previa de enlaces en menos de 20 líneas de código.

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

Un usuario pega una URL en su aplicación de chat. Quiere mostrar una tarjeta de vista previa enriquecida con el título de la página, descripción e imagen en miniatura; la misma tarjeta que se muestra en Slack, Discord e iMessage. tu podrías busque la página, analice el HTML y extraiga las etiquetas de Open Graph usted mismo. O podrías enviar uno Solicitud de publicación.

La botoi /v1/url-metadata El punto final recupera cualquier URL, lee su <meta> etiquetas y devuelve JSON estructurado: título de OG, descripción de OG, imagen de OG, Datos de la tarjeta de Twitter, favicon, URL canónica, idioma y más. Una llamada reemplaza la búsqueda, la Analizador HTML y lógica alternativa.

El punto final

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

Respuesta:

{
  "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 respuesta le brinda todo lo que necesita para generar una tarjeta de vista previa de enlace. El og El objeto contiene las etiquetas Open Graph que leen Slack y Discord. El twitter El objeto contiene las etiquetas de la tarjeta de Twitter. Cuando una página establece ambos, obtienes ambos. Cuando una página no establece ninguna de las dos opciones, aún obtienes el HTML. title y description como alternativas.

Cree un componente de vista previa de enlaces de aplicaciones de chat

Este componente de Preact toma una URL, llama a la API y genera una tarjeta con la imagen OG, el título y descripción y nombre del sitio. Vuelve al título HTML cuando faltan etiquetas OG.

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 El gancho maneja la búsqueda y asigna la respuesta de la API a un plano. objeto que su interfaz de usuario puede consumir. La cadena alternativa (data.og?.title || data.title) significa que siempre tienes algo que mostrar, incluso cuando una página no tiene etiquetas OG. el componente representa un esqueleto de carga mientras la llamada API está en curso, luego intercambia la tarjeta de vista previa.

Autocompletar metadatos de SEO en un CMS

Los editores de contenido pegan URL de referencia al escribir artículos. En lugar de hacerlos copiar y pegar el título y la descripción a mano, su CMS puede extraer esos datos de la URL y completar previamente el Campos de SEO.

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/..."

Cuando un editor pega una URL en el campo "referencia", el CMS llama autoFillSeoFields, completa el título SEO, la descripción y las entradas de imagen OG, y permite que el editor modifique desde allí. El mismo enfoque funciona para administradores de marcadores, aplicaciones de lectura posterior y herramientas wiki internas que generar automáticamente tarjetas a partir de enlaces pegados.

Función Node.js con tiempo de espera y manejo de errores

En producción, desea un tiempo de espera para que una página de destino lenta no bloquee su solicitud indefinidamente. Esta función envuelve la llamada API con un período de 5 segundos. AbortController tiempo de espera y regresa null en el fracaso en lugar de tirar.

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 función devuelve un objeto limpio con los campos que su interfaz de usuario necesita. Las personas que llaman no tocan lo crudo. Respuesta API. Si la página de destino está inactiva o la solicitud se agota, la función devuelve null y su aplicación puede mostrar un respaldo en lugar de fallar.

Manejo de casos extremos

No todas las URL cooperan. Algunas páginas no tienen etiquetas OG. Algunos están detrás de cadenas de redireccionamiento. algunos Tómese 10 segundos para responder. A continuación se explica cómo manejar cada caso.

// 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,
}));

Sin etiquetas AND: retroceder a title y description. si esos también están vacíos, muestra la URL sin formato. Mostrar una imagen de marcador de posición cuando og.image Es nula

Redirecciones: La API sigue las redirecciones y devuelve metadatos de la página final. Compare la URL de entrada con canonical para detectar cuándo ocurrió una redirección.

Páginas lentas: Establezca un tiempo de espera de su lado (5 segundos funcionan en la mayoría de los casos). el La API en sí tiene un tiempo de espera interno, pero debes aplicar el tuyo propio para que un objetivo lento no lo haga. detener la experiencia de su usuario.

Obtención por lotes: Usar Promise.allSettled para obtener vistas previas de varias URL en paralelo. Devolución de solicitudes fallidas null sin bloquear el resto.

Puntos clave

  • POST /v1/url-metadata devuelve etiquetas OG, etiquetas de tarjetas de Twitter, favicon, canónico URL, idioma y palabras clave en una respuesta JSON.
  • La respuesta refleja los datos que utilizan Slack, Discord e iMessage para representar vistas previas de enlaces. tu obtenga los mismos campos sin escribir un analizador HTML.
  • El acceso anónimo funciona a 5 solicitudes por minuto sin clave API. Suficiente para el desarrollo y aplicaciones de poco tráfico.
  • retroceder a title y description cuando faltan etiquetas OG. La API siempre los devuelve desde el HTML. <head> cuando existen.
  • Para uso en producción, agregue un tiempo de espera, almacene en caché los resultados por URL y maneje null responde con gracia. Compruebe el Documentos API para obtener la referencia completa de los parámetros.

FAQ

¿Qué es una API de metadatos de URL?
Una API de metadatos de URL recupera una página web y extrae datos estructurados de su HTML: el título de la página, la meta descripción, las etiquetas Open Graph (og:title, og:image, og:description), las etiquetas de Twitter Card, la URL de favicon, la URL canónica y el idioma. Envías una URL y la API devuelve todo esto como JSON. Le evita buscar la página usted mismo y analizar HTML sin formato.
¿Cómo generan Slack, Discord e iMessage vistas previas de enlaces?
Cuando un usuario pega una URL, estas aplicaciones recuperan la página en segundo plano y leen sus metaetiquetas Open Graph (og:title, og:description, og:image). Representan una tarjeta de vista previa a partir de esos valores. Si faltan etiquetas OG, recurren al título HTML y la meta descripción. El punto final botoi /v1/url-metadata devuelve los mismos datos que leen estas aplicaciones, por lo que puede crear tarjetas de vista previa idénticas en su propia aplicación.
¿Qué sucede si una página no tiene etiquetas Open Graph?
La API aún devuelve el título HTML, la meta descripción, el favicon, la URL canónica y el idioma. Los campos og en la respuesta serán nulos. Su interfaz debe volver al título y la descripción cuando faltan og.title y og.image.
¿La API sigue las redirecciones?
Sí. La API sigue las redirecciones HTTP 301/302/307/308 y devuelve metadatos de la URL de destino final. La respuesta incluye la URL resuelta y su código de estado HTTP, para que puedas detectar cadenas de redireccionamiento.
¿La API de metadatos de URL es gratuita?
El acceso anónimo no requiere clave API y permite 5 solicitudes por minuto más 100 por día. Esto cubre casos de uso de desarrollo y de bajo tráfico. Los planes pagos comienzan en $9/mes para límites de tarifas más altos.

Empieza a construir con botoi

150+ endpoints de API para consultas, procesamiento de texto, generacion de imagenes y utilidades para desarrolladores. Plan gratuito, sin tarjeta de credito.