Files
magnus919_agent-skills/ghost/references/content-vs-admin-api.md
T
Magnus Hedemarkandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> cc7e61ed30 docs(ghost): thicken Admin API skill
Full lastfm-model rebuild of the ghost skill against current docs.ghost.org
research:

- Fix JWT signer correctness: hex-decode the secret half before HMAC-SHA256
  signing (official contract; literal-hex signing produced invalid tokens),
  document HS256 + kid header + aud /admin/ + 5-minute token window, add
  admin_api_audience() derivation and Ghost-scheme error handling with
  researched signatures (409 UPDATE_COLLISION, 404 non-public guidance,
  INVALID_AUTH_HEADER hint, 204 delete tolerance).
- Extend CLI surface: get-post, update-post (updated_at collision guard),
  delete-post, create-page, create-tag, posts pagination (--page/--order,
  meta.pagination surfaced), scheduled posting with --published-at guard;
  dry-run now previews method/URL/payload exactly as executed.
- Add 5 cited reference files (auth/basics, content-vs-admin split incl.
  draft-visibility asymmetry, endpoint guide, worked recipes, gotchas).
- Add scripts/test_ghost.py: 28 offline tests incl. fixed-vector JWT
  known-answer checks and jq-executed pipeline-consumability chains.
- Add evals/evals.json (6 cases incl. npm ghost-cli negative probe).
- Rewrite SKILL.md (155-line body) and README in lastfm model.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-26 21:24:20 -04:00

3.9 KiB

Content API vs Admin API: Which Plane, Which Key

Ghost exposes two REST APIs with different credentials, scopes, and content visibility. Picking the wrong one produces the classic failure: everything looks configured, yet drafts are nowhere to be found and nothing errors.

The split at a glance

Aspect Content API Admin API
Base path /ghost/api/content/ /ghost/api/admin/
Credential Content key as ?key=<RECORD_KEY> query param JWT in Authorization: Ghost <token>
Verbs GET only (Browse, Read) Full REST per resource
Scope Published posts/pages/tags/authors/tiers/settings Everything public plus drafts, scheduled posts, members, webhooks, images, themes
Key safety Safe for browsers and clients (public data only) Server-side only; signs mutations
Cacheability Designed to be cached/CDN-fronted Mutating; publish busts front-end caches
Typical consumers Site themes, headless frontends, mobile apps Editorial automation, migrations, scheduling bots

Both key types come from the same Custom Integration screen; an integration has a Content API key and an Admin API key side by side. They are not interchangeable.

Draft-visibility asymmetry (the trap)

The Content API delivers published content only. Its docs state the key "only ever provide[s] access to public data," and Ghost enforces this at the model layer: public-context post queries carry a non-overridable status:published filter.

Consequences worth internalizing:

  1. Drafts are unreachable via Content API regardless of key validity. A valid key does not make drafts visible; the request simply never matches them.
  2. It fails silent, not loud. Browsing with a valid Content key returns HTTP 200 with only published posts — an empty or partial list, no error, no hint. There is no 403 saying "you can't see drafts."
  3. Filtering does not bypass it. filter=status:draft against the Content API returns the same published collection; the disallowed filter is ignored rather than rejected.
  4. Direct reads of non-public posts 404. Reading /content/posts/<draft-id>/ behaves as if the post does not exist — consistent with the documented 404 category "data which is not public."

The bundled CLI is Admin-API-first precisely because of this asymmetry: ghost posts --status draft works only because it authenticates with the Admin JWT, which sees drafts, scheduled, and published posts alike.

Diagnostic checklist when "posts are missing"

  • Authenticated with the Content key? Switch to the Admin key workflow (GHOST_ADMIN_KEY); drafts will appear.
  • Using Admin and still missing them? Check filter=status: values (draft, scheduled, published) and page through with --page.
  • Post visible in Admin UI but 404s from your site code? That is the same asymmetry in reverse: unpublished content never appears on the public plane.

When to use which

Use the Content API for anything that renders your site to the world: headless frontend builds, static-site generators, search indexes, feeds, mobile apps. It is read-only, key-as-query-param, and cache-friendly.

Use the Admin API for anything that changes content or needs non-public data: creating and editing posts, publishing/scheduling, managing tags and pages, uploading images, working with drafts before they go live. Keep Admin keys out of browsers, client bundles, and CI logs.

A frequent pattern pairs both: editorial automation writes through Admin; the public site reads through Content plus CDN. If a workflow only ever reads published content, prefer Content — smaller blast radius, browser-safe key.

Sources