Unmarkdown
Developers

Automate Document Publishing with the Unmarkdown API

Updated Feb 25, 2026 · 7 min read

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 100KB
  • destination (optional, default generic): google-docs, word, slack, onenote, email, plain-text, generic, html
  • template_id (optional, default swiss): Any of the 62 template IDs
  • theme_mode (optional, default light): light or dark

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 title
  • content (optional): Markdown content, max 500KB
  • template_id (optional, default swiss)
  • theme_mode (optional, default light)
  • 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 page
  • visibility (optional, default link): public or link (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.

Your markdown deserves a beautiful home.

Start publishing for free. Upgrade when you need more.

View pricing