REST API Overview #
CMS includes a robust REST API, allowing you to use it as a headless CMS. You can fetch content to display on external platforms, mobile apps, or separate frontend frameworks.
The API provides full CRUD capabilities for Posts, Pages, Categories, Tags, and Media. To test endpoints interactively without any external tools, use the built-in API Explorer.
Authentication #
All API requests must be authenticated using an API Key.
To enable the API and generate a key:
- Navigate to Blog > Settings.
- Check the Enable REST API box.
- Check the Generate New API Key box and save settings.
- Copy the generated API key.
Pass the API key in your requests using either:
- HTTP Header:
X-PolyCMS-API-Key: pcms_your_key_here - Query Parameter:
?api_key=pcms_your_key_here
Rate Limiting #
The API includes configurable rate limiting to protect your server from abuse. The limits are tracked per IP address.
By default:
- Read Operations (GET): 60 requests per minute.
- Write Operations (POST, PUT, DELETE): 30 requests per minute.
These limits can be adjusted in Blog > Settings. The API responds with standard rate-limiting headers:
X-RateLimit-LimitX-RateLimit-RemainingRetry-After
If limits are exceeded, you will receive a 429 Too Many Requests HTTP status code.
Endpoints #
All endpoints are prefixed with /cms/api/v1/.
Posts #
List Posts:
GET /cms/api/v1/posts
Query Parameters:
page(default: 1)per_page(default: 10, max: 100)status(e.g., published, draft)category_idauthor_idsearchsort_by(default: created_at)sort_order(default: DESC)
Get Single Post:
GET /cms/api/v1/posts/{id}
Create Post:
POST /cms/api/v1/posts
Request Body (JSON):
{
"title": "My New Post",
"content": "This is the content...",
"status": "published",
"category_ids": [1, 2]
}
Update Post:
PUT /cms/api/v1/posts/{id}
Delete Post:
DELETE /cms/api/v1/posts/{id}
Pages #
List Pages:
GET /cms/api/v1/pages
Get Single Page:
GET /cms/api/v1/pages/{id}
Create / Update / Delete Pages:
POST /cms/api/v1/pages | PUT /cms/api/v1/pages/{id} | DELETE /cms/api/v1/pages/{id} |
|---|
Categories #
List Categories (Returns Tree Structure):
GET /cms/api/v1/categories
Full CRUD:
POST /cms/api/v1/categories | PUT /cms/api/v1/categories/{id} | DELETE /cms/api/v1/categories/{id} |
|---|
Tags #
List Tags:
GET /cms/api/v1/tags
Full CRUD:
POST /cms/api/v1/tags | PUT /cms/api/v1/tags/{id} | DELETE /cms/api/v1/tags/{id} |
|---|
Media #
List Media (Database-backed, Paginated):
GET /cms/api/v1/media
Query Parameters:
page(default: 1)per_page(default: 20, max: 100)search— Search by filename, alt_text, or titlemime— Filter by MIME type prefix (e.g.,image)
All media are stored in the blog_media database table with metadata: filename, path, URL, alt_text, title, caption, MIME type, file_size, width, height, and author.
Upload Media:
POST /cms/api/v1/media
Upload images via multipart/form-data with the field name file. The API enforces 6 layers of security:
- Extension whitelist
- MIME validation
- Magic bytes verification
- Malicious code scanning
- Double-extension blocking
- GD image reprocessing to strip embedded payloads
On success, the response includes a media_id for linking to posts/pages via feature_image_id.
Delete Media:
DELETE /cms/api/v1/media/{path}
Performs a 3-layer cleanup:
- Physical file removal — deletes the original file and its thumbnail
- Database record deletion — removes entry from
blog_media - Reference cascade — clears
feature_imageandfeature_image_idfrom any posts/pages that referenced this media
Media Database Architecture #
New in v1.1.0: All uploaded media are tracked in the blog_media table.
Feature Image Relationship #
Posts and Pages now have a feature_image_id column linking to blog_media.id. The original feature_image path string is retained for fast rendering without JOIN queries.
Auto-Sync Migration #
When the module is activated (or reactivated), existing files are automatically synced into the database and feature_image_id is populated for matching posts. This migration is idempotent — safe to run multiple times.
Response Format #
All successful API responses return a standard JSON structure:
{
"success": true,
"message": "Posts retrieved",
"data": [
{
"id": 1,
"title": "Hello World",
...
}
],
"meta": {
"total": 1,
"page": 1,
"per_page": 10,
"pages": 1
}
}
Errors return success: false with an appropriate message and HTTP status code.