SDKs
TypeScript SDK
Manage MisarBlog content and embed posts with the @misar/blog TypeScript SDK
Installation
npm install @misar/blog
# or
pnpm add @misar/blogAPI Client
new MisarBlog({ apiKey }) is the programmatic client. It authenticates with an mbk_ API key and defaults to https://api.misar.io/blog.
import { MisarBlog } from '@misar/blog';
const blog = new MisarBlog({ apiKey: process.env.MISARBLOG_API_KEY! });
// Override the base URL if needed (defaults to https://api.misar.io/blog)
const staging = new MisarBlog({
apiKey: 'mbk_...',
baseUrl: 'https://api.misar.io/blog',
});Articles
// List your articles
const { articles, total } = await blog.articles.list({ status: 'published', limit: 20 });
// Get one by slug
const article = await blog.articles.get('my-first-article');
// Publish an article
const created = await blog.articles.create({
title: 'Getting Started with the MisarBlog API',
body_markdown: '## Introduction\n\nThis guide covers…',
tags: ['typescript', 'api'],
cover_image_url: 'https://example.com/cover.jpg',
visibility: 'public',
});
// Save a draft
const draft = await blog.articles.createDraft({
title: 'Work in Progress',
body_markdown: '## Coming soon…',
tags: ['draft'],
});
// Search
const results = await blog.articles.search({ q: 'react', type: 'articles', sort: 'newest' });
// Related-article recommendations
const { recommendations } = await blog.articles.recommendations(article.id, 5);Series
const { series } = await blog.series.list();
const detail = await blog.series.get('my-series'); // { series, articles }
const newSeries = await blog.series.create({ title: 'A Series', description: 'Optional' });AI titles
// action: "seo" needs a prompt; action: "suggest" needs context
const { titles } = await blog.ai.titles({
action: 'seo',
prompt: 'best AI writing tools for beginner bloggers 2025',
});Analytics
const summary = await blog.analytics.summary(30); // days
// { period_days, views, revenue_cents, revenue_net_cents, active_subscribers }Reactions
const state = await blog.reactions.get(article.id); // counts + user_reactions
await blog.reactions.add(article.id, 'like'); // "like" | "clap" | "bookmark"
await blog.reactions.remove(article.id, 'like');Comments, follows, profile
const { comments } = await blog.comments.list(article.id, { limit: 20 });
const follow = await blog.follows.status(userId); // { is_following, follower_count }
const me = await blog.profiles.me(); // your profileNewsletter
// Session-scoped endpoints — call from a server context with a valid session
const { subscribers } = await blog.newsletter.subscribers({ limit: 50 });
const { issues } = await blog.newsletter.issues({ limit: 20 });API key management
blog.apiKeys.generate() and blog.apiKeys.revoke() intentionally throw — key creation and revocation require a dashboard session and cannot be performed with a Bearer key. Manage keys in Dashboard → Settings → API Keys.
Resources
| Resource | Methods |
|---|---|
blog.articles | list(opts?) · get(slug) · create(opts) · createDraft(opts) · search(opts?) · recommendations(articleId, limit?) |
blog.series | list() · get(slug) · create(opts) |
blog.ai | titles({ action, prompt?, context? }) |
blog.analytics | summary(days?) |
blog.reactions | get(articleId) · add(articleId, type) · remove(articleId, type) |
blog.comments | list(articleId, opts?) |
blog.follows | status(userId) |
blog.profiles | me() |
blog.newsletter | subscribers(opts?) · issues(opts?) |
blog.apiKeys | generate() · revoke() — dashboard only, both throw |
Error handling
import { BlogApiError } from '@misar/blog';
try {
await blog.articles.get('missing-slug');
} catch (err) {
if (err instanceof BlogApiError) {
console.error(err.status, err.message);
}
}Embed Helpers
The package also exports browser embed helpers (no API key required).
import { embed, embedUrl } from '@misar/blog';
// Just the URL
const url = embedUrl({ username: 'johndoe', theme: 'dark' });
// Inject an iframe into a container
const { iframe, destroy } = embed(document.getElementById('blog-container')!, {
username: 'johndoe',
width: '100%',
height: '600px',
theme: 'dark',
});
destroy(); // remove the embed laterEmbed a specific post
const url = embedUrl({ username: 'johndoe', slug: 'my-first-post', theme: 'light' });embedUrl(options) / embed(container, options)
| Option | Type | Description |
|---|---|---|
username | string | Required. Blog author username. |
slug | string? | Post slug for a single-post embed. |
theme | 'light' | 'dark' | 'auto' | Color theme (default: auto). |
width | string? | CSS width for the iframe (embed only). |
height | string? | CSS height for the iframe (embed only). |
className | string? | Class applied to the iframe (embed only). |
embedUrl returns a URL string. embed appends an <iframe> to container and returns { iframe, destroy }.
Token refresh
For gated (paywalled) embeds, refresh the session token:
import { refreshToken, getToken, clearToken } from '@misar/blog';
const { token, expiresAt } = await refreshToken({ token: 'current-session-token' });
const stored = getToken(); // string | null, from localStorage key "misar_blog_token"
clearToken(); // clear on logout