API Reference
A fully headless REST API for integrating Pressly content into any frontend, mobile app, or third-party service.
Base URL
https://www.pressly.co.ke/api/v1
Format
JSON (application/json)
Auth
Bearer Token (Sanctum)
Tenant Resolution
Every API request needs to resolve the tenant you want to read from or write to. You can do that by hostname, query string, or header.
Path-based site
https://www.pressly.co.ke/api/docs
Public site access uses the tenant short name in the URL.
API query style
https://www.pressly.co.ke/api/v1
Useful for external apps calling one shared API host.
Optional subdomain
Set a tenant subdomain to use this pattern.
If wildcard DNS is configured, the tenant can also resolve by subdomain.
Pass the tenant by query string
GET https://www.pressly.co.ke/api/v1/posts?tenant=demo
Or send the tenant as a header
X-Tenant: demo
Authentication
Use Bearer token authentication for all protected endpoints. Obtain a token by calling POST /auth/login.
Exchange credentials for a Bearer token.
Request Body (JSON)
email
string
required
User email address
password
string
required
User password
Request
{"email": "admin@demo.com", "password": "password"}
Response
{"token": "1|abc123...", "token_type": "Bearer", "user": {"id": 1, "name": "Admin", "email": "admin@demo.com"}}
Create a new subscriber account.
Request Body (JSON)
name
string
required
Display name
email
string
required
Email address
password
string
required
Password (min 8 chars)
Request
{"name": "Jane Doe", "email": "jane@example.com", "password": "secret123"}
Response
{"token": "2|xyz789...", "user": {"id": 2, "name": "Jane Doe"}}
Returns the authenticated user's profile.
Response
{"data": {"id": 1, "name": "Admin", "email": "admin@demo.com", "roles": ["admin"]}}
Revoke the current Bearer token.
Response
{"message": "Logged out successfully"}
Posts
Retrieve published posts. Write operations require authentication and appropriate roles.
Returns a paginated list of published posts.
Query Parameters
page
int
Page number (default: 1)
per_page
int
Results per page, max 50 (default: 10)
category
string
Filter by category slug
tag
string
Filter by tag name
featured
bool
Return only featured posts
search
string
Full-text search across title, excerpt, content
Response
{"data": [{"id": 1, "title": "Post Title", "slug": "post-title", "excerpt": "...", "published_at": "2026-05-01T12:00:00Z"}], "meta": {"total": 1236, "current_page": 1, "last_page": 124}}
Returns a single published post by its URL slug.
Path Parameters
slug
Post URL slug
Response
{"data": {"id": 1, "title": "Post Title", "slug": "post-title", "content": "...", "author": {"name": "Admin"}, "category": {"name": "Tech", "slug": "tech"}, "tags": []}}
Creates a new post. Requires editor or admin role.
Request Body (JSON)
title
string
required
Post title
content
string
Post body HTML
status
string
draft or published (default: draft)
Request
{"title": "New Article", "content": "<p>Body text...</p>", "status": "draft"}
Response
{"data": {"id": 42, "title": "New Article", "slug": "new-article", "status": "draft"}}
Updates an existing post. Must be author, editor, or admin.
Path Parameters
id
Post ID
Request
{"title": "Updated Title", "status": "published"}
Response
{"data": {"id": 42, "title": "Updated Title", "status": "published"}}
Permanently deletes a post. Requires admin role.
Path Parameters
id
Post ID
Response
{"message": "Post deleted successfully"}
Categories
Read-only access to all categories.
Returns all categories with post counts.
Response
{"data": [{"id": 1, "name": "Technology", "slug": "technology", "posts_count": 142}]}
Returns a category and its paginated posts.
Path Parameters
slug
Category slug
Query Parameters
page
int
Page number
per_page
int
Results per page, max 50
Response
{"data": {"category": {"id": 1, "name": "Technology"}, "posts": [...], "meta": {"total": 142}}}
Search
Full-text search across titles, excerpts, content, categories, and tags.
Returns paginated search results. Searches across title, excerpt, content, categories, and tags.
Query Parameters
q
string
Search query (required)
page
int
Page number
per_page
int
Results per page, max 50 (default: 10)
Response
{"data": [...], "meta": {"query": "laravel", "total": 23, "current_page": 1}}
Subscription Plans
Expose public subscription plans so external checkout flows or frontend apps can render pricing consistently.
Returns all active subscription plans ordered the same way they appear in the CMS.
Response
{"data": [{"name": "Monthly", "slug": "monthly", "price": "499.00", "currency": "KES", "interval": "monthly", "features": []}]}
Returns a single active subscription plan by slug.
Path Parameters
slug
Plan slug
Response
{"data": {"name": "Monthly", "slug": "monthly", "price": "499.00", "currency": "KES", "subscribe_url": "https://example.com/demo/subscribe?plan=monthly"}}
E-paper
Read published edition metadata and, where allowed, the page manifest for free or accessible editions.
Returns paginated published editions with page counts, cover images, and reader URLs.
Query Parameters
page
int
Page number
per_page
int
Results per page, max 50 (default: 12)
Response
{"data": [{"title": "Weekend Edition", "slug": "weekend-edition", "page_count": 28, "is_free": true, "reader_url": "https://example.com/demo/epaper/weekend-edition"}], "meta": {"total": 8}}
Returns one edition. Free or accessible editions include page image URLs, while subscriber-only editions return metadata with `requires_subscription=true`.
Path Parameters
slug
Edition slug
Response
{"data": {"title": "Weekend Edition", "slug": "weekend-edition", "requires_subscription": false, "pages": [{"page_number": 1, "image_url": "https://example.com/storage/..." }]}}
Streams
Surface live, scheduled, and ended stream metadata for apps, smart TV clients, and custom frontend pages.
Returns paginated streams ordered with live items first.
Query Parameters
page
int
Page number
per_page
int
Results per page, max 50 (default: 12)
Response
{"data": [{"title": "Morning Bulletin", "slug": "morning-bulletin", "is_live": true, "watch_url": "https://example.com/demo/streams/morning-bulletin"}], "meta": {"total": 3}}
Returns one stream with provider, embed code, stream URL, and current status.
Path Parameters
slug
Stream slug
Response
{"data": {"title": "Morning Bulletin", "slug": "morning-bulletin", "provider": "youtube", "stream_url": "https://youtube.com/...", "embed_code": "<iframe ...>"}}
Error Codes
All error responses follow a consistent JSON structure.
| Status | Meaning | When it occurs |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 401 | Unauthorized | Missing or invalid Bearer token |
| 403 | Forbidden | Token valid but lacks required role |
| 404 | Not Found | Post, category, or tag does not exist |
| 422 | Unprocessable | Validation failed check `errors` in response body |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected server error |
Error response shape
{
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."]
}
}
Comments
Read approved comments and post new ones (authenticated comments are auto-approved).
Returns all approved top-level comments with their approved replies.
Path Parameters
slugPost slugResponse
{"data": [{"id": 1, "content": "Great article!", "author_name": "Jane", "created_at": "2026-05-01T14:00:00Z", "replies": []}], "meta": {"total": 1}}Adds a comment to a post. Authenticated comments are approved immediately; guest comments require moderation.
Path Parameters
slugPost slugRequest Body (JSON)
contentstring required Comment text (max 2000 chars)parent_idint Parent comment ID for repliesRequest
{"content": "Really useful, thanks!"}Response
{"data": {"id": 5, "content": "Really useful, thanks!", "status": "approved"}, "message": "Comment posted."}