REST API Documentation
Integrate MassBlogger with any website using our REST API.
Quick Start - Copy & Paste
Copy these two files to your Next.js project. Replace your_api_key with your API key from website settings.
1. Blog Overview Page
Shows all posts with category/tag filtering. Supports /blog, /blog?category=Tech, and /blog?tag=React.
// app/blog/page.js (or app/blog/category/[category]/page.js, app/blog/tag/[tag]/page.js)
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key'; // Get this from your website settings
async function getBlogData() {
const [postsRes, taxonomiesRes] = await Promise.all([
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } }),
fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`, { next: { revalidate: 3600 } }),
]);
const posts = await postsRes.json();
const taxonomies = await taxonomiesRes.json();
// Filter out scheduled posts
const now = new Date();
const visiblePosts = posts.filter(post =>
!post.scheduleDate || new Date(post.scheduleDate) <= now
);
return { posts: visiblePosts, taxonomies };
}
export default async function BlogPage({ searchParams }) {
const { posts, taxonomies } = await getBlogData();
const category = searchParams?.category;
const tag = searchParams?.tag;
// Filter posts by category or tag if provided
let filteredPosts = posts;
if (category) {
filteredPosts = posts.filter(p => p.category === category);
} else if (tag) {
filteredPosts = posts.filter(p => p.tags?.includes(tag));
}
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">
{category ? `Category: ${category}` : tag ? `Tag: ${tag}` : 'Blog'}
</h1>
{/* Category/Tag Navigation */}
<div className="flex flex-wrap gap-2 mb-8">
<Link href="/blog" className="px-3 py-1 bg-gray-100 rounded-full text-sm hover:bg-gray-200">
All
</Link>
{taxonomies.categories?.map(cat => (
<Link
key={cat.id}
href={`/blog?category=${encodeURIComponent(cat.name)}`}
className={`px-3 py-1 rounded-full text-sm ${category === cat.name ? 'bg-blue-500 text-white' : 'bg-gray-100 hover:bg-gray-200'}`}
>
{cat.name}
</Link>
))}
</div>
{/* Posts Grid */}
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
{post.featuredImage && (
<img src={post.featuredImage} alt={post.title} className="w-full h-48 object-cover rounded-lg mb-4" />
)}
<p className="text-sm text-blue-600 mb-1">{post.category}</p>
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
<div className="flex gap-2 mt-3">
{post.tags?.map(tag => (
<span key={tag} className="text-xs bg-gray-100 px-2 py-1 rounded">{tag}</span>
))}
</div>
</Link>
))}
</div>
</div>
);
}2. Single Post Page
Displays a single post with SEO metadata, internal links automatically applied, and category/tag links.
// app/blog/[slug]/page.js
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key'; // Get this from your website settings
async function getPost(slug) {
const [postRes, linksRes] = await Promise.all([
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}&slug=${slug}`, { next: { revalidate: 60 } }),
fetch(`${API_URL}/api/internal-links?apiKey=${API_KEY}`, { next: { revalidate: 3600 } }),
]);
const post = await postRes.json();
const links = await linksRes.json();
// Apply internal links to content
let content = post.content || '';
if (Array.isArray(links)) {
links.forEach(link => {
const regex = new RegExp(`(?<![\\p{L}\\p{N}])(${link.keyword})(?![\\p{L}\\p{N}])`, 'giu');
content = content.replace(regex, `<a href="${link.url}" class="text-blue-600 hover:underline">$1</a>`);
});
}
return { ...post, content };
}
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.metaTitle || post.title,
description: post.metaDescription,
};
}
export default async function PostPage({ params }) {
const post = await getPost(params.slug);
if (!post || post.error) {
return <div className="text-center py-20">Post not found</div>;
}
const showUpdated = post.updatedAt && new Date(post.updatedAt) > new Date(post.createdAt);
return (
<article className="max-w-3xl mx-auto px-4 py-12">
{/* Category & Tags */}
<div className="flex items-center gap-3 mb-4">
{post.category && (
<a href={`/blog?category=${encodeURIComponent(post.category)}`} className="text-blue-600 text-sm font-medium">
{post.category}
</a>
)}
{post.tags?.map(tag => (
<a key={tag} href={`/blog?tag=${encodeURIComponent(tag)}`} className="text-xs bg-gray-100 px-2 py-1 rounded">
{tag}
</a>
))}
</div>
{/* Title */}
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
{/* Date */}
<p className="text-gray-500 mb-8">
{new Date(post.createdAt).toLocaleDateString()}
{showUpdated && <span> ยท Updated {new Date(post.updatedAt).toLocaleDateString()}</span>}
</p>
{/* Featured Image */}
{post.featuredImage && (
<img src={post.featuredImage} alt={post.title} className="w-full rounded-xl mb-8" />
)}
{/* Content - use Tailwind Typography plugin for styling */}
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</article>
);
}Tip: Install @tailwindcss/typography for automatic content styling with the prose class.
Category & Tag Pages
Create dedicated pages for categories and tags to improve SEO and user experience. You have two options:
Query Parameters (Simple)
/blog?category=TechUse the blog overview page code above. Filter posts based on URL parameters.
Dedicated Pages (SEO-friendly)
/blog/category/techCreate separate page files for cleaner URLs and better SEO.
Category Page
Create app/blog/category/[slug]/page.js for SEO-friendly category URLs.
// app/blog/category/[slug]/page.js
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key';
export async function generateStaticParams() {
const res = await fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`);
const { categories } = await res.json();
return categories?.map(cat => ({
slug: cat.name.toLowerCase().replace(/\s+/g, '-'),
})) || [];
}
export async function generateMetadata({ params }) {
const { slug } = await params;
const name = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return {
title: `${name} - Blog`,
description: `Browse all posts in ${name}`,
};
}
export default async function CategoryPage({ params }) {
const { slug } = await params;
const postsRes = await fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } });
const posts = await postsRes.json();
const filteredPosts = posts.filter(p =>
p.category?.toLowerCase().replace(/\s+/g, '-') === slug
);
const categoryName = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">Category: {categoryName}</h1>
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
</Link>
))}
</div>
</div>
);
}Tag Page
Create app/blog/tag/[slug]/page.js for SEO-friendly tag URLs.
// app/blog/tag/[slug]/page.js
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key';
export async function generateStaticParams() {
const res = await fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`);
const { tags } = await res.json();
return tags?.map(tag => ({
slug: tag.name.toLowerCase().replace(/\s+/g, '-'),
})) || [];
}
export default async function TagPage({ params }) {
const { slug } = await params;
const postsRes = await fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } });
const posts = await postsRes.json();
const filteredPosts = posts.filter(p =>
p.tags?.some(tag => tag.toLowerCase().replace(/\s+/g, '-') === slug)
);
const tagName = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">Tag: {tagName}</h1>
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
</Link>
))}
</div>
</div>
);
}Making Categories & Tags Clickable
In your single post page, link categories and tags so users can browse related content:
{/* Category link */}
{post.category && (
<a
href={`/blog/category/${post.category.toLowerCase().replace(/\s+/g, '-')}`}
className="text-blue-600 text-sm font-medium hover:underline"
>
{post.category}
</a>
)}
{/* Tag links */}
<div className="flex gap-2">
{post.tags?.map(tag => (
<a
key={tag}
href={`/blog/tag/${tag.toLowerCase().replace(/\s+/g, '-')}`}
className="text-xs bg-gray-100 px-2 py-1 rounded hover:bg-gray-200"
>
{tag}
</a>
))}
</div>Filtering & Sorting
Filter and sort posts for category/tag pages:
// Filter by category
const categoryPosts = posts.filter(p => p.category === 'Technology');
// Filter by tag
const tagPosts = posts.filter(p => p.tags?.includes('React'));
// Sort by date (newest first)
const sorted = posts.sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt)
);
// Sort alphabetically
const alphabetical = posts.sort((a, b) =>
a.title.localeCompare(b.title)
);
// Combine filter + sort
const result = posts
.filter(p => p.category === 'Technology')
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));Recommended URL Structure
| /blog | All posts |
| /blog/[slug] | Single post |
| /blog/category/[slug] | Posts in category |
| /blog/tag/[slug] | Posts with tag |
Authentication
All API requests require an API key. You can find your API key in your website settings after adding a Next.js or REST API website.
GET https://www.massblogger.com/api/blog?apiKey=YOUR_API_KEYAPI Reference
/api/blogReturns all published posts for your website.
Query Parameters
| apiKey | Required | Your API key |
Example Response
[
{
"title": "My First Post",
"slug": "my-first-post",
"category": "Technology",
"tags": ["React", "JavaScript"],
"featuredImage": "https://example.com/image.jpg",
"metaTitle": "My First Post | Blog",
"metaDescription": "An introduction to...",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-16T14:20:00.000Z",
"scheduleDate": null
}
]/api/blog?slug=POST_SLUGReturns a single post by slug, including the full content.
Query Parameters
| apiKey | Required | Your API key |
| slug | Required | The post slug |
Example Response
{
"title": "My First Post",
"slug": "my-first-post",
"category": "Technology",
"tags": ["React", "JavaScript"],
"featuredImage": "https://example.com/image.jpg",
"metaTitle": "My First Post | Blog",
"metaDescription": "An introduction to...",
"content": "<p>Your full HTML content here...</p>",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-16T14:20:00.000Z",
"scheduleDate": null
}/api/internal-linksReturns all internal links configured for your account. Use these to automatically add internal links to your articles based on keywords.
Query Parameters
| apiKey | Required | Your API key |
Example Response
[
{ "keyword": "contact us", "url": "/contact" },
{ "keyword": "pricing", "url": "/pricing" },
{ "keyword": "learn more", "url": "/about" }
]Usage Example
You can use these links to automatically replace keywords in your article content with links:
// Fetch internal links
const links = await fetch(
`${API_URL}/api/internal-links?apiKey=${API_KEY}`
).then(res => res.json());
// Replace keywords in content with links
let content = post.content;
links.forEach(link => {
const regex = new RegExp(`\\b${link.keyword}\\b`, 'gi');
content = content.replace(regex,
`<a href="${link.url}">${link.keyword}</a>`
);
});/api/pseo-pagesReturns live pSEO pages stored in Massblogger for your website. This is useful for custom sites that want to render pSEO pages via REST instead of storing webhook payloads themselves.
Query Parameters
| apiKey | Required | Your website API key |
| slug | Optional | Return a single live pSEO page by slug |
| campaignId | Optional | Filter pages to a single pSEO campaign |
Example Response: list
[
{
"pageId": "pseo_abc123_x7k2m9p1",
"campaignId": "67cafe1234567890abcd1234",
"websiteId": "67site1234567890abcd9999",
"slug": "cookies/ttp",
"targetSlug": "cookies/ttp",
"status": "live",
"variables": { "slug": "_ttp" },
"createdAt": "2026-03-11T10:30:00.000Z",
"updatedAt": "2026-03-11T14:20:00.000Z"
}
]Example Response: single page
{
"pageId": "pseo_abc123_x7k2m9p1",
"campaignId": "67cafe1234567890abcd1234",
"websiteId": "67site1234567890abcd9999",
"status": "live",
"slug": "cookies/ttp",
"targetSlug": "cookies/ttp",
"variables": { "slug": "_ttp" },
"html": "<h2>_ttp cookie</h2><p>Resolved page content...</p>",
"fields": {
"metaTitle": "_ttp cookie",
"metaDescription": "What the _ttp cookie does"
},
"internalLinks": [
{ "keyword": "cookie policy", "anchor": "cookie policy", "url": "/cookie-policy", "internal": true, "nofollow": false }
],
"createdAt": "2026-03-11T10:30:00.000Z",
"updatedAt": "2026-03-11T14:20:00.000Z"
}Usage Example
async function getPseoPage(slug) {
const res = await fetch(
`${API_URL}/api/pseo-pages?apiKey=${API_KEY}&slug=${encodeURIComponent(slug)}`,
{ next: { revalidate: 60 } }
);
return res.json();
}/api/taxonomiesReturns all categories and tags configured for your website. Use these to build category/tag pages on your frontend.
Query Parameters
| apiKey | Required | Your API key |
Example Response
{
"categories": [
{ "id": "abc123", "name": "Technology" },
{ "id": "def456", "name": "Lifestyle" }
],
"tags": [
{ "id": "ghi789", "name": "React" },
{ "id": "jkl012", "name": "JavaScript" }
]
}Usage Example
Use taxonomies to filter posts and build category/tag pages:
// Fetch taxonomies
const { categories, tags } = await fetch(
`${API_URL}/api/taxonomies?apiKey=${API_KEY}`
).then(res => res.json());
// Filter posts by category
const techPosts = posts.filter(p => p.category === 'Technology');
// Filter posts by tag
const reactPosts = posts.filter(p => p.tags.includes('React'));Response Fields
| Field | Type | Description |
|---|---|---|
| title | string | The article title |
| slug | string | URL-friendly identifier |
| category | string | null | Post category |
| tags | string[] | Array of tag names |
| featuredImage | string | null | Featured image URL |
| content | string | Full HTML content (single post endpoint only) |
| metaTitle | string | SEO title for the page |
| metaDescription | string | SEO description for the page |
| createdAt | string (ISO 8601) | When the post was created |
| updatedAt | string (ISO 8601) | When the post was last updated (show "Updated on...") |
| scheduleDate | string | null | Scheduled publish date (hide post if in future) |
Quick Notes
- Styling:The
contentfield is raw HTML. Style it with @tailwindcss/typography (proseclasses) or custom CSS. - Images:For Next.js Image component with external URLs, add
remotePatternsto your next.config. - Cache:You may need to clear your cache or redeploy your site to see new articles.
- Scheduled Posts:Filter out posts where
scheduleDateis in the future. - Updated Date:Show "Updated on..." when
updatedAt>createdAt. - Internal Links:Use
/api/internal-linksto auto-link keywords in your content.
Need help? Contact us at [email protected]