Base URLs
| Production | http://blogware.site/api/v1 |
| Development | http://localhost/blogware/public_html/api/v1 |
API Version: 1.1.1 | Format: JSON
Authentication
The API supports two authentication methods:
API Key Authentication
GET /api/v1/posts HTTP/1.1
Host: blogware.site
X-API-Key: your-api-key-here
Bearer Token Authentication
GET /api/v1/posts HTTP/1.1
Host: blogware.site
Authorization: Bearer your-bearer-token
Authentication Requirements
| Endpoint Type |
Authentication Required |
| Read (GET) - Public content | No |
| Create/Update/Delete (POST/PUT/DELETE) | Yes |
API Information
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/ | No | Get API metadata and available endpoints |
Example Response
{
"success": true,
"status": 200,
"message": "Welcome to Blogware RESTful API",
"data": {
"name": "Blogware RESTful API",
"version": "1.1.1",
"description": "RESTful API for Blogware content management system",
"base_url": "/api/v1",
"authentication": {
"type": "API Key or Bearer Token",
"header": "X-API-Key or Authorization: Bearer ",
"required": true
}
},
"_links": {
"self": { "href": "http://blogware.site/api/v1", "rel": "self", "type": "GET" },
"posts": { "href": "http://blogware.site/api/v1/posts", "rel": "posts", "type": "GET" },
"categories": { "href": "http://blogware.site/api/v1/categories", "rel": "categories", "type": "GET" },
"comments": { "href": "http://blogware.site/api/v1/comments", "rel": "comments", "type": "GET" },
"archives": { "href": "http://blogware.site/api/v1/archives", "rel": "archives", "type": "GET" },
"search": { "href": "http://blogware.site/api/v1/search?q={query}", "rel": "search", "type": "GET", "templated": true },
"openapi": { "href": "http://blogware.site/api/v1/openapi.json", "rel": "service-desc", "type": "application/json" }
}
}
Permission Levels
| Level |
Create Posts |
Edit Posts |
Delete Posts |
Manage Categories |
Moderate Comments |
| administrator | | | | | |
| editor | | | | | |
| author | | (own only) | | | |
| subscriber | | | | | |
Rate Limiting
API requests are rate limited to ensure fair usage and prevent abuse. Rate limiting is applied per-client using IP address, API key, or Bearer token as the identifier.
| Endpoint Type |
Limit |
Window |
| Read (GET) | 60 requests | 60 seconds |
| Write (POST/PUT/DELETE/PATCH) | 20 requests | 60 seconds |
| Header |
Description |
X-RateLimit-Limit | Maximum requests allowed per window |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | Unix timestamp when the rate limit resets |
Retry-After | Seconds to wait before retrying (only on 429 responses) |
If you exceed the rate limit, you'll receive a 429 Too Many Requests response.
Posts API
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/posts | No | List published posts |
| GET | /api/v1/posts/{id} | No | Get single post |
| GET | /api/v1/posts/{id}/comments | No | Get post comments |
| POST | /api/v1/posts | Yes | Create post |
| PUT | /api/v1/posts/{id} | Yes | Update post |
| PATCH | /api/v1/posts/{id} | Yes | Partially update post |
| DELETE | /api/v1/posts/{id} | Yes | Delete post |
Query Parameters (List Posts)
| Parameter |
Type |
Default |
Description |
page | integer | 1 | Page number |
per_page | integer | 10 | Items per page (max: 100) |
sort_by | string | ID | Sort field (ID, post_date, post_modified, post_title) |
sort_order | string | DESC | Sort direction (ASC, DESC) |
Path Parameters (Get Single Post)
| Parameter |
Type |
Description |
id | integer | Post ID |
Example Paginated Response
{
"success": true,
"status": 200,
"data": [...],
"pagination": {
"current_page": 1,
"per_page": 10,
"total_items": 50,
"total_pages": 5,
"has_next_page": true,
"has_previous_page": false
},
"_links": {
"self": { "href": "http://blogware.site/api/v1/posts?page=1", "rel": "self", "type": "GET" },
"next": { "href": "http://blogware.site/api/v1/posts?page=2", "rel": "next", "type": "GET" },
"last": { "href": "http://blogware.site/api/v1/posts?page=5", "rel": "last", "type": "GET" }
}
}
Create Post - Request Body
{
"post_title": "My New Post",
"post_content": "Full content of the post",
"post_summary": "Optional summary",
"post_status": "draft",
"post_visibility": "public",
"post_tags": "php, api",
"comment_status": "open",
"topics": [1, 2]
}
Required Fields
post_title (string)
post_content (string)
Optional Fields
post_summary (string)
post_status (string: "publish", "draft")
post_visibility (string: "public", "private", "protected")
post_tags (string, comma-separated)
comment_status (string: "open", "closed")
topics (array of integers)
Query Parameters (Get Comments for Post)
| Parameter |
Type |
Default |
Description |
page | integer | 1 | Page number |
per_page | integer | 10 | Items per page |
Categories API
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/categories | No | List categories |
| GET | /api/v1/categories/{id} | No | Get category |
| GET | /api/v1/categories/{id}/posts | No | Get posts in category |
| POST | /api/v1/categories | Yes | Create category |
| PUT | /api/v1/categories/{id} | Yes | Update category |
| PATCH | /api/v1/categories/{id} | Yes | Partially update category |
| DELETE | /api/v1/categories/{id} | Yes | Delete category |
Query Parameters (List Categories)
| Parameter |
Type |
Default |
Description |
page | integer | 1 | Page number |
per_page | integer | 10 | Items per page |
sort_by | string | ID | Sort field |
sort_order | string | DESC | Sort direction |
Example Response (List Categories)
{
"success": true,
"status": 200,
"data": [
{
"id": 1,
"title": "Technology",
"slug": "technology",
"status": "Y",
"post_count": 15,
"url": "http://blogware.site/category/technology"
}
],
"pagination": {...}
}
Create Category - Request Body
{
"topic_title": "Category Name",
"topic_status": "Y"
}
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/comments | No | List approved comments |
| GET | /api/v1/comments/{id} | No | Get comment |
| POST | /api/v1/comments | No | Submit comment |
| PUT | /api/v1/comments/{id} | Yes | Update comment |
| PATCH | /api/v1/comments/{id} | Yes | Partially update comment |
| DELETE | /api/v1/comments/{id} | Yes | Delete comment |
Query Parameters (List Comments)
| Parameter |
Type |
Description |
post_id | integer | Filter by post ID |
page | integer | Page number |
per_page | integer | Items per page |
sort_by | string | Sort field |
sort_order | string | Sort direction |
Create Comment - Request Body
{
"comment_author_name": "John Doe",
"comment_author_email": "[email protected]",
"comment_content": "Great article!",
"comment_post_id": 1,
"comment_parent_id": 0
}
Note: Comments are submitted with 'pending' status for moderation.
Archives API
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/archives | No | List archive dates |
| GET | /api/v1/archives/{year} | No | Posts from year |
| GET | /api/v1/archives/{year}/{month} | No | Posts from month |
Example Response (List Archives)
{
"success": true,
"status": 200,
"data": {
"archives": [
{
"year": 2024,
"months": [
{
"month": 6,
"month_name": "June",
"post_count": 5
}
],
"total_posts": 25
}
],
"total_years": 3
}
}
Path Parameters
| Parameter |
Type |
Description |
year | integer | Year (e.g., 2024) |
month | integer | Month (1-12) |
Search API
| Method |
Endpoint |
Auth |
Description |
| GET | /api/v1/search | No | Search all content (posts + pages) |
| GET | /api/v1/search/posts | No | Search posts only |
| GET | /api/v1/search/pages | No | Search pages only |
Search Parameters
| Parameter |
Type |
Required |
Description |
q | string | Yes | Search keyword (min 2, max 100 chars) |
type | string | No | all, posts, or pages (default: all) |
Example Request
GET /api/v1/search?q=cicero&type=all
Query Parameters
All list endpoints support the following parameters:
| Parameter |
Type |
Default |
Description |
page | integer | 1 | Page number |
per_page | integer | 10 | Items per page (max: 100) |
sort_by | string | ID | Sort field |
sort_order | string | DESC | Sort direction (ASC/DESC) |
Success Response
{
"success": true,
"status": 200,
"message": "Operation description",
"data": { ... }
}
Paginated Response
{
"success": true,
"status": 200,
"data": [...],
"pagination": {
"current_page": 1,
"per_page": 10,
"total_items": 50,
"total_pages": 5,
"has_next_page": true,
"has_previous_page": false
}
}
Error Response
{
"success": false,
"status": 400,
"error": {
"code": "BAD_REQUEST",
"message": "Error description"
}
}
HATEOAS (Hypermedia as the Engine of Application State)
All API responses include HATEOAS links following RFC 5988 (Web Linking). This allows clients to discover available actions dynamically without hardcoding URLs.
Common Link Relations
| Relation |
Description |
self | The current resource URL |
collection | The parent collection URL |
first | First page of paginated results |
prev | Previous page of paginated results |
next | Next page of paginated results |
last | Last page of paginated results |
canonical | The canonical HTML URL for the resource |
comments | Comments for a post |
post | The parent post for a comment |
posts | Posts in a category |
year | Year archive for a month |
search | Search endpoint (templated URL) |
service-desc | OpenAPI specification URL |
Example Single Resource with HATEOAS
{
"success": true,
"status": 200,
"data": {
"id": 1,
"title": "My First Blog Post",
"slug": "my-first-blog-post"
},
"_links": {
"self": { "href": "http://blogware.site/api/v1/posts/1", "rel": "self", "type": "GET" },
"comments": { "href": "http://blogware.site/api/v1/posts/1/comments", "rel": "comments", "type": "GET" },
"canonical": { "href": "http://blogware.site/post/1/my-first-blog-post", "rel": "canonical", "type": "text/html" },
"collection": { "href": "http://blogware.site/api/v1/posts", "rel": "collection", "type": "GET" }
}
}
HTTP Status Codes
| Code |
Meaning |
| 200 | OK |
| 201 | Created |
| 204 | No Content |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 405 | Method Not Allowed |
| 409 | Conflict |
| 422 | Unprocessable Entity |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
Error Codes
| Code |
Description |
BAD_REQUEST | Invalid request parameters |
UNAUTHORIZED | Authentication required |
FORBIDDEN | Insufficient permissions |
NOT_FOUND | Resource not found |
CONFLICT | Resource already exists |
VALIDATION_ERROR | Validation failed |
RATE_LIMIT_EXCEEDED | Too many requests |
INTERNAL_SERVER_ERROR | Server error |
SDK Examples
JavaScript / Fetch
const baseUrl = 'http://blogware.site/api/v1';
// Get posts
const response = await fetch(`${baseUrl}/posts`);
const data = await response.json();
// Get single post
const post = await fetch(`${baseUrl}/posts/1`);
// Create comment (no auth required)
const comment = await fetch(`${baseUrl}/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
comment_author_name: 'John Doe',
comment_author_email: '[email protected]',
comment_content: 'Great article!',
comment_post_id: 1
})
});
PHP
$baseUrl = 'http://blogware.site/api/v1';
// Get posts
$response = file_get_contents($baseUrl . '/posts');
$posts = json_decode($response, true);
// Get posts with authentication
$context = stream_context_create([
'http' => [
'header' => "X-API-Key: your-api-key\r\n"
]
]);
$response = file_get_contents($baseUrl . '/posts', false, $context);
Python
import requests
base_url = 'http://blogware.site/api/v1'
# Get posts
response = requests.get(f'{base_url}/posts')
posts = response.json()
# Get posts with authentication
headers = {'X-API-Key': 'your-api-key'}
response = requests.get(f'{base_url}/posts', headers=headers)
# Create comment
data = {
'comment_author_name': 'John Doe',
'comment_author_email': '[email protected]',
'comment_content': 'Great article!',
'comment_post_id': 1
}
response = requests.post(f'{base_url}/comments', json=data)
cURL
# Get posts
curl http://blogware.site/api/v1/posts
# Get posts with authentication
curl -H "X-API-Key: your-api-key" http://blogware.site/api/v1/posts
# Create comment
curl -X POST http://blogware.site/api/v1/comments \
-H "Content-Type: application/json" \
-d '{
"comment_author_name": "John Doe",
"comment_author_email": "[email protected]",
"comment_content": "Great article!",
"comment_post_id": 1
}'
OpenAPI Specification
The complete OpenAPI 3.0 specification is available for download:
Use these files to generate client SDKs, validate API responses, import into API testing tools (Postman, Swagger UI), or auto-generate documentation.
Using with Swagger UI
To view the API documentation in Swagger UI:
- Copy the
API_OPENAPI.json file to a web server
- Navigate to Swagger Editor
- Paste the JSON content
- Explore the interactive API documentation
Using with Postman
To import into Postman:
- Open Postman
- Click Import
- Select "Import from link"
- Enter:
http://blogware.site/docs/API_OPENAPI.json
Creating API Controllers
Step 1: Create Controller
<?php
class MyResourceApiController extends ApiController
{
private $resourceDao;
public function __construct()
{
parent::__construct();
$this->resourceDao = new MyResourceDao();
}
public function index($params = [])
{
$this->requiresAuth = false;
$pagination = $this->getPagination($params);
try {
$resources = $this->resourceDao->findAll($pagination);
$total = $this->resourceDao->count();
ApiResponse::paginated($resources, $pagination['page'], $pagination['per_page'], $total);
} catch (\Throwable $e) {
ApiResponse::error($e->getMessage(), 500, 'FETCH_ERROR');
}
}
public function show($params = [])
{
$id = isset($params[0]) ? (int)$params[0] : 0;
if (!$id) {
ApiResponse::badRequest('ID is required');
return;
}
$resource = $this->resourceDao->findById($id);
if (!$resource) {
ApiResponse::notFound('Resource not found');
return;
}
ApiResponse::success($resource);
}
public function store($params = [])
{
$this->requiresAuth = true;
if (!$this->hasPermission(['administrator'])) {
ApiResponse::forbidden('Permission denied');
return;
}
$validationErrors = $this->validateRequired($this->requestData, ['name']);
if ($validationErrors) {
ApiResponse::unprocessableEntity('Validation failed', $validationErrors);
return;
}
$id = $this->resourceDao->create($this->requestData);
ApiResponse::created(['id' => $id], 'Created successfully');
}
}
Step 2: Register Routes
$router->get('resources', 'MyResourceApiController@index');
$router->get('resources/([0-9]+)', 'MyResourceApiController@show');
$router->post('resources', 'MyResourceApiController@store');
$router->put('resources/([0-9]+)', 'MyResourceApiController@update');
$router->delete('resources/([0-9]+)', 'MyResourceApiController@destroy');
Support
For issues and questions:
Changelog
Version 1.1.1 (2026-04-04)
- Caching: GET responses are now cacheable with
Cache-Control: public, max-age=300
ETag header for entity tag cache validation
Last-Modified header for timestamp-based validation
304 Not Modified response for conditional requests
Vary: Accept, Accept-Encoding, X-API-Key header for proper cache keying
- HTTP Compliance:
Location header on all 201 Created responses
204 No Content for DELETE operations (was 200 OK)
406 Not Acceptable for unsupported Accept header values
PATCH method support for partial updates
Allow header on 405 Method Not Allowed responses
- Changed
POST /languages/{code}/default to PUT (proper state change semantics)
Version 1.1.0 (2026-04-04)
- Rate Limiting: Implemented file-based rate limiting with sliding window
- 60 requests/minute for read operations (GET)
- 20 requests/minute for write operations (POST/PUT/DELETE/PATCH)
- Per-client tracking by API key, Bearer token, or IP address
- Standard rate limit headers
- HATEOAS: Added Hypermedia as the Engine of Application State to all responses
_links object in every response following RFC 5988
- Pagination links (self, first, prev, next, last)
- Resource links (self, collection, canonical, comments, post)
- Root API links for endpoint discovery
- Templated search URL support
Version 1.0.0 (2024-01-15)
- Initial release
- Posts CRUD operations
- Categories CRUD operations
- Comments CRUD operations
- Archives by date
- API Key and Bearer Token authentication
- OpenAPI 3.0 specification