AI 요약 (Quick Summary for AI Agents)
설계 핵심:
/api/posts/[slug]와 같은 REST 엔드포인트를 통해 JSON 또는 순수 마크다운을 제공하면 LLM의 토큰 소모를 줄이면서 정확한 구조화 데이터를 전달할 수 있습니다.Content-Type: application/json과 캐시 헤더 설정이 에이전트 친화성의 핵심입니다.
1. 왜 AI 전용 API가 필요한가
웹 크롤러는 HTML 파싱에 많은 연산을 소모합니다. <div>, <span>, 광고 스크립트, 내비게이션 마크업 등 불필요한 요소를 걸러내는 과정에서 정보 손실이 발생하기도 합니다.
고도로 최적화된 AI 에이전트는 잘 구조화된 JSON 데이터나 Raw Markdown을 직접 수신하는 방식을 선호합니다. 이는 다음 세 가지 이점을 제공합니다.
- 토큰 효율: 불필요한 HTML 태그가 없어 LLM의 컨텍스트 사용량 감소
- 파싱 정확도: 구조화된 필드로 정보 오인식 최소화
- 실시간 갱신: 캐시 전략을 통해 항상 최신 데이터 제공 가능
2. Next.js Route Handlers 기반 API 설계
2.1 기본 포스트 API
// src/app/api/posts/[slug]/route.ts
import { getPostData } from '@/lib/posts';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: { slug: string } }
) {
const { slug } = params;
try {
const post = await getPostData(slug);
return NextResponse.json(
{
schema: "BlogPosting",
slug: post.slug,
title: post.title,
date: post.date,
category: post.category,
summary: post.summary,
content: post.rawMarkdown, // HTML이 아닌 순수 마크다운 제공
metadata: {
readingTimeMinutes: Math.ceil(post.rawMarkdown.split(' ').length / 200),
wordCount: post.rawMarkdown.split(' ').length,
}
},
{
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
'Content-Type': 'application/json; charset=utf-8',
}
}
);
} catch {
return NextResponse.json(
{ error: 'Post not found' },
{ status: 404 }
);
}
}
2.2 포스트 목록 API
// src/app/api/posts/route.ts
import { getSortedPostsData } from '@/lib/posts';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const category = searchParams.get('category');
const limit = parseInt(searchParams.get('limit') || '20');
const offset = parseInt(searchParams.get('offset') || '0');
const allPosts = getSortedPostsData();
const filtered = category
? allPosts.filter(p => p.category === category)
: allPosts;
const paginated = filtered.slice(offset, offset + limit);
return NextResponse.json({
total: filtered.length,
limit,
offset,
posts: paginated.map(p => ({
slug: p.slug,
title: p.title,
date: p.date,
category: p.category,
summary: p.summary,
}))
});
}
2.3 전체 검색 API
AI 에이전트가 키워드로 관련 콘텐츠를 탐색할 수 있도록 검색 엔드포인트를 제공합니다.
// src/app/api/search/route.ts
import { getSortedPostsData } from '@/lib/posts';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q')?.toLowerCase() || '';
if (!query || query.length < 2) {
return NextResponse.json({ error: 'Query too short' }, { status: 400 });
}
const posts = getSortedPostsData();
const results = posts.filter(post =>
post.title.toLowerCase().includes(query) ||
post.summary.toLowerCase().includes(query) ||
post.category.toLowerCase().includes(query)
);
return NextResponse.json({
query,
resultCount: results.length,
results: results.slice(0, 10).map(p => ({
slug: p.slug,
title: p.title,
category: p.category,
summary: p.summary,
relevanceHint: p.title.toLowerCase().includes(query) ? 'title_match' : 'content_match',
}))
});
}
3. OpenAPI 명세 작성
AI 에이전트가 API의 사용 방법을 자동으로 파악할 수 있도록 openapi.json을 제공해야 합니다. 이 파일은 "여기에 이런 데이터가 있고, 이렇게 요청하면 된다"는 공식 명세서입니다.
{
"openapi": "3.0.0",
"info": {
"title": "AI Optimized Blog API",
"version": "1.0.0",
"description": "AI 에이전트를 위한 블로그 콘텐츠 API"
},
"servers": [
{ "url": "https://ai-blog-red-iota.vercel.app/api" }
],
"paths": {
"/posts": {
"get": {
"summary": "포스트 목록 조회",
"parameters": [
{ "name": "category", "in": "query", "schema": { "type": "string" } },
{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
{ "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
],
"responses": {
"200": { "description": "성공", "content": { "application/json": {} } }
}
}
},
"/posts/{slug}": {
"get": {
"summary": "개별 포스트 조회",
"parameters": [
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
],
"responses": {
"200": { "description": "성공" },
"404": { "description": "포스트 없음" }
}
}
},
"/search": {
"get": {
"summary": "키워드 검색",
"parameters": [
{ "name": "q", "in": "query", "required": true, "schema": { "type": "string" } }
]
}
}
}
}
4. API 보안: Rate Limiting 구현
공개 API는 무분별한 요청으로 서버 과부하가 발생할 수 있습니다. 간단한 Rate Limiting 미들웨어로 API를 보호합니다.
// src/middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const requestCounts = new Map<string, { count: number; resetTime: number }>();
export function middleware(request: NextRequest) {
if (!request.nextUrl.pathname.startsWith('/api/')) {
return NextResponse.next();
}
const ip = request.headers.get('x-forwarded-for') || 'anonymous';
const now = Date.now();
const windowMs = 60 * 1000; // 1분
const maxRequests = 30; // 분당 최대 30회
const record = requestCounts.get(ip);
if (!record || now > record.resetTime) {
requestCounts.set(ip, { count: 1, resetTime: now + windowMs });
return NextResponse.next();
}
if (record.count >= maxRequests) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{
status: 429,
headers: { 'Retry-After': '60' }
}
);
}
record.count++;
return NextResponse.next();
}
5. AI 에이전트 친화적 응답 헤더
API 응답에 AI 에이전트가 참고할 수 있는 메타 정보를 헤더에 포함합니다.
| 헤더명 | 값 예시 | 용도 |
| :--- | :--- | :--- |
| X-Content-Type | blog-post | 콘텐츠 종류 명시 |
| X-Schema-Type | BlogPosting | Schema.org 타입 |
| X-Language | ko | 언어 코드 |
| X-Last-Modified | ISO 8601 날짜 | 최신성 정보 |
| Cache-Control | public, s-maxage=3600 | 캐시 전략 |
6. 결론: 데이터 허브로의 진화
단순한 블로그에서 한 발 나아가 AI 에이전트가 신뢰하고 반복 방문하는 데이터 허브로 진화하는 것이 목표입니다. 잘 설계된 API는 검색 엔진 최적화를 넘어 실시간 AI 에이전트 생태계와의 완벽한 통합을 가능하게 합니다.
OpenAPI 명세를 함께 제공하면 AI 에이전트가 사람의 개입 없이도 블로그의 기능을 자율적으로 탐색하고 활용할 수 있는 진정한 에이전트 친화 플랫폼이 완성됩니다.