The Unmarkdown™ API lets you convert, store, and publish markdown documents programmatically. Instead of opening the web app, pasting content, picking a template, and clicking publish, you can do all of that with a single API call. Or chain multiple calls into automated workflows that run without human intervention.
This guide covers every endpoint, authentication method, and practical pattern for building automations with the Unmarkdown™ API.
What the Unmarkdown API lets you automate
The API supports four core workflows:
Conversion: Send markdown, get back destination-specific HTML and plain text. The conversion handles formatting differences between Google Docs, Word, Slack, OneNote, Email, and plain text automatically. No document is stored; the content is processed and returned.
Document management: Create, read, update, list, and organize documents in your Unmarkdown™ account. Documents persist across sessions and are accessible from the web app, API, and MCP tools.
Publishing: Publish any document to a shareable web page with a clean URL, SEO metadata, and template styling. Unpublish when you are done.
Monitoring: Check your usage quota, available calls, and billing period.
Unmarkdown API authentication
All API requests require a Bearer token. Generate an API key at Settings > API in your Unmarkdown™ account.
curl https://api.unmarkdown.com/v1/usage \
-H "Authorization: Bearer um_your_api_key_here"
Keys use the um_ prefix, are 66 characters total, and are stored as SHA-256 hashes (never in plaintext). You can have up to 2 active keys. Rotate keys by creating a new one and deleting the old one.
The API also accepts OAuth 2.1 tokens with the uat_ prefix, used by MCP integrations with Claude and ChatGPT. For programmatic access, API keys are simpler.
Unmarkdown API endpoint reference
Base URL: https://api.unmarkdown.com
Convert markdown
Convert markdown to a destination-specific format. Nothing is stored.
curl -X POST https://api.unmarkdown.com/v1/convert \
-H "Authorization: Bearer um_your_key" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Weekly Update\n\n## Shipped\n- User search (Cmd+K)\n- Document graph\n\n## In Progress\n| Feature | Status | ETA |\n|---------|--------|-----|\n| Folders | 80% | Feb 28 |\n| Tags | Planning | TBD |",
"destination": "slack",
"template_id": "swiss"
}'
Parameters:
markdown(required): Markdown content, max 100KBdestination(optional, defaultgeneric):google-docs,word,slack,onenote,email,plain-text,generic,htmltemplate_id(optional, defaultswiss): Any of the 62 template IDstheme_mode(optional, defaultlight):lightordark
Response:
{
"html": "<p><b>Weekly Update</b></p><p><b>Shipped</b></p>...",
"plain_text": "*Weekly Update*\n\n*Shipped*\n• User search (Cmd+K)\n...",
"destination": "slack",
"template_id": "swiss",
"theme_mode": "light"
}
For Slack, use the plain_text field. It contains Slack-compatible mrkdwn formatting. For Google Docs, Word, and OneNote, the html field contains destination-optimized markup, but you need the browser copy button to get it into the clipboard with the correct MIME type. For more on how destination-specific conversion works, see The AI Formatting Problem Nobody Talks About.
Create a document
curl -X POST https://api.unmarkdown.com/v1/documents \
-H "Authorization: Bearer um_your_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Architecture Decision Record: Auth Migration",
"content": "# ADR: Auth Migration\n\n## Context\nOur current session-based auth...",
"template_id": "github",
"folder": "Engineering"
}'
Parameters:
title(optional): Document titlecontent(optional): Markdown content, max 500KBtemplate_id(optional, defaultswiss)theme_mode(optional, defaultlight)folder(optional): Folder name (case-insensitive) or folder UUID
Response: { "id": "uuid", "title": "...", "template_id": "...", "created_at": "..." }
Free tier: max 5 documents. Pro: unlimited.
List documents
# List all documents
curl https://api.unmarkdown.com/v1/documents \
-H "Authorization: Bearer um_your_key"
# Filter by folder
curl "https://api.unmarkdown.com/v1/documents?folder=Engineering&limit=10" \
-H "Authorization: Bearer um_your_key"
Parameters: folder (optional), limit (optional, default 20, max 100), cursor (optional, pagination token)
Response: { "data": [...], "has_more": true, "next_cursor": "..." }
Does not count toward usage quota.
Get a document
curl https://api.unmarkdown.com/v1/documents/abc-123 \
-H "Authorization: Bearer um_your_key"
Returns full document content, metadata, publish status, and template info. Does not count toward usage quota.
Update a document
curl -X PATCH https://api.unmarkdown.com/v1/documents/abc-123 \
-H "Authorization: Bearer um_your_key" \
-H "Content-Type: application/json" \
-d '{
"content": "# Updated content\n\nNew information added...",
"template_id": "executive",
"folder": "Reports"
}'
Only the fields you include are updated. Set folder to null to move a document to Unfiled.
Publish a document
curl -X POST https://api.unmarkdown.com/v1/documents/abc-123/publish \
-H "Authorization: Bearer um_your_key" \
-H "Content-Type: application/json" \
-d '{
"slug": "auth-migration-adr",
"description": "Architecture decision record for the OAuth 2.1 migration",
"visibility": "link",
"page_width": "standard"
}'
Parameters:
slug(optional, Pro only): Custom URL slug. Auto-generated if omitted.description(optional): SEO description for the published pagevisibility(optional, defaultlink):publicorlink(unlisted)page_width(optional):standard(1280px),wide(1920px),full(unconstrained)
Response: { "published_url": "https://unmarkdown.com/u/username/auth-migration-adr", ... }
Unpublish
curl -X DELETE https://api.unmarkdown.com/v1/documents/abc-123/publish \
-H "Authorization: Bearer um_your_key"
List templates
# All 62 templates
curl https://api.unmarkdown.com/v1/templates \
-H "Authorization: Bearer um_your_key"
# Filter by category
curl "https://api.unmarkdown.com/v1/templates?category=business" \
-H "Authorization: Bearer um_your_key"
Does not count toward usage quota. Returns id, name, category, and description for each template. For a visual preview of all templates, see the templates page. For an explanation of how templates affect document output, see Markdown Templates: What They Are and Why They Matter.
Check usage
curl https://api.unmarkdown.com/v1/usage \
-H "Authorization: Bearer um_your_key"
Response:
{
"used": 342,
"limit": 1000,
"remaining": 658,
"month": "2026-02",
"reset_date": "2026-03-01T00:00:00Z",
"tier": "free"
}
Unmarkdown API automation examples
GitHub Actions: publish changelog on release
name: Publish Changelog
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish changelog
run: |
# Extract latest version from CHANGELOG.md
CONTENT=$(sed -n '/^## \[/,/^## \[/{//!p}' CHANGELOG.md | head -100)
TITLE="Release ${GITHUB_REF_NAME}"
curl -X POST https://api.unmarkdown.com/v1/documents/publish \
-H "Authorization: Bearer ${{ secrets.UNMARKDOWN_API_KEY }}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"${TITLE}\",
\"content\": $(echo "$CONTENT" | jq -Rs .),
\"template_id\": \"github\",
\"slug\": \"changelog-${GITHUB_REF_NAME}\"
}"
Cron job: weekly status report
#!/bin/bash
# weekly-status.sh - Run via cron every Friday at 5pm
# Gather data
COMMITS=$(git log --oneline --since="7 days ago" | head -20)
OPEN_ISSUES=$(gh issue list --state open --limit 5 --json title,number)
# Build markdown
MARKDOWN="# Weekly Status: $(date +%Y-%m-%d)
## Commits This Week
\`\`\`
${COMMITS}
\`\`\`
## Open Issues
$(echo $OPEN_ISSUES | jq -r '.[] | "- #\(.number): \(.title)"')
"
# Update the running status document
curl -X PATCH "https://api.unmarkdown.com/v1/documents/${STATUS_DOC_ID}" \
-H "Authorization: Bearer ${UNMARKDOWN_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"content\": $(echo "$MARKDOWN" | jq -Rs .)}"
# Re-publish
curl -X POST "https://api.unmarkdown.com/v1/documents/${STATUS_DOC_ID}/publish" \
-H "Authorization: Bearer ${UNMARKDOWN_API_KEY}" \
-H "Content-Type: application/json" \
-d '{}'
Batch convert for multiple destinations
const destinations = ["google-docs", "slack", "email", "plain-text"];
const markdown = fs.readFileSync("announcement.md", "utf-8");
const results = await Promise.all(
destinations.map((dest) =>
fetch("https://api.unmarkdown.com/v1/convert", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.UNMARKDOWN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
markdown,
destination: dest,
template_id: "executive",
}),
}).then((r) => r.json())
)
);
// results[0].html = Google Docs formatted
// results[1].plain_text = Slack mrkdwn
// results[2].html = Email with inline CSS
// results[3].plain_text = Clean plain text
Handling Unmarkdown API errors
All errors follow a consistent format:
{
"error": {
"code": "quota_exceeded",
"message": "Monthly API quota exceeded. Upgrade to Pro for 10,000 calls/month.",
"status": 429
}
}
Common error codes: unauthorized (401), rate_limited (429), quota_exceeded (429), validation_error (400), not_found (404), forbidden (403).
Check X-RateLimit-Remaining headers to avoid hitting rate limits. Implement exponential backoff for 429 responses.
MCP vs REST: when to use which
The Unmarkdown™ API is available through both REST and MCP. The tools are identical; the transport differs.
Use REST when building automated pipelines: CI/CD workflows, cron jobs, server-side integrations, and any context where you are writing code that calls the API directly.
Use MCP when working through an AI assistant: Claude, ChatGPT, Cursor, or VS Code. MCP lets the AI decide which tools to call based on your natural language request, so you describe what you want instead of writing API calls.
Both access the same documents, the same templates, and the same usage quota.
