API Documentation
Learn how to integrate Blogs for Vercel into your Next.js application.
Quick Start
Using the NPM Package (Recommended)
The easiest way to get started is with our official NPM package, which provides React hooks, Server Components utilities, and seamless Next.js ISR support.
npm install @lztek/blogs-for-vercel-sdkThe SDK includes React hooks, Server Component utilities, ISR support, and full TypeScript types.
Using the REST API
You can also use our REST API directly. All requests require authentication via API key.
curl https://blogsforvercel.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY"Authentication
All API requests require authentication using an API key. You can create and manage API keys in your dashboard.
Include your API key in the Authorization header:
Authorization: Bearer bfv_your_api_key_hereAPI Endpoints
/api/v1/postsRetrieve all published blog posts for your organization. Posts are returned in reverse chronological order (newest first).
Query Parameters
tags(optional) - Comma-separated list of tags to filter bytag_logic(optional) - Filter logic:and(all tags must match) oror(any tag matches, default)search(optional) - Search query to filter posts by title or contentlimit(optional) - Maximum number of posts to return (default: 100, max: 100)offset(optional) - Number of posts to skip for pagination (default: 0)
Example Request
curl "https://blogsforvercel.com/api/v1/posts?tags=javascript,react" \
-H "Authorization: Bearer YOUR_API_KEY"Example Response
{
"posts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Getting Started with Next.js",
"slug": "getting-started-with-nextjs",
"content_markdown": "# Getting Started\n\nThis is a blog post...",
"content_html": "<h1>Getting Started</h1>\n<p>This is a blog post...</p>",
"tags": ["nextjs", "react"],
"published_at": "2024-01-15T10:00:00Z",
"created_at": "2024-01-15T09:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
],
"count": 1
}/api/v1/posts/{slug}Retrieve a single published blog post by its slug. The slug is a URL-friendly version of the post title.
Path Parameters
slug(required) - The slug of the blog post
Example Request
curl "https://blogsforvercel.com/api/v1/posts/getting-started-with-nextjs" \
-H "Authorization: Bearer YOUR_API_KEY"Example Response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Getting Started with Next.js",
"slug": "getting-started-with-nextjs",
"subtitle": "Learn the basics of Next.js",
"excerpt": "This guide will help you get started with Next.js...",
"content_markdown": "# Getting Started\n\nThis is a blog post...",
"content_html": "<h1>Getting Started</h1>\n<p>This is a blog post...</p>",
"tags": ["nextjs", "react"],
"featured_image": "https://example.com/image.jpg",
"featured_image_alt": "Next.js logo",
"featured_image_width": 1200,
"featured_image_height": 630,
"og_image": "https://example.com/og-image.jpg",
"og_description": "Learn Next.js with this comprehensive guide",
"twitter_card_type": "summary_large_image",
"twitter_image": "https://example.com/twitter-image.jpg",
"published_at": "2024-01-15T10:00:00Z",
"created_at": "2024-01-15T09:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}/api/v1/postsCreate a new blog post for your organization. The slug will be auto-generated from the title if not provided. Posts are created as drafts by default unless status: "published" is specified.
Subscription Limits:
- Pro Subscriptions: Organizations with any Pro subscriber have unlimited published posts.
- Free Tier: Organizations are limited to 3 published posts. Draft posts are unlimited.
- Post limits are enforced when publishing. You can create unlimited draft posts regardless of subscription tier.
Request Body
title(required) - Post titlecontent_markdown(required) - Markdown contentslug(optional) - Auto-generated from title if not providedtags(optional) - Array of tagsstatus(optional) -"draft"or"published"(default:"draft")- All Phase 1 & Phase 2 fields are optional (subtitle, excerpt, featured_image, og_image, meta_title, etc.)
Example Request
curl -X POST "https://blogsforvercel.com/api/v1/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "My New Blog Post",
"content_markdown": "# Introduction\n\nThis is my new post...",
"tags": ["javascript", "react"],
"status": "draft"
}'Example Response (201 Created)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My New Blog Post",
"slug": "my-new-blog-post",
"content_markdown": "# Introduction\n\nThis is my new post...",
"content_html": "<h1>Introduction</h1>\n<p>This is my new post...</p>",
"tags": ["javascript", "react"],
"status": "draft",
"published_at": null,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"reading_time_minutes": 5
}/api/v1/posts/{slug}Update an existing blog post by its slug. All fields are optional - only provided fields will be updated. Works with both draft and published posts.
Path Parameters
slug(required) - The slug of the blog post to update
Request Body
All fields are optional (partial updates supported):
title,slug,content_markdown,tags,status- All Phase 1 & Phase 2 fields (subtitle, excerpt, featured_image, og_image, meta_title, etc.)
Example Request
curl -X PUT "https://blogsforvercel.com/api/v1/posts/my-new-blog-post" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Title",
"status": "published",
"tags": ["updated", "tags"]
}'Example Response (200 OK)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Updated Title",
"slug": "my-new-blog-post",
"status": "published",
"published_at": "2024-01-15T11:00:00Z",
...
}/api/v1/posts/{slug}Delete an existing blog post by its slug. Works with both draft and published posts.
Path Parameters
slug(required) - The slug of the blog post to delete
Example Request
curl -X DELETE "https://blogsforvercel.com/api/v1/posts/my-new-blog-post" \
-H "Authorization: Bearer YOUR_API_KEY"Example Response (200 OK)
{
"success": true,
"message": "Post deleted successfully"
}Response Format
Post Object
idUUID of the blog posttitleTitle of the blog postslugURL-friendly identifiercontent_markdownOriginal Markdown contentcontent_htmlHTML rendered from MarkdowntagsArray of tag stringssubtitleOptional subtitle or descriptionexcerptShort excerpt for previews (150-200 characters recommended)featured_imageURL to featured imagefeatured_image_altAlt text for featured image accessibilityfeatured_image_widthWidth of featured image in pixelsfeatured_image_heightHeight of featured image in pixelsog_imageOpen Graph image URL (defaults to featured_image if not set)og_descriptionOpen Graph description (defaults to excerpt if not set)twitter_card_typeTwitter card type: summary or summary_large_image (default)twitter_imageTwitter-specific image URL if different from OG imagepublished_atISO 8601 timestamp when post was publishedcreated_atISO 8601 timestamp when post was createdupdated_atISO 8601 timestamp when post was last updatedError Handling
All errors are returned in JSON format with an error field.
401 Unauthorized
{ "error": "Invalid API key" }403 Forbidden
Post limit exceeded (when publishing)
Note: Post limits are enforced at the organization level. If any user in your organization has a Pro subscription, the entire organization has unlimited posts. Free tier organizations are limited to 3 published posts.
{ "error": "Free tier allows up to 3 published posts. Upgrade to Pro for unlimited posts." }404 Not Found
{ "error": "Post not found" }409 Conflict
Slug already exists
{ "error": "A post with this slug already exists" }429 Too Many Requests
Rate limit exceeded
{ "error": "Rate limit exceeded" }500 Internal Server Error
{ "error": "Internal server error" }Next.js Integration Examples
Server Component (App Router)
async function BlogPosts() {
const response = await fetch(
'https://blogsforvercel.com/api/v1/posts',
{
headers: {
'Authorization': `Bearer ${process.env.BLOGS_API_KEY}`
},
next: { revalidate: 3600 } // Revalidate every hour
}
);
const { posts } = await response.json();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<div dangerouslySetInnerHTML={{ __html: post.content_html }} />
</article>
))}
</div>
);
}Client Component
'use client';
import { useEffect, useState } from 'react';
export default function BlogPosts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('https://blogsforvercel.com/api/v1/posts', {
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_BLOGS_API_KEY}`
}
})
.then(res => res.json())
.then(data => setPosts(data.posts));
}, []);
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<div dangerouslySetInnerHTML={{ __html: post.content_html }} />
</article>
))}
</div>
);
}Using the SDK (Recommended)
The SDK provides convenient methods for creating, updating, and deleting posts:
import { BlogsForVercelClient } from '@lztek/blogs-for-vercel-sdk';
const client = new BlogsForVercelClient({
apiKey: process.env.BLOGS_FOR_VERCEL_API_KEY!,
});
// Create a post
const newPost = await client.createPost({
title: "My New Post",
content_markdown: "# Introduction\n\nThis is my post...",
tags: ["javascript"],
status: "draft"
});
// Update a post
const updatedPost = await client.updatePost("my-new-post", {
title: "Updated Title",
status: "published"
});
// Delete a post
await client.deletePost("my-new-post");