How Ghost Content API Authentication Differs from the Admin API

The Ghost Content API authenticates using a plain API key passed as a query string parameter, while the Admin API requires a signed JWT token verified against an admin-type API key secret, supporting both header-based and URL-based authentication for different use cases.

Ghost, the open-source publishing platform maintained by TryGhost/Ghost, exposes two distinct public entry points for external clients. Understanding how Ghost Content API authentication differs from Admin API authentication is critical for securing your headless CMS implementation and choosing the right integration pattern for your application.

Core Authentication Methods

Ghost enforces fundamentally different security models for reading versus managing content. While both APIs require valid API keys stored in the database, the mechanisms for presenting and verifying those credentials diverge significantly.

Content API: Plain API Key

The Content API provides read-only access to published resources such as posts, tags, and pages. According to the source code in ghost/core/core/server/services/auth/api-key/content.js, authentication relies on a simple secret key passed via the ?key= query parameter. The middleware extracts req.query.key, queries the models.ApiKey table to verify the secret exists, and confirms the key's type attribute equals "content". If validation fails, the pipeline immediately returns a 401 Unauthorized response. Upon success, the resolved key object attaches to req.api_key for downstream permission checks.

Admin API: JWT Token Validation

The Admin API enables full-stack management capabilities including creating, editing, and deleting resources. As implemented in ghost/core/core/server/services/auth/api-key/admin.js, this endpoint requires a JSON Web Token (JWT) signed using the secret of an API key with type: "admin". Clients must supply the token either in an Authorization: Ghost <JWT> header or, for scheduled publish workflows, as a ?token= query parameter. The JWT must include a kid (key ID) header claim referencing the specific admin key, plus an aud (audience) claim matching the request path. Tokens expire after 5 minutes by default, providing short-lived credentials that reduce the blast radius of potential leaks.

Key Verification Flows

The authentication pipelines differ in complexity due to their distinct security requirements. The Content API prioritizes performance and cacheability, while the Admin API implements cryptographic verification to protect destructive operations.

Content API Verification Pipeline

The Content API authentication flow follows these discrete steps:

  1. Middleware checks for the presence of req.query.key in the incoming request
  2. The system queries the database for a matching record in models.ApiKey using the provided secret
  3. Validation confirms the key exists and its type field equals "content"; otherwise, rejection occurs
  4. The resolved key attaches to req.api_key for subsequent permission evaluation

This lightweight approach enables fast, cache-friendly requests suitable for public content delivery.

Admin API Verification Pipeline

The Admin API implements a robust verification sequence to ensure only authorized administrative actions proceed:

  1. Middleware extracts the JWT from either the Authorization header or the URL query string (?token=)
  2. The token decodes to read the kid (key ID) header claim, identifying which admin key signed the request
  3. The system retrieves the corresponding admin API key record from the database
  4. Cryptographic verification validates the JWT signature, confirms the audience matches the request path, and checks the expiry timestamp (default 5-minute window)
  5. Upon successful validation, the middleware attaches the key to req.api_key and, if present, the associated user to req.user

This multi-layer verification prevents replay attacks and ensures tokens are bound to specific endpoints via audience claims.

Rate Limiting and Security Hardening

Despite their different authentication mechanisms, both APIs share a common protection layer. After successful key resolution, both pipelines invoke limitService.isLimited('customIntegrations') to enforce rate limits against custom integration abuse. However, the Admin API's JWT-based architecture provides superior security characteristics through short-lived tokens, mandatory audience validation, and cryptographic signing—essential protections for write operations that modify site state.

Practical Implementation Examples

Requesting Data from the Content API

To fetch published content, append your Content API key to any read endpoint:

const fetch = require('node-fetch');

const CONTENT_KEY = 'your_content_api_key_here';
const url = `https://your-site.com/ghost/api/v5/content/posts/?key=${CONTENT_KEY}`;

const response = await fetch(url);
const data = await response.json();
console.log(data.posts);

Authenticating Admin API Requests

Admin API requests require generating a JWT signed with your admin key secret:

const jwt = require('jsonwebtoken');
const fetch = require('node-fetch');

const ADMIN_KEY_SECRET = 'your_admin_key_secret_here';
const ADMIN_KEY_ID = 'your_admin_key_id_here';

const token = jwt.sign(
  {},
  ADMIN_KEY_SECRET,
  {
    header: { kid: ADMIN_KEY_ID },
    expiresIn: '5m',
    audience: '/ghost/api/v5/admin/posts/'
  }
);

const response = await fetch('https://your-site.com/ghost/api/v5/admin/posts/', {
  headers: {
    'Authorization': `Ghost ${token}`,
    'Content-Type': 'application/json'
  }
});
const data = await response.json();

For scheduled publishing workflows that require URL-based authentication, pass the same JWT via the ?token= query parameter instead of the header.

Summary

  • Ghost Content API authentication uses a static query-string key (?key=) with database verification against keys of type: "content" as defined in ghost/core/core/server/services/auth/api-key/content.js
  • Admin API authentication requires short-lived JWT tokens signed with admin key secrets, supporting both Authorization: Ghost <JWT> headers and ?token= URL parameters, implemented in ghost/core/core/server/services/auth/api-key/admin.js
  • The Content API pipeline validates key existence and type, storing results on req.api_key, while the Admin API additionally verifies cryptographic signatures, audience claims, and expiry timestamps
  • Both APIs enforce customIntegrations rate limiting after successful authentication, but the Admin API's JWT architecture provides stronger security guarantees for write operations
  • Permission distinctions between public reads and private administrative actions are handled in ghost/core/core/server/api/endpoints/utils/permissions.js

Frequently Asked Questions

Can I use a Content API key to access Admin API endpoints?

No. The authentication middleware explicitly checks the type field in the models.ApiKey database record. Content API keys have type: "content", and attempting to use them against Admin API endpoints results in a 401 Unauthorized response because the JWT verification pipeline requires keys with type: "admin".

Why does the Admin API use JWT instead of a simple API key like the Content API?

The Admin API performs destructive write operations that require stronger security guarantees. JWT tokens provide short-lived credentials (default 5-minute expiry), audience validation binding tokens to specific endpoints, and cryptographic signing that prevents replay attacks. This design allows Ghost to safely expose administrative functions via HTTP while mitigating risks associated with long-lived credentials.

How long do Admin API tokens remain valid, and can this be changed?

Admin API tokens expire after 5 minutes by default, as implemented in the token generation logic within ghost/core/core/server/services/auth/api-key/admin.js. While you can technically specify a different expiresIn value when generating the JWT, doing so degrades security and may violate Ghost's integration guidelines. Always generate fresh tokens for each administrative request or session.

Is it safe to expose the Content API key in frontend JavaScript code?

Generally yes, but with caveats. Content API keys are read-only credentials designed for public consumption, making them suitable for browser-based JavaScript applications. However, exposing any API key exposes your site to potential rate-limiting abuse or scraping. For production applications, consider proxying Content API requests through your own backend or using Ghost's built-in customIntegrations rate limiting to mitigate abuse.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →