PolyCMS REST API Guide

Last updated on Jun 8, 2026 3:09 AM

REST API Overview

PolyCMS 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.

Live Demo: Try the API Explorer on the official demo site at polycms.polyxgo.com/admin/polycms/api_explorer.

Authentication

All API requests must be authenticated using an API Key.

To enable the API and generate a key:

  1. Navigate to Blog > Settings.
  2. Check the Enable REST API box.
  3. Check the Generate New API Key box and save settings.
  4. 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-Limit
  • X-RateLimit-Remaining
  • Retry-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_id
  • author_id
  • search
  • sort_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/pagesPUT /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/categoriesPUT /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/tagsPUT /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 title
  • mime β€” 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:

  1. Extension whitelist
  2. MIME validation
  3. Magic bytes verification
  4. Malicious code scanning
  5. Double-extension blocking
  6. 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:

  1. Physical file removal β€” deletes the original file and its thumbnail
  2. Database record deletion β€” removes entry from blog_media
  3. Reference cascade β€” clears feature_image and feature_image_id from 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.

Security & Data Isolation

Data Scope β€” CMS Tables Only

The REST API operates exclusively on PolyCMS data tables. It has zero access to any Perfex CRM core data (clients, invoices, projects, leads, staff passwords, etc.).

All API endpoints interact only with the following dedicated PolyCMS tables:

  • blog_posts / blog_post_meta / blog_post_tags
  • blog_pages / blog_page_meta
  • blog_categories
  • blog_tags
  • blog_media

The only CRM table referenced is staff (read-only, first/last name only) to display author names. No sensitive CRM data is ever exposed or writable through the API.

Authentication

  • API Key Required: Every request must include a valid API key via X-PolyCMS-API-Key HTTP header or ?api_key= query parameter.
  • Constant-Time Comparison: API key verification uses PHP hash_equals() to prevent timing attacks β€” the comparison takes the same amount of time regardless of how many characters match.
  • Cryptographic Key Generation: API keys are generated using random_bytes(24) (48 hex characters), prefixed with pcms_, providing 192 bits of entropy.
  • No Key = No Access: If no API key has been generated in Settings, all API requests are rejected.

SQL Injection Prevention

  • Zero Raw SQL: All database queries use CodeIgniter Active Record / Query Builder, which automatically escapes all values.
  • Parameter Binding: Filter values (status, category_id, author_id, search) are passed through CI's built-in escaping layer β€” never concatenated into query strings.
  • Integer Casting: All numeric parameters (IDs, pagination) are cast to (int) before use.
  • Whitelist Sorting: Sort columns are restricted by the model layer.

XSS & Content Injection Prevention

  • HTMLPurifier: All HTML content (post body, descriptions) submitted via API is processed through polycms_purify_content(), which uses:
  1. Perfex CRM's html_purify() powered by HTMLPurifier library
  2. Additional regex stripping of <script> tags
  3. Removal of inline JavaScript event handlers (onclick, onerror, etc.)
  • strip_tags(): All text-only fields (title, slug, status, visibility) are sanitized with strip_tags() β€” no HTML allowed.
  • JSON Output Encoding: All responses use json_encode() with JSON_UNESCAPED_UNICODE, preventing output injection.

Additional Security Measures

  • Global Kill Switch: The entire API can be disabled instantly via Settings toggle β€” all endpoints return 403 Forbidden when disabled.
  • BASEPATH Protection: The controller file includes defined('BASEPATH') or exit() to prevent direct script execution.
  • 6-Layer Media Upload Security: Extension whitelist, MIME validation, magic bytes, code scanning, double-extension blocking, GD reprocessing.
  • No Admin Operations: The API cannot modify settings, permissions, themes, plugins, or any system configuration.

Related Documentation

Launch Your CMS for Perfex CRM β€” Lifetime Access for Just $19
Create blogs, pages, themes, plugins, and manage everything from one powerful unified admin panel.

Early Adopter Advantage

Current pricing reflects the module’s present feature set. As additional plugins, themes, multilingual capabilities, builders (MTBuilder), and ecosystem integrations are released, pricing may be adjusted. Early customers secure today’s price while benefiting from future updates.