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-sdk

View package on NPM →

The 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_here
Manage API Keys

API Endpoints

GET/api/v1/posts

Retrieve 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 by
  • tag_logic (optional) - Filter logic: and (all tags must match) or or (any tag matches, default)
  • search (optional) - Search query to filter posts by title or content
  • limit (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 }
GET/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" }
POST/api/v1/posts

Create 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 title
  • content_markdown (required) - Markdown content
  • slug (optional) - Auto-generated from title if not provided
  • tags (optional) - Array of tags
  • status (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 }
PUT/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", ... }
DELETE/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 post
titleTitle of the blog post
slugURL-friendly identifier
content_markdownOriginal Markdown content
content_htmlHTML rendered from Markdown
tagsArray of tag strings
subtitleOptional subtitle or description
excerptShort excerpt for previews (150-200 characters recommended)
featured_imageURL to featured image
featured_image_altAlt text for featured image accessibility
featured_image_widthWidth of featured image in pixels
featured_image_heightHeight of featured image in pixels
og_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 image
published_atISO 8601 timestamp when post was published
created_atISO 8601 timestamp when post was created
updated_atISO 8601 timestamp when post was last updated

Error 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");

Ready to get started?

Create an account and start managing your blog posts today.

Sign Up Free