Перейти к содержимому
Tutorial

API метаданных URL: создавайте предварительный просмотр ссылок, как в Slack, за один вызов

| 6 min read

Извлекайте теги Open Graph, данные Twitter Card, значки и заголовки страниц из любого URL-адреса с помощью одного запроса POST. Создавайте карточки предварительного просмотра ссылок, используя менее 20 строк кода.

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

Пользователь вставляет URL-адрес в ваше приложение чата. Вы хотите показать расширенную карточку предварительного просмотра с заголовком страницы, описание и миниатюрное изображение; та же карта отображается в Slack, Discord и iMessage. Ты мог бы получите страницу, проанализируйте HTML и извлеките теги Open Graph самостоятельно. Или вы можете отправить один POST-запрос.

Ботой /v1/url-metadata конечная точка извлекает любой URL-адрес, читает его <meta> теги и возвращает структурированный JSON: заголовок OG, описание OG, изображение OG, Данные Twitter Card, значок, канонический URL-адрес, язык и многое другое. Один вызов заменяет выборку, HTML-парсер и резервная логика.

Конечная точка

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

Ответ:

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

Ответ дает вам все необходимое для отображения карточки предварительного просмотра ссылки. og Объект содержит теги Open Graph, которые считывают Slack и Discord. twitter объект содержит теги Twitter Card. Когда на странице заданы оба параметра, вы получаете оба. Если на странице не установлено ни того, ни другого, вы все равно получите HTML-код. title и description в качестве запасного варианта.

Создайте компонент предварительного просмотра ссылки на приложение чата.

Этот компонент Preact принимает URL-адрес, вызывает API и отображает карточку с изображением OG, заголовком и описание и название сайта. Он возвращается к заголовку HTML, когда теги 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;
  );
}

The useLinkPreview Хук обрабатывает выборку и отображает ответ API в плоский объект, который может использовать ваш пользовательский интерфейс. Резервная цепочка (data.og?.title || data.title) означает, что вам всегда есть что отобразить, даже если на странице нет тегов OG. Компонент отображает скелет загрузки во время вызова API, а затем заменяет карту предварительного просмотра.

Автоматическое заполнение метаданных SEO в CMS

Редакторы контента вставляют ссылочные URL-адреса при написании статей. Вместо того, чтобы копипастить их заголовок и описание вручную, ваша CMS может извлечь эти данные из URL-адреса и предварительно заполнить 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/..."

Когда редактор вставляет URL-адрес в поле «Ссылка», CMS вызывает autoFillSeoFields, заполняет входные данные SEO-заголовка, описания и изображения OG и позволяет редактору настраивать их оттуда. Тот же подход работает для менеджеров закладок, приложений для последующего чтения и внутренних вики-инструментов, которые автоматически генерировать карточки из вставленных ссылок.

Функция Node.js с таймаутом и обработкой ошибок

В рабочей среде вам нужен тайм-аут, чтобы медленная целевая страница не блокировала ваш запрос на неопределенный срок. Эта функция оборачивает вызов API 5-секундным интервалом. AbortController тайм-аут и возвращает null при неудаче вместо броска.

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

Функция возвращает чистый объект с полями, необходимыми вашему пользовательскому интерфейсу. Вызывающие абоненты не прикасаются к сырому Ответ API. Если целевая страница не работает или время запроса истекло, функция возвращает null и ваше приложение может отображать резервный вариант вместо сбоя.

Обработка пограничных случаев

Не каждый URL-адрес сотрудничает. На некоторых страницах нет тегов OG. Некоторые из них находятся за цепочками перенаправления. Некоторые ответьте 10 секунд. Вот как действовать в каждом случае.

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

Нет тегов AND: Вернуться к title и description. Если они также пусты, отобразите необработанный URL-адрес. Показывать изображение-заполнитель, когда og.image является нулевым.

Перенаправления: API следует за перенаправлениями и возвращает метаданные с конечной страницы. Сравните входной URL с canonical чтобы определить, когда произошло перенаправление.

Медленные страницы: Установите тайм-аут на своей стороне (в большинстве случаев подойдет 5 секунд). Сам API имеет внутренний тайм-аут, но вам следует использовать свой собственный, чтобы медленная цель не остановить ваш пользовательский опыт.

Пакетная загрузка: Использовать Promise.allSettled чтобы получить превью для несколько URL-адресов параллельно. Неудачные запросы возвращаются null не блокируя остальных.

Ключевые моменты

  • POST /v1/url-metadata возвращает теги OG, теги Twitter Card, favicon, canonical URL-адрес, язык и ключевые слова в одном ответе JSON.
  • Ответ отражает данные, которые Slack, Discord и iMessage используют для отображения предварительного просмотра ссылок. ты получить те же поля без написания парсера HTML.
  • Анонимный доступ работает со скоростью 5 запросов в минуту без API-ключа. Достаточно для развития и приложения с низким трафиком.
  • Вернуться к title и description когда теги OG отсутствуют. API всегда возвращает их из HTML. <head> когда они существуют.
  • Для производственного использования добавьте тайм-аут, кэшируйте результаты по URL-адресу и обработайте null отвечает изящно. Проверьте Документация по API для полной справки по параметрам.

FAQ

Что такое API метаданных URL-адреса?
API метаданных URL-адреса извлекает веб-страницу и извлекает структурированные данные из ее HTML: заголовок страницы, метаописание, теги Open Graph (og:title, og:image, og:description), теги Twitter Card, URL-адрес значка, канонический URL-адрес и язык. Вы отправляете URL-адрес, и API возвращает все это в формате JSON. Это избавляет вас от необходимости самостоятельно получать страницу и анализировать необработанный HTML.
Как Slack, Discord и iMessage создают предварительный просмотр ссылок?
Когда пользователь вставляет URL-адрес, эти приложения получают страницу в фоновом режиме и считывают ее метатеги Open Graph (og:title, og:description, og:image). Они отображают карту предварительного просмотра на основе этих значений. Если теги OG отсутствуют, они возвращаются к заголовку HTML и мета-описанию. Конечная точка botoi /v1/url-metadata возвращает те же данные, которые считывают эти приложения, поэтому вы можете создавать идентичные карточки предварительного просмотра в своем собственном приложении.
Что произойдет, если на странице нет тегов Open Graph?
API по-прежнему возвращает заголовок HTML, метаописание, значок, канонический URL-адрес и язык. Поля og в ответе будут иметь значение null. Ваш интерфейс должен вернуться к заголовку и описанию, когда og.title и og.image отсутствуют.
Соблюдает ли API перенаправления?
Да. API следует перенаправлению HTTP 301/302/307/308 и возвращает метаданные из конечного целевого URL-адреса. Ответ включает разрешенный URL-адрес и его код состояния HTTP, поэтому вы можете обнаружить цепочки перенаправлений.
Является ли API метаданных URL бесплатным?
Анонимный доступ не требует ключа API и допускает 5 запросов в минуту плюс 100 в день. Это охватывает случаи разработки и использования с низким трафиком. Платные планы начинаются с 9 долларов в месяц для более высоких лимитов.

Начните разработку с botoi

150+ API-эндпоинтов для поиска, обработки текста, генерации изображений и утилит для разработчиков. Бесплатный тариф, без банковской карты.