Blog API Documentation
Access your blog content programmatically with our simple REST API. Perfect for building custom frontends, mobile apps, or integrating with other services.
Base URL
https://blog.codhash.comAll API endpoints use the path /api/v1/blog/ prefix.
Authentication
All API requests require authentication using API keys with the format sk_live_.... Create an API key in your organization settings with the appropriate permissions.
Header Format
Authorization: Bearer sk_live_your_api_key_hereThe API key must be prefixed with sk_live_ and have the required permissions for each endpoint.
SSO Login (Single Sign-On)
Automatically connect your application users to the blog manager without requiring a separate login. Perfect for integrating the blog into your CMS or e-commerce admin panel.
How It Works
- 1. Configure your external authentication API URL in organization settings
- 2. Generate a JWT token for your authenticated user (same JWT used for blog comments if already configured)
- 3. Redirect user to the SSO endpoint with the JWT token
- 4. User is automatically logged in and redirected to the blog dashboard
SSO Endpoint
Query Parameters
token- JWT token from your applicationorg- Organization slug (e.g., "my-company")callbackUrl- Optional redirect path after login (default: "/")
Integration Example
Add a "Access Blog" button in your admin panel that redirects to the SSO endpoint:
Your Admin Panel Code// Get current user's JWT token
const token = await getUserJWT();
// Redirect to SSO endpoint
const ssoUrl = `https://blog.codhash.com/api/auth/sso/login?token=${token}&org=${YOUR_ORG_SLUG}`;
window.location.href = ssoUrl;
};
Prerequisites
- 1. Configure External Authentication API URL in your organization settings
- 2. Your API must return user data in the required format (same as blog comments integration)
- 3. User email must be unique across the platform
- 4. Users are automatically created on first SSO login
What Happens on SSO Login
- 1. Validates JWT token with your external authentication API
- 2. Creates user account if it doesn't exist (using email from JWT)
- 3. Adds user to the specified organization as a member
- 4. Creates an authenticated session (30 days validity)
- 5. Redirects to the blog dashboard (or custom callback URL)
External API Contract
Your external authentication API must respond to:
Headers: Authorization: Bearer <JWT>
"valid": true,
"user": {
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"avatar": "https://..." // optional
}
}
Advanced: Member Management via SSO
Programmatically manage organization members from your external application. Perfect for syncing user access between your CMS and the blog manager.
Add Member to Organization
Request Body:
{
"adminToken": "JWT_of_admin",
"userToken": "JWT_of_user_to_add",
"organizationId": "org_id",
"role": "member" | "admin" | "owner"
}Remove Member from Organization
Request Body:
{
"adminToken": "JWT_of_admin",
"userEmail": "user@example.com",
"organizationId": "org_id"
}List Organization Members
Response:
{
"organization": { "id": "...", "name": "...", "slug": "..." },
"members": [
{
"userId": "...",
"email": "user@example.com",
"name": "John Doe",
"role": "owner",
"joinedAt": "2024-01-15T10:00:00Z"
}
],
"totalMembers": 5
}Use Case: Automated Access Control
When you grant blog access to a user in your CMS admin panel:
- Get admin JWT token (your CMS admin)
- Get user JWT token (user to add)
- Call add-member with role (member, admin, or owner)
- User can now access blog via SSO login
Articles Management
Complete CRUD operations for articles. Read operations require blog:read, write operations require blog:write.
Get All Articles
Required permission: blog:read
/api/v1/blog/articlesmethod: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": [
{
"id": "clx1234567890",
"slug": "getting-started-guide",
"title": "Getting Started Guide",
"excerpt": "Learn how to get started...",
"publishedAt": "2024-01-15T10:00:00Z",
"readingTime": 5,
"viewCount": 142,
"coverImage": "https://blog.codhash.com/image.jpg",
"coverImageAlt": "Guide illustration",
"categories": [{"name": "Tutorial", "slug": "tutorial"}],
"tags": [{"name": "guide", "slug": "guide"}],
"author": {"displayName": "John Doe"}
}
],
"pagination": {
"page": 1,
"limit": 10,
"totalItems": 25,
"totalPages": 3,
"hasNextPage": true,
"hasPreviousPage": false
},
"meta": {
"organization": {
"id": "org_123",
"name": "My Organization",
"slug": "my-org"
}
}
}Query Parameters
pageintegerPage number (default: 1)limitintegerArticles per page (max: 50, default: 10)categorystringFilter by category slugtagstringFilter by tag slugsearchstringSearch in titles and contentsortstringSort order: latest, oldest, popular, viewsstatusstringFilter by status: DRAFT, PUBLISHED, SCHEDULED, ARCHIVEDlanguagestringLanguage code (default: en)Create Article
Required permission: blog:write
/api/v1/blog/articlesmethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx1234567890",
"title": "My New Article",
"slug": "my-new-article",
"status": "DRAFT",
"coverImage": "https://blog.codhash.com/image.jpg",
"author": {"displayName": "John Doe"},
"categories": [{"name": "Tutorial", "slug": "tutorial"}],
"tags": [{"name": "guide", "slug": "guide"}],
"createdAt": "2024-01-15T10:00:00Z"
},
"meta": {
"message": "Article created as draft"
}
}Update Article
Required permission: blog:write
/api/v1/blog/articles/{slug}method: 'PUT',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx1234567890",
"title": "Updated Article Title",
"slug": "updated-article",
"status": "PUBLISHED",
"updatedAt": "2024-01-15T11:00:00Z"
},
"meta": {
"message": "Article updated successfully"
}
}Delete Article
Required permission: blog:delete
/api/v1/blog/articles/{slug}method: 'DELETE',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx1234567890",
"slug": "deleted-article",
"deletedAt": "2024-01-15T12:00:00Z"
},
"meta": {
"message": "Article deleted successfully"
}
}Get Single Article
Retrieve a specific article by its slug with full content. Required permission: blog:read
/api/v1/blog/articles/{slug}method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx1234567890",
"slug": "getting-started-guide",
"title": "Getting Started Guide",
"content": "# Getting Started\n\nThis is the full content...",
"excerpt": "Learn how to get started...",
"publishedAt": "2024-01-15T10:00:00Z",
"readingTime": 5,
"viewCount": 142,
"coverImage": "https://blog.codhash.com/image.jpg",
"coverImageAlt": "Guide illustration",
"categories": [{"name": "Tutorial", "slug": "tutorial"}],
"tags": [{"name": "guide", "slug": "guide"}],
"author": {"displayName": "John Doe"},
"seo": {
"title": "Getting Started Guide",
"description": "Learn how to get started...",
"keywords": ["tutorial", "guide"],
"ogImage": "https://blog.codhash.com/og-image.jpg"
}
},
"meta": {
"organization": {
"id": "org_123",
"name": "My Organization"
}
}
}View Tracking
Securely track article views with rate limiting and deduplication. Required permission: blog:read
/api/v1/blog/articles/{slug}/viewmethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"articleId": "clx1234567890",
"viewCount": 143,
"incremented": true,
"viewId": "view_123456",
"country": "US",
"referrer": "https://google.com"
},
"meta": {
"rateLimited": false,
"nextAllowedAt": null
}
}Rate Limiting
Views are rate-limited to prevent artificial inflation. Only 1 view per hour per article is counted.
Categories Management
Manage blog categories with hierarchical support. Read operations require blog:read, write operations require blog:write.
Get All Categories
/api/v1/blog/categoriesmethod: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": [
{
"id": "clx_cat_123",
"name": "Tutorial",
"slug": "tutorial",
"description": "Step-by-step guides",
"color": "#3b82f6",
"icon": "book",
"articleCount": 12,
"parent": null,
"children": [],
"seo": {
"title": "Tutorial Articles",
"description": "Learn with our step-by-step tutorials"
}
}
],
"meta": {
"organization": {
"id": "org_123",
"name": "My Organization"
},
"includeEmpty": false
}
}Create Category
Required permission: blog:write
/api/v1/blog/categoriesmethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx_cat_new",
"name": "Development",
"slug": "development",
"description": "Programming and development articles",
"color": "#10b981",
"icon": "code",
"articleCount": 0,
"createdAt": "2024-01-15T10:00:00Z"
},
"meta": {
"message": "Category created successfully"
}
}Tags Management
Manage blog tags for content organization. Required permission: blog:read
/api/v1/blog/tagsmethod: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": [
{
"id": "clx_tag_123",
"name": "JavaScript",
"slug": "javascript",
"color": "#f7df1e",
"useCount": 15
},
{
"id": "clx_tag_456",
"name": "Tutorial",
"slug": "tutorial",
"color": "#3b82f6",
"useCount": 8
}
],
"meta": {
"organization": {
"id": "org_123",
"name": "My Organization"
}
}
}Media Management
Manage uploaded files and media assets. Supports images, videos, and documents.
Get All Media
Required permission: blog:read
/api/v1/blog/mediamethod: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": [
{
"id": "clx_media_123",
"filename": "hero-image.jpg",
"url": "https://blog.codhash.com/uploads/hero-image.jpg",
"mimeType": "image/jpeg",
"size": 245760,
"width": 1920,
"height": 1080,
"alt": "Hero banner image",
"type": "image",
"uploader": {"displayName": "John Doe"},
"createdAt": "2024-01-15T10:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 45,
"totalPages": 3,
"hasNextPage": true
},
"meta": {
"totalSize": 5242880
}
}Upload Media
Required permission: blog:write
/api/v1/blog/mediamethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx_media_new",
"filename": "document.pdf",
"url": "https://blog.codhash.com/uploads/document.pdf",
"mimeType": "application/pdf",
"size": 1048576,
"type": "document",
"uploader": {"displayName": "John Doe"},
"createdAt": "2024-01-15T11:00:00Z"
},
"meta": {
"message": "Media file registered successfully"
}
}Update Media Metadata
Required permission: blog:write
/api/v1/blog/media/{id}method: 'PATCH',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx_media_123",
"filename": "updated-filename.jpg",
"alt": "Updated alt text",
"updatedAt": "2024-01-15T12:00:00Z"
},
"meta": {
"message": "Media metadata updated successfully"
}
}Delete Media
Required permission: blog:write
/api/v1/blog/media/{id}method: 'DELETE',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "clx_media_123",
"filename": "deleted-file.jpg",
"deletedAt": "2024-01-15T13:00:00Z"
},
"meta": {
"message": "Media file deleted successfully",
"warning": "File was used in 3 article(s)"
}
}Blog Comments
Submit and retrieve comments on blog articles. Supports anonymous comments and external JWT authentication for identified users.
Authentication Modes
- • Anonymous: Provide authorName + authorEmail in request body
- • External JWT: Send user token via X-User-Token header (your app users)
- • Hybrid: Support both modes in the same endpoint
Submit Comment (Anonymous)
POST a comment as an anonymous user by providing name and email.
/api/v1/blog/articles/{slug}/commentsmethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": {
"id": "cmgyjfis30003itplctf99g1r",
"authorName": "John Doe",
"content": "<p>Great article!</p>",
"createdAt": "2024-01-15T10:30:00Z",
"status": "PENDING"
},
"message": "Comment submitted successfully. It will appear after approval."
}Request Body (Anonymous)
{
"content": "<p>Your comment here</p>",
"authorName": "John Doe",
"authorEmail": "john@example.com",
"formStartTime": 1234567890000
}Submit Comment (External JWT)
POST a comment as an authenticated external user (e.g., from your app). User info is extracted from JWT.
Setup Required
Configure your external authentication API URL in Organization Settings → External Authentication. Your API must verify JWTs and return user data in the specified format.
/api/v1/blog/articles/{slug}/commentsmethod: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'X-User-Token': user_jwt_token, User's JWT from your app
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: '<p>Great article!</p>',
formStartTime: Date.now() - 5000
}))
});
Response (External JWT)
{
"success": true,
"data": {
"id": "cmgyjfis30003itplctf99g1r",
"authorName": "nate",
"content": "<p>Great article!</p>",
"createdAt": "2024-01-15T10:30:00Z",
"status": "APPROVED"
},
"message": "Comment submitted successfully."
}Get Comments
Retrieve approved comments for an article. Public endpoint, supports pagination.
/api/v1/blog/articles/{slug}/comments?cursor=xyz&limit=20method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
{
"success": true,
"data": [
{
"id": "comment_123",
"authorName": "John Doe",
"externalAuthorAvatar": "https://...",
"content": "<p>Great article!</p>",
"createdAt": "2024-01-15T10:30:00Z"
}
],
"meta": {
"nextCursor": "comment_abc",
"hasMore": true
}
}Comment Moderation
Comments have three statuses: PENDING, APPROVED, REJECTED. Enable auto-approve in article settings to skip moderation. Only APPROVED comments appear in public API responses.
Security Features
- • Rate Limiting: 5 comments per 10 seconds per IP
- • Honeypot Field: Bot detection via hidden field
- • Form Timing: Minimum 3 seconds required (blocks bots)
- • XSS Protection: HTML sanitization on all content
- • Content Validation: Max 5000 characters
API Key Permissions
All API endpoints require specific permissions. Configure these when creating your API key.
Security Best Practices
- • Only grant the minimum permissions needed for your use case
- • Use separate API keys for different applications or environments
- • Regularly rotate your API keys for better security
- • Never expose API keys in client-side code or public repositories
Error Codes
Standard HTTP status codes used by the API.
