Examples
Copy, paste, publish.
Each example runs against this server as written. Set API_KEY to a key from Connect first.
curl: draft to live page
BASE="https://bloting.com"
# 1. Find the publication to publish into
curl -s "$BASE/v1/publications" -H "Authorization: Bearer $API_KEY"
# 2. Create a draft
curl -s "$BASE/v1/content" \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"publication_id":"pub_...","markdown":"# Release notes\n\nWhat changed this week..."}'
# 3. Check it
curl -s "$BASE/v1/publishing/preview" \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"content_id":"art_..."}'
# 4. Publish it (returns 200 with a url, or 202 with an approve_url)
curl -s "$BASE/v1/publishing/publish" \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: release-notes-week-41" \
-d '{"content_id":"art_..."}'TypeScript: publish a Markdown file
Uses the built-in fetch; no SDK needed. Run with Node.js 22 or later.
import { readFile } from "node:fs/promises";
const BASE = "https://bloting.com";
const KEY = process.env.API_KEY!;
async function api<T>(method: string, path: string, body?: unknown, idempotencyKey?: string): Promise<{ status: number; data: T }> {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
...(body ? { "Content-Type": "application/json" } : {}),
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = res.status === 204 ? null : await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return { status: res.status, data };
}
const file = process.argv[2]!;
const markdown = await readFile(file, "utf8");
const pubs = await api<{ data: { id: string; name: string }[] }>("GET", "/v1/publications");
const publication = pubs.data.data[0];
if (!publication) throw new Error("Create a publication first.");
const draft = await api<{ id: string }>("POST", "/v1/content", { publication_id: publication.id, markdown }, `draft:${file}`);
const preview = await api<{ requires_approval: boolean; validation: { checks: { id: string; label: string; state: string; notes: string[] }[] } }>(
"POST", "/v1/publishing/preview", { content_id: draft.data.id });
for (const check of preview.data.validation.checks) for (const note of check.notes) console.log(`${check.label}: ${note}`);
// A key without publish permission is "blocked" only by that check; it can still ask for approval.
const blocking = preview.data.validation.checks.filter((c) => c.state === "block" && c.id !== "publishing_permissions");
if (blocking.length) process.exit(1);
const result = await api<{ status: string; url?: string; approve_url?: string }>(
"POST", "/v1/publishing/publish", { content_id: draft.data.id }, `publish:${draft.data.id}`);
console.log(result.status === 202 ? `Waiting for approval: ${result.data.approve_url}` : `Live: ${result.data.url}`);Python: list everything you've published
import os
import requests
BASE = "https://bloting.com"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['API_KEY']}"
cursor = None
while True:
params = {"status": "published", "limit": 100}
if cursor:
params["cursor"] = cursor
res = session.get(f"{BASE}/v1/content", params=params, timeout=30)
if res.status_code == 429:
raise SystemExit(f"Rate limited. Retry in {res.headers['Retry-After']}s.")
res.raise_for_status()
page = res.json()
for item in page["data"]:
print(item["published_at"], item["url"], item["title"], sep="\t")
if not page["has_more"]:
break
cursor = page["next_cursor"]In a conversation
With the MCP server connected, publishing can happen without leaving your assistant. An illustration:
- YouPublish my latest article.
- AssistantWhich publication should I use?
- YouMy Technology Blog.
- AssistantI've prepared the page. Would you like to publish it?
- YouYes.
- AssistantPublished. Here's your link.
This is an example, not a transcript. It depends on your AI client supporting remote MCP servers and asking before it calls tools, and on the connection having permission to publish. Without that permission the last step is a link for you to approve instead.
Behind those turns the assistant calls publication_list, content_create, content_preview and, after you say yes, content_publish.