Install our app for a better experience!

Content Publishing API (Blog & Pages)

Content Publishing API (Blog & Pages)

Publish and update blog posts and custom marketing pages programmatically — including the "new-style" AEO fields: FAQs, citations, key takeaways, and custom JSON-LD schema. Two APIs share one authentication scheme:

API Base Creates
Blog API /blog/api/ Blog posts (rendered at /blog/<slug>/)
Pages API /api/pages/ Custom pages (rendered at /<slug>/)

Full field-by-field reference lives in the repo at docs/BLOG_API.md and docs/PAGES_API.md. This page is the in-product quick start.

Authentication

Both APIs use the same key, sent on every request:

X-API-Key: <your-raw-key>

(Staff may instead use Authorization: Bearer <session-key> or a logged-in session.)

Get a key: Django Admin → Blog API Keys → select or add a row → Actions → Generate new API key → Go. The raw key is shown once — copy it immediately; it is stored only as a hash and never shown again. The same key works for both APIs.

The new-style content fields

These four fields turn a plain post/page into AEO-optimized, machine-readable content. They work on both APIs (the Pages API supports faqs and schema_json; the Blog API supports all four).

Field Shape Renders as
faqs array of {"question": "...", "answer": "..."} Visible FAQ accordion + schema.org FAQPage JSON-LD
citations array of {title, url, author, publisher, date_published, type} "Sources & References" block + JSON-LD citation[]
key_takeaways string, one takeaway per line A styled "Quick Answer" box near the top
schema_json raw JSON-LD string Injected verbatim (overrides the auto-generated schema)

Notes that apply to all of them:

  • faqs: rows missing either question or answer (or non-object rows) are silently dropped. The visible accordion and the FAQPage JSON-LD are built from the same list, so they never drift. (FAQ rich results are limited by Google to authoritative government/health sites — treat FAQPage as semantic metadata for search and AI answer engines, not a guaranteed rich-result feature.)
  • citations: a row with neither title nor url is dropped; stored rows are normalized to the full key set.
  • On update, providing faqs or citations replaces the whole list (not append).
  • You do not need to hand-write FAQ or citation JSON into schema_json — those are generated automatically from faqs / citations.

Publish a blog post

curl -X POST https://<host>/blog/api/posts/create/ \
  -H "X-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{
    "title": "CELPIP vs IELTS for Canadian PR",
    "author_username": "admin",
    "content_format": "html",
    "content": "<section><h2>Which test?</h2><p>...</p></section>",
    "is_published": true,
    "categories": ["immigration"],
    "tags": ["celpip", "ielts"],
    "meta_description": "CELPIP vs IELTS for Express Entry — accepted tests, scoring, and which to pick.",
    "key_takeaways": "Both are accepted for Express Entry\nCELPIP is computer-delivered and Canada-only\nIELTS General Training is available worldwide",
    "faqs": [
      {"question": "Is CELPIP accepted for Express Entry?", "answer": "Yes. IRCC accepts CELPIP General for Express Entry and most PR streams."},
      {"question": "Which is easier, CELPIP or IELTS?", "answer": "Neither is objectively easier; CELPIP is fully computer-based, IELTS offers paper and computer formats."}
    ],
    "citations": [
      {"title": "Language testing — IRCC", "url": "https://www.canada.ca/...", "publisher": "IRCC", "date_published": "2026", "type": "WebPage"}
    ]
  }'

Update (send only what changes; faqs/citations/categories replace the whole list):

curl -X PUT https://<host>/blog/api/posts/celpip-vs-ielts-for-canadian-pr/ \
  -H "X-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{"faqs": [{"question": "How long are scores valid?", "answer": "Two years for both tests."}]}'

Blog fields (create): title (required), author_username (required for API-key auth), slug, content, content_format (richtext | html), is_published, featured, published_at, categories, tags, meta_description, meta_keywords, schema_json, citations, faqs, key_takeaways, show_in_footer, image_alt, image_title, image_caption.

Publish a custom page

Custom pages render at the site root (/<slug>/). Reserved slugs (e.g. admin, blog, docs) are rejected.

curl -X POST https://<host>/api/pages/ \
  -H "X-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{
    "slug": "best-ielts-platform-canada",
    "title": "Best IELTS Platform in Canada",
    "html_content": "<h1>Best IELTS platform for Canadian institutes</h1><p>...</p>",
    "is_active": true,
    "seo_title": "Best IELTS Platform in Canada (2026)",
    "seo_description": "How to pick an IELTS/CELPIP platform for a Canadian coaching centre.",
    "faqs": [
      {"question": "Does it support CLB score mapping?", "answer": "Yes — CLB predictions are built in for CELPIP and PTE Core."}
    ]
  }'

Pages fields (create): slug (required), title (required), html_content, is_active, seo_title, seo_description, seo_keywords, og_title, og_description, og_image, schema_json, faqs.

Editing without the API

Both content types are also editable in the staff GUI, where the same FAQ / citations editors are available:

  • Blog posts/blog/manage/ (or Django Admin → Blog Posts).
  • Custom pages — the custom-pages dashboard (Django Admin → language-tests admin → Custom Pages), which now includes the FAQ editor card.

Endpoint summary

API Method & path Purpose
Blog POST /blog/api/posts/create/ Create a post
Blog GET /blog/api/posts/ List posts
Blog GET /blog/api/posts/<slug>/ Get a post
Blog PUT /blog/api/posts/<slug>/ Update a post
Blog DELETE /blog/api/posts/<slug>/delete/ Delete a post
Pages GET /api/pages/ List pages (?is_active=true)
Pages POST /api/pages/ Create a page
Pages GET / PUT / DELETE /api/pages/<slug>/ Get / update / delete a page