Ghost Admin API — Authentication, CRUD Operations and Webhook Management
In this tutorial, you'll learn how to use the Ghost Admin API — an authenticated REST API that lets you create, read, update, and delete posts, pages, tags, members, and webhooks programmatically from server-side applications.
What You'll Learn
- The Admin API architecture and authentication
- Creating and using Admin API keys
- CRUD operations for posts, pages, and tags
- Managing members via the Admin API
- Creating and managing webhooks
- Working with Content API vs Admin API
- Error handling and response codes
- Using the official Ghost Admin API client library
- Security best practices for Admin API keys
Why It Matters
The Admin API unlocks Ghost's full potential as a programmable CMS. You can automate content publishing (import articles from a news wire), sync member data with an external CRM, build custom admin interfaces, or create content programmatically from other systems. While the Content API is for reading public content, the Admin API is for managing everything — and it requires careful handling because it provides full write access to your site.
Real-World Use
A publishing company uses an automated workflow: editors write articles in Google Docs, a custom integration fetches the content via Google Docs API, creates a new post in Ghost via the Admin API with the correct tags and feature image, and schedules it for publication. The entire pipeline runs without anyone logging into the Ghost admin.
Learning Path
flowchart LR A["Content API"] --> B["Admin API
You are here"]:::current B --> C["Webhooks"] C --> D["Custom Integrations"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Admin API Overview
The Admin API provides full programmatic access to your Ghost site.
Key Characteristics
- Authenticated: Requires a secret Admin API key
- Full CRUD: Create, read, update, delete for all resources
- Server-side only: Admin API keys must NEVER be exposed in client-side code
- Versioned: Uses Ghost API version headers
Base URL
https://yoursite.com/ghost/api/admin/
Authentication
Admin API authentication uses a shared secret key that you create in Ghost admin.
Getting Your Admin API Key
- Go to
Settings > Integrations > Add custom integration. - Name the integration (e.g., "Automation Script").
- Copy the Admin API Key.
API Key Format
The key has two parts separated by a colon:
64a1b2c3d4e5f6:abcdef1234567890abcdef1234567890
- Left part: Key ID
- Right part: Secret
How Authentication Works
The Admin API uses Ghost's own authentication scheme. You generate a JWT (JSON Web Token) signed with your secret key.
const jwt = require('jsonwebtoken');
function getAdminToken(key) {
const [id, secret] = key.split(':');
const token = jwt.sign(
{
kid: id,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 5 * 60 // 5 minutes
},
Buffer.from(secret, 'hex'),
{ algorithm: 'HS256' }
);
return token;
}
Using the Token
Include the token as a Bearer token in the Authorization header:
Authorization: Ghost YOUR_TOKEN
Using the Official Client Library
The official @tryghost/admin-api npm package handles authentication automatically:
const GhostAdminAPI = require('@tryghost/admin-api');
const api = new GhostAdminAPI({
url: 'https://yoursite.com',
key: 'YOUR_ADMIN_API_KEY',
version: 'v5.0'
});
CRUD Operations
Posts
// Create a post
const post = await api.posts.add({
title: 'My New Post',
html: '<p>Content here</p>',
status: 'draft',
tags: ['javascript', 'tutorial'],
feature_image: 'https://example.com/image.jpg'
});
// Read a post
const post = await api.posts.read({ id: 'POST_ID' });
// Read all posts
const posts = await api.posts.browse({ limit: 10 });
// Update a post
await api.posts.edit({
id: 'POST_ID',
title: 'Updated Title',
status: 'published'
});
// Delete a post
await api.posts.delete({ id: 'POST_ID' });
Pages
// Create a page
const page = await api.pages.add({
title: 'About Us',
html: '<p>About page content</p>',
visibility: 'public'
});
// Browse pages
const pages = await api.pages.browse({ limit: 50 });
Tags
// Create a tag
const tag = await api.tags.add({
name: 'React',
description: 'React.js tutorials and articles'
});
// Update a tag
await api.tags.edit({
id: 'TAG_ID',
description: 'Updated description'
});
Members
// Create a member
const member = await api.members.add({
name: 'John Doe',
email: 'john@example.com',
labels: ['newsletter', 'website-signup'],
note: 'Signed up from the contact page'
});
// Update member subscription
await api.members.edit({
id: 'MEMBER_ID',
subscriptions: [{
id: 'SUB_ID',
status: 'canceled'
}]
});
// Browse members with filter
const paidMembers = await api.members.browse({
filter: 'status:paid',
limit: 100
});
Settings
// Get settings
const settings = await api.settings.browse();
// Update settings
await api.settings.edit({
title: 'New Site Title',
description: 'Updated description'
});
Webhooks
// Create a webhook
const webhook = await api.webhooks.add({
name: 'Post Published',
event: 'post.published',
target_url: 'https://myservice.com/ghost-webhook',
integration: 'INTEGRATION_ID'
});
// Browse webhooks
const webhooks = await api.webhooks.browse();
Admin API Endpoints
| Resource | Endpoint | Methods |
|---|---|---|
| Posts | /posts/ | GET, POST, PUT, DELETE |
| Pages | /pages/ | GET, POST, PUT, DELETE |
| Tags | /tags/ | GET, POST, PUT, DELETE |
| Members | /members/ | GET, POST, PUT, DELETE |
| Users | /users/ | GET, POST, PUT |
| Settings | /settings/ | GET, PUT |
| Webhooks | /webhooks/ | GET, POST, PUT, DELETE |
| Themes | /themes/ | GET, POST, DELETE |
| Tiers | /tiers/ | GET, POST, PUT, DELETE |
Direct HTTP Requests
If you are not using a Node.js environment, make direct HTTP requests:
# Generate token (pseudo-code)
TOKEN=$(generate_admin_jwt "$ADMIN_KEY")
# Create a post
curl -X POST https://yoursite.com/ghost/api/admin/posts/ \
-H "Authorization: Ghost $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept-Version: v5.0" \
-d '{
"posts": [{
"title": "API Created Post",
"status": "draft"
}]
}'
Response Format
All Admin API responses wrap data in resource-specific keys:
{
"posts": [{ ... }],
"meta": { "pagination": { ... } }
}
Requests with a single ID return a single object array. List requests include pagination metadata.
Error Handling
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created successfully |
| 204 | Deleted successfully |
| 400 | Bad request (invalid parameters) |
| 401 | Unauthorized (missing or invalid key) |
| 404 | Resource not found |
| 422 | Validation error |
| 500 | Server error |
Error Response Format
{
"errors": [{
"message": "Validation error",
"type": "ValidationError",
"context": "Title is required"
}]
}
Handling Errors in Code
try {
await api.posts.add({ title: '' });
} catch (error) {
if (error.statusCode === 422) {
console.error('Validation error:', error.context);
} else if (error.statusCode === 401) {
console.error('Authentication error - check your API key');
}
}
Content API vs Admin API
| Aspect | Content API | Admin API |
|---|---|---|
| Authentication | API key in URL | JWT from Admin key |
| Access level | Public, read-only | Authenticated, full CRUD |
| Content visibility | Public only | All content including drafts |
| Use in client-side | Safe | Never |
| Rate Limiting | Moderate | Standard |
Security Best Practices
- Never expose Admin API keys: Do not include Admin API keys in client-side JavaScript, public repositories, or anywhere a user could see them.
- Use environment variables: Store keys in environment variables, not in code.
- Create separate integrations: Use different integrations for different purposes (one for automation, one for CRM sync). This makes auditing and revocation easier.
- Rotate keys periodically: Regenerate integration keys if they may have been compromised.
- Use short-lived tokens: The JWT token expires after 5 minutes. Generate a new token for each batch of operations.
Common Mistakes
Exposing Admin API keys in client-side code: Admin key exposure allows anyone to delete your entire site. Never include Admin keys in browser JavaScript, mobile apps, or public repositories.
Not using the API version header: Admin API responses are versioned. Always include
Accept-Version: v5.0header. Without it, you may get unexpected response formats.Incorrect JWT token generation: The Admin API authentication requires a specific JWT format. If your token is malformed, you get 401 errors. Use the official client library to avoid this.
Rate limiting without backoff: Sending too many requests too quickly triggers rate limiting. Implement exponential backoff in your scripts.
Forgetting pagination: Browse endpoints return paginated results. If you do not loop through pages, you only get the first page. Always check
meta.paginationand continue fetching.
Practice Questions
How does Admin API authentication work? Answer: Admin API uses a JWT (JSON Web Token) signed with your Admin API key. The key has two parts: an ID and a secret. You create a JWT with the key ID in the header and sign it with the hex-decoded secret. This token is sent as a Bearer token in the Authorization header.
What is the difference between
browseandreadoperations? Answer:browsefetches a list of resources (e.g., all posts) with optional filtering and pagination.readfetches a single resource by ID or slug. Both are GET requests but with different response structures.Why should Admin API keys never be exposed in client-side code? Answer: Admin API keys provide full CRUD access to your Ghost site, including the ability to create, modify, and delete all content and settings. If exposed in client-side code, anyone can extract the key and gain unauthorized access to your site.
Challenge: Write a Node.js script that uses the Admin API to: create 5 new posts with different tags, update the site title, create a Webhook for the "post.published" event, and verify all operations by fetching and displaying the updated data.
FAQ
Mini Project
Your task: Build an automated content publishing pipeline.
- Create an Admin API integration in Ghost.
- Write a Node.js script that:
- Reads a list of articles from a JSON file
- Creates each article as a draft post in Ghost with correct tags
- Uploads a feature image for each post
- Schedules the posts to publish over the next week
- Create a webhook that notifies a Slack channel when each post is published.
- Test the pipeline on a local Ghost site.
- Document the setup for reuse.
This exercise gives you a real-world automation pipeline using the Admin API.
What's Next
Now that you understand the Admin API, learn about webhooks for real-time notifications:
Continue to Lesson 27: Webhooks — Post published, member added, and custom webhook targets.
Related lessons:
- Custom Integrations — Zapier, Slack, and SSO
- Content API — Read-only content access
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro