Blog Admin API Specification
Status: Awaiting Socrates implementation
Frontend Status: ✅ Complete (Phase 4 admin UI delivered 2026-04-21)
Overview
Admin API endpoints required to power the HoffDesk Blog admin interface. All endpoints return JSON unless otherwise specified. HTMX requests expect HTML partials in response.
Dev Server
A standalone dev server is available for local preview:
cd blog/
python3 dev_server.py
Access points:
- Public blog: http://localhost:8080/
- Admin panel: http://localhost:8080/admin/
- Editor: http://localhost:8080/admin/posts/the-night-i-broke-dns/edit
The dev server includes mock data for all endpoints so you can preview the UI before Socrates wires the real API.
Authentication
All admin endpoints require authentication. Current assumption: session-based auth via FastAPI (same as main HoffDesk app).
Endpoints
Dashboard
GET /api/blog/admin/stats
Returns dashboard statistics for the admin homepage.
Response:
{
"total": 12,
"published": 8,
"drafts": 4,
"images": 23,
"last_build_time": "2026-04-21T10:30:00Z",
"last_build_iso": "2026-04-21T10:30:00+00:00"
}
GET /api/blog/admin/recent
Returns recent posts for the dashboard activity feed.
Response:
{
"posts": [
{
"slug": "the-night-i-broke-dns",
"title": "The Night I Broke DNS and My Wife Couldn't Reach Facebook",
"category": "homelab",
"status": "published",
"updated_relative": "2 hours ago"
}
]
}
Posts
GET /api/blog/admin/posts
Returns list of posts with filtering.
Query Parameters:
- status (optional): published | draft
- category (optional): homelab | openclaw | ai-news
- q (optional): search string
- page (optional): page number (default: 1)
- per_page (optional): items per page (default: 20)
Response:
{
"posts": [
{
"slug": "post-slug",
"title": "Post Title",
"category": "homelab",
"status": "published",
"updated_at": "2026-04-21T10:30:00Z",
"updated_relative": "2 hours ago",
"excerpt": "Brief summary..."
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 12,
"pages": 1
}
}
GET /api/blog/admin/posts/{slug}
Returns full post data for editing.
Response:
{
"slug": "post-slug",
"title": "Post Title",
"category": "homelab",
"status": "published",
"tags": "dns, homelab, fail",
"excerpt": "Brief summary...",
"cover_image": "/blog/images/hero.jpg",
"content": "# Markdown content...",
"rendered_html": "<h1>HTML content...</h1>",
"created_at": "2026-04-20T08:00:00Z",
"updated_at": "2026-04-21T10:30:00Z",
"updated_relative": "2 hours ago"
}
POST /api/blog/admin/posts/{slug}
Auto-save endpoint for the editor. Creates new post if slug is new.
Request Body:
{
"title": "Post Title",
"category": "homelab",
"tags": "dns, homelab",
"excerpt": "Brief summary...",
"cover_image": "/blog/images/hero.jpg",
"content": "# Markdown content..."
}
Response (HTMX):
<span class="admin-toast-message">Saved</span>
Response (JSON):
{
"slug": "post-slug",
"status": "draft",
"saved": true
}
POST /api/blog/admin/posts/{slug}/publish
Publishes a draft post.
Response (HTMX): Full status widget HTML partial
POST /api/blog/admin/posts/{slug}/unpublish
Reverts a published post to draft.
Response (HTMX): Full status widget HTML partial
POST /api/blog/admin/posts/{slug}/delete
Deletes a post permanently.
Response:
- Success: 302 Redirect to /admin/blog/posts
- HTMX: Toast confirmation
POST /api/blog/admin/posts/{slug}/preview
Returns rendered HTML preview of post content.
Request Body: Same as save endpoint
Response:
{
"rendered_html": "<h1>Title</h1><p>Content...</p>"
}
Build & Deploy
POST /api/blog/admin/rebuild
Triggers static site rebuild.
Response (HTMX):
<span class="admin-toast-message admin-toast-message--success">Rebuild started</span>
POST /api/blog/admin/deploy
Triggers deployment to production (Cloudflare Pages).
Response (HTMX):
<span class="admin-toast-message admin-toast-message--success">Deploying...</span>
Images
GET /api/blog/admin/images
Returns list of available images in the blog images directory.
Response:
{
"images": [
{
"path": "/blog/images/hero.jpg",
"filename": "hero.jpg",
"relative_path": "images/hero.jpg",
"size_bytes": 45000,
"dimensions": "1200x600",
"modified": "2026-04-21T10:30:00Z"
}
]
}
HTMX Conventions
All endpoints support HTMX requests via the HX-Request header:
- Save endpoints: Return toast message HTML
- Status changes: Return full component HTML (status widget, post row)
- Rebuild/Deploy: Return toast confirmation
- Delete: Return redirect header or toast
Frontend Integration Notes
Auto-save Behavior
- Debounced 3s after input stops
- Includes all form fields via
hx-include - Visual feedback via autosave indicator
Live Preview
- Client-side: markdown-it (500ms debounce)
- Server-side:
POST /api/blog/admin/posts/{slug}/preview(enhancement)
Status Widget
- Dynamic partial swap on publish/unpublish
- Green dot = published, amber dot = draft
Mobile Considerations
- Preview pane hidden behind toggle on <768px
- Sidebar collapses to hamburger menu
- Tables become card layouts
Files Delivered
Templates (Jinja2):
- admin/templates/admin_base.html.j2 — Base layout with sidebar
- admin/templates/admin_dashboard.html.j2 — Stats + recent activity
- admin/templates/admin_post_list.html.j2 — Filterable post table
- admin/templates/admin_editor.html.j2 — Split-pane editor
- admin/templates/admin_images.html.j2 — Image browser
- admin/templates/components/post_row.html.j2 — HTMX partial
- admin/templates/components/status_widget.html.j2 — HTMX partial
- admin/templates/components/preview_pane.html.j2 — HTMX partial
Stylesheets:
- admin/static/admin.css — 28KB, extends blog.css tokens
Scripts:
- Inline JS in admin_editor.html.j2 for client-side preview
Next Steps
- Socrates implements above endpoints
- Integration testing on dev environment
- Matt approves UX flow
- Deploy to production