URL メタデータ API: 1 回の呼び出しで Slack のようなリンク プレビューを構築
1 回の POST リクエストで、任意の URL から Open Graph タグ、Twitter Card データ、ファビコン、ページ タイトルを抽出します。 20 行未満のコードでリンク プレビュー カードを作成します。
ユーザーがチャット アプリに URL を貼り付けます。 ページ タイトルを含むリッチ プレビュー カードを表示したいとします。 説明とサムネイル画像。 同じカードの Slack、Discord、および iMessage が表示されます。 できますよ ページを取得し、HTML を解析し、Open Graph タグを自分で抽出します。 または、送信することもできます POSTリクエスト。
ボトイ /v1/url-metadata エンドポイントは任意の URL をフェッチし、その URL を読み取ります。
<meta> タグを取得し、構造化された JSON を返します: OG タイトル、OG 説明、OG 画像、
Twitter カード データ、ファビコン、正規 URL、言語など。 1 つの呼び出しでフェッチが置き換えられます。
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 オブジェクトには、Slack と Discord が読み取る Open Graph タグが含まれています。
の twitter オブジェクトには Twitter Card タグが含まれています。 ページで両方を設定すると、次のようになります。
両方。 ページでどちらも設定されていない場合でも、HTML が取得されます。 title そして
description フォールバックとして。
チャット アプリのリンク プレビュー コンポーネントを構築する
この Preact コンポーネントは URL を取得し、API を呼び出し、OG 画像、タイトル、 説明とサイト名。 OG タグが欠落している場合は、HTML タイトルに戻ります。
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<LinkPreview | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
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) => res.json())
.then(({ data }) => {
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(() => setPreview(null))
.finally(() => setLoading(false));
}, [url]);
return { preview, loading };
}
function LinkPreviewCard({ url }: { url: string }) {
const { preview, loading } = useLinkPreview(url);
if (loading) {
return (
<div class="rounded-lg border border-gray-200 p-4 animate-pulse">
<div class="h-4 bg-gray-100 rounded w-3/4 mb-2"></div>
<div class="h-3 bg-gray-100 rounded w-full"></div>
</div>
);
}
if (!preview) return null;
return (
<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"
>
{preview.image && (
<img
src={preview.image}
alt=""
class="w-full h-40 object-cover"
/>
)}
<div class="p-4">
<div class="flex items-center gap-2 mb-2">
{preview.favicon && (
<img src={preview.favicon} alt="" class="w-4 h-4" />
)}
<span class="text-xs text-gray-500">
{preview.siteName || new URL(preview.url).hostname}
</span>
</div>
<p class="font-semibold text-sm text-gray-900 mb-1">
{preview.title}
</p>
<p class="text-xs text-gray-600 line-clamp-2">
{preview.description}
</p>
</div>
</a>
);
}
の useLinkPreview フックはフェッチを処理し、API 応答をフラットにマップします。
UI が使用できるオブジェクト。 フォールバック チェーン (data.og?.title || data.title)
これは、ページに OG タグがまったくない場合でも、常に表示するものがあることを意味します。 コンポーネント
API 呼び出しの実行中に読み込み中のスケルトンをレンダリングし、プレビュー カードを入れ替えます。
CMS への SEO メタデータの自動入力
コンテンツ編集者は記事を書くときに参照 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 画像を入力し、編集者がそこから調整できるようにします。
同じアプローチは、ブックマーク マネージャー、後で読むアプリ、内部 Wiki ツールにも機能します。
貼り付けたリンクからカードを自動生成します。
タイムアウトとエラー処理を備えた Node.js 関数
運用環境では、遅いターゲット ページによってリクエストが無期限にブロックされないように、タイムアウトが必要です。
この関数は API 呼び出しを 5 秒でラップします。 AbortController タイムアウトと
返品 null 投げる代わりに失敗したときに。
async function getLinkPreview(url: string) {
const controller = new AbortController();
const timeout = setTimeout(() => 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);
}
}
この関数は、UI に必要なフィールドを含むクリーンなオブジェクトを返します。 発信者は生には触れない
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) => getLinkPreview(u))
);
const results = previews.map((p, i) => ({
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-metadataOG タグ、Twitter カード タグ、ファビコン、正規を返します 1 つの JSON 応答内の URL、言語、およびキーワード。 - 応答は、Slack、Discord、iMessage がリンク プレビューをレンダリングするために使用するデータを反映しています。 あなた HTML パーサーを作成せずに同じフィールドを取得します。
- 匿名アクセスは、API キーなしで 1 分あたり 5 リクエストで機能します。 開発には十分 トラフィックの少ないアプリ。
-
フォールバック
titleそしてdescriptionOG タグが欠落している場合。 API は常に HTML からこれらを返します。<head>それらが存在するとき。 -
運用環境で使用する場合は、タイムアウトを追加し、URL によって結果をキャッシュし、処理します。
null優雅に答えます。 チェックしてください APIドキュメント 完全なパラメータ参照については。
FAQ
- URL メタデータ API とは何ですか?
- URL メタデータ API は Web ページを取得し、その 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 ステータス コードが含まれるため、リダイレクト チェーンを検出できます。
- URL メタデータ API は無料ですか?
- 匿名アクセスには API キーは必要なく、1 分あたり 5 件のリクエストに加え、1 日あたり 100 件のリクエストが可能です。 これは、開発と低トラフィックのユースケースをカバーします。 有料プランは月額 9 ドルから始まり、レート制限が高くなります。
botoiで開発を始めよう
150以上のAPIエンドポイント。検索、テキスト処理、画像生成、開発者ユーティリティに対応。無料プラン、クレジットカード不要。