API Documentation
Everything you need to fetch your blog content via the Blotd API.
Getting Started
- Sign up - Create a free account at
blotd.com/login - Get your API key - Go to Dashboard > API Keys and copy your key
- Make your first request - See the example below
Authentication
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer blotd_xxxxxxxxxxxx
Keep your API key safe
Never expose your API key in client-side code, public repositories, or browser requests. Store it as an environment variable (e.g. BLOTD_API_KEY) and only use it in server-side code such as API routes, server components, or build scripts. If you believe a key has been compromised, revoke it immediately from your dashboard and create a new one.
API Key Scoping
Each API key only returns articles assigned to it. This lets you manage content for multiple websites or clients from a single Blotd account.
How it works
- Create separate API keys - Go to Dashboard > API Keys and create one key per website or client (e.g. "Marketing Site", "Partner Portal", "Mobile App").
- Assign articles to keys - When creating or editing an article, use the "API Key Access" panel in the sidebar to select which keys can access that article.
- Fetch with the right key - Each key only returns its assigned articles. An article assigned to "Marketing Site" won't appear in API responses for "Partner Portal".
Default behavior
If you leave all keys unchecked when creating an article, that article will be accessible by all of your API keys. This preserves backward compatibility and is the default for existing articles.
// Marketing site — uses its own key, only gets marketing articles
const marketing = await fetch(
"https://api.blotd.com/v1/articles",
{
headers: {
Authorization: `Bearer ${process.env.MARKETING_BLOTD_KEY}`,
},
}
);
// Partner portal — different key, different articles
const partner = await fetch(
"https://api.blotd.com/v1/articles",
{
headers: {
Authorization: `Bearer ${process.env.PARTNER_BLOTD_KEY}`,
},
}
);Base URL
https://api.blotd.com/v1Endpoints
/articlesList all published articles, paginated.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 10 | Items per page (max 50) |
| sort | string | newest | newest or oldest |
| tag | string | - | Filter by tag |
Example
const res = await fetch(
"https://api.blotd.com/v1/articles?page=1&limit=10",
{
headers: {
Authorization: "Bearer blotd_xxxxxxxxxxxx",
},
}
);
const { data, pagination } = await res.json();/articles/:slugGet a single article by its slug.
const res = await fetch(
"https://api.blotd.com/v1/articles/my-first-post",
{
headers: {
Authorization: "Bearer blotd_xxxxxxxxxxxx",
},
}
);
const { data } = await res.json();/articles/tag/:tagFilter articles by tag. Supports the same pagination params as /articles.
/articles/search?q=querySearch articles by title, content, or tags.
Response Format
All responses follow this shape:
{
"success": true,
"data": { ... },
"pagination": {
"page": 1,
"limit": 10,
"total": 42,
"totalPages": 5
}
}Rate Limits
| Plan | Monthly Limit | Exceeded |
|---|---|---|
| Free | 1,000 requests | 429 Too Many Requests |
| Pro | 25,000 requests | 429 Too Many Requests |
Code Examples
Next.js (App Router)
async function getArticles() {
const res = await fetch(
"https://api.blotd.com/v1/articles",
{
headers: {
Authorization: `Bearer ${process.env.BLOTD_API_KEY}`,
},
next: { revalidate: 60 },
}
);
return res.json();
}
export default async function BlogPage() {
const { data } = await getArticles();
return (
<div>
{data.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.readingTime}</p>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
))}
</div>
);
}Astro
---
const res = await fetch(
"https://api.blotd.com/v1/articles",
{
headers: {
Authorization: `Bearer ${import.meta.env.BLOTD_API_KEY}`,
},
}
);
const { data: posts } = await res.json();
---
{posts.map((post) => (
<article>
<h2>{post.title}</h2>
<Fragment set:html={post.content} />
</article>
))}Plain JavaScript
fetch("https://api.blotd.com/v1/articles", {
headers: {
Authorization: "Bearer blotd_xxxxxxxxxxxx",
},
})
.then((res) => res.json())
.then(({ data }) => {
data.forEach((post) => {
console.log(post.title, post.slug);
});
});Best Practices
Cache responses server-side
Blog content changes infrequently. Cache API responses so one request serves thousands of visitors. In Next.js, use revalidate for time-based caching:
await fetch(url, { next: { revalidate: 60 } })Use on-demand revalidation for zero waste
Even better than time-based caching: tag your fetches and only invalidate when content actually changes. Your pages stay fully cached until you publish, update, or delete an article.
// In your page (cache indefinitely)
await fetch(url, { next: { tags: ["blog"] } })
// In your publish/update handler (bust the cache)
import { revalidateTag } from "next/cache"
revalidateTag("blog")Fetch only what you need
Use ?limit=5 if your homepage only shows 5 posts. Filter by tag with ?tag=tutorials for specific sections. Smaller responses mean faster loads and fewer wasted API calls.
Handle errors gracefully
Always handle 401, 404, and 429 responses. Show fallback UI when the API is unreachable, a friendly message for missing articles, and a retry prompt when rate-limited. Never let an API error crash your page.
Error Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (missing params) |
| 401 | Unauthorized (missing or invalid API key) |
| 404 | Article not found |
| 429 | Rate limit exceeded |
| 500 | Server error |