EM REST API CPT
WordPress plugin that stores external data in a private custom post type and exposes authenticated CRUD REST API endpoints.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/esmond-m/em-rest-api-cpt/archive/refs/heads/main.zipEM REST API CPT Plugin
A WordPress plugin that registers a custom post type (API Data) and exposes a set of authenticated REST API endpoints for creating, retrieving, and deleting entries programmatically.
Project: GitHub Repository
Author: esmondmccain.com
Features
- Registers a custom post type: API Data
- Three REST API endpoints:
POST,GET, andDELETE - Plugin-managed API key authentication via
X-API-Keyheader - Custom meta fields per entry:
source,received_at,external_id - Custom admin list columns for source, external ID, and received timestamp
- Admin settings page under API Data → Settings — view/regenerate the API key and see live endpoint reference with example cURL commands
- API key auto-generated on plugin activation
- Proper HTTP status codes and
WP_Errorresponses - Input sanitization on all fields (
sanitize_text_field,wp_kses_post) - Full REST route argument schema with types, defaults, and validation callbacks
Requirements
- WordPress 6.0+
- PHP 8.0+
Installation
- Download the latest release
- Upload and extract
em-rest-api-cpt.zipinto/wp-content/plugins/. - Activate via Plugins → Installed Plugins.
- Navigate to API Data → Settings to find your auto-generated API key.
Authentication
All endpoints require an X-API-Key header. The key is generated on activation and can be regenerated at any time from the settings page.
X-API-Key: <your-api-key>
Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
/wp-json/esmond-api/v1/receive |
Create a new API Data entry |
GET |
/wp-json/esmond-api/v1/entries |
List entries (filterable, paginated) |
DELETE |
/wp-json/esmond-api/v1/entries/{id} |
Permanently delete an entry |
Usage
Create an entry (POST)
Body (JSON):
{
"title": "Entry title",
"body": "Entry content here.",
"source": "my-app",
"external_id": "abc-123"
}
title(required) — Post titlebody(required) — Post content (HTML allowed viawp_kses_post)source(optional) — Origin identifier, e.g."crm","mobile-app"external_id(optional) — ID from the external system for cross-referencing
Example cURL:
curl -X POST https://yoursite.domain/wp-json/esmond-api/v1/receive \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"title":"My Entry","body":"Hello world.","source":"demo","external_id":"ext-001"}'
Response (201):
{
"success": true,
"message": "Entry created successfully.",
"data": {
"id": 42,
"title": "My Entry",
"source": "demo",
"external_id": "ext-001",
"received_at": "2026-05-07 14:30:00"
}
}
List entries (GET)
Query params (all optional):
| Param | Type | Default | Description |
|---|---|---|---|
source |
string | — | Filter by source identifier |
per_page |
int | 10 | Results per page (max 100) |
page |
int | 1 | Page number |
curl "https://yoursite.domain/wp-json/esmond-api/v1/entries?source=demo&per_page=5" \
-H "X-API-Key: YOUR_API_KEY"
Response (200):
{
"success": true,
"total": 12,
"total_pages": 3,
"page": 1,
"data": [
{
"id": 42,
"title": "My Entry",
"body": "Hello world.",
"source": "demo",
"external_id": "ext-001",
"received_at": "2026-05-07 14:30:00"
}
]
}
Delete an entry (DELETE)
curl -X DELETE https://yoursite.domain/wp-json/esmond-api/v1/entries/42 \
-H "X-API-Key: YOUR_API_KEY"
Response (200):
{
"success": true,
"message": "Entry #42 has been permanently deleted."
}
File Structure
em-rest-api-cpt/
├── em-rest-api-cpt.php # Plugin bootstrap, singleton, activation hook
├── classes/
│ ├── register-cpt.php # CPT registration, meta fields, admin columns
│ ├── admin-settings.php # Settings page, API key management
│ └── make-endpoint.php # REST routes: POST /receive, GET /entries, DELETE /entries/{id}
├── build/
└── package.json
Error Responses
All errors return a standard WP REST error shape with an appropriate HTTP status code:
{
"code": "rest_forbidden",
"message": "Invalid or missing API key. Pass your key in the X-API-Key header.",
"data": { "status": 401 }
}
| Status | Code | Meaning |
|---|---|---|
| 401 | rest_forbidden |
Missing or invalid API key |
| 404 | rest_not_found |
Entry ID does not exist |
| 500 | rest_cannot_create |
Post insert failed |
| 500 | rest_api_key_not_configured |
No API key set in options |
Maintained by Esmond McCain.