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

All 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_here

The 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. 1. Configure your external authentication API URL in organization settings
  2. 2. Generate a JWT token for your authenticated user (same JWT used for blog comments if already configured)
  3. 3. Redirect user to the SSO endpoint with the JWT token
  4. 4. User is automatically logged in and redirected to the blog dashboard

SSO Endpoint

GET https://blog.codhash.com/api/auth/sso/login?token=JWT_TOKEN&org=ORG_SLUG

Query Parameters

  • token - JWT token from your application
  • org - 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
const handleAccessBlog = async () => {
// 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. 1. Validates JWT token with your external authentication API
  2. 2. Creates user account if it doesn't exist (using email from JWT)
  3. 3. Adds user to the specified organization as a member
  4. 4. Creates an authenticated session (30 days validity)
  5. 5. Redirects to the blog dashboard (or custom callback URL)

External API Contract

Your external authentication API must respond to:

GET https://your-api.com/verify
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

POST /api/auth/sso/add-member

Request Body:

{
  "adminToken": "JWT_of_admin",
  "userToken": "JWT_of_user_to_add",
  "organizationId": "org_id",
  "role": "member" | "admin" | "owner"
}

Remove Member from Organization

POST /api/auth/sso/remove-member

Request Body:

{
  "adminToken": "JWT_of_admin",
  "userEmail": "user@example.com",
  "organizationId": "org_id"
}

List Organization Members

GET /api/auth/sso/members?token=JWT&organizationId=ORG_ID

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:

  1. Get admin JWT token (your CMS admin)
  2. Get user JWT token (user to add)
  3. Call add-member with role (member, admin, or owner)
  4. 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

GET/api/v1/blog/articles
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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 slug
tagstringFilter by tag slug
searchstringSearch in titles and content
sortstringSort order: latest, oldest, popular, views
statusstringFilter by status: DRAFT, PUBLISHED, SCHEDULED, ARCHIVED
languagestringLanguage code (default: en)

Create Article

Required permission: blog:write

POST/api/v1/blog/articles
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

PUT/api/v1/blog/articles/{slug}
const response = await fetch('https://blog.codhash.com/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);
Response
{
  "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

DELETE/api/v1/blog/articles/{slug}
const response = await fetch('https://blog.codhash.com/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);
Response
{
  "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

GET/api/v1/blog/articles/{slug}
const response = await fetch('https://blog.codhash.com/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);
Response
{
  "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

POST/api/v1/blog/articles/{slug}/view
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles/{slug}/view', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

GET/api/v1/blog/categories
const response = await fetch('https://blog.codhash.com/api/v1/blog/categories', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

POST/api/v1/blog/categories
const response = await fetch('https://blog.codhash.com/api/v1/blog/categories', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

GET/api/v1/blog/tags
const response = await fetch('https://blog.codhash.com/api/v1/blog/tags', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

GET/api/v1/blog/media
const response = await fetch('https://blog.codhash.com/api/v1/blog/media', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

POST/api/v1/blog/media
const response = await fetch('https://blog.codhash.com/api/v1/blog/media', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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

PATCH/api/v1/blog/media/{id}
const response = await fetch('https://blog.codhash.com/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);
Response
{
  "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

DELETE/api/v1/blog/media/{id}
const response = await fetch('https://blog.codhash.com/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);
Response
{
  "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.

POST/api/v1/blog/articles/{slug}/comments
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles/{slug}/comments', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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.

POST/api/v1/blog/articles/{slug}/comments
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles/my-article/comments', {
method: '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.

GET/api/v1/blog/articles/{slug}/comments?cursor=xyz&limit=20
const response = await fetch('https://blog.codhash.com/api/v1/blog/articles/{slug}/comments?cursor=xyz&limit=20', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key_here',
'Content-Type': 'application/json'
}
});

const data = await response.json();
console.log(data);
Response
{
  "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.

blog:readRead all blog content: articles, categories, tags, media, analytics. View and search content.
blog:writeFull write access: create, update, delete articles, categories, tags, media. Complete management.

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.

200
OK
Request successful
400
Bad Request
Invalid parameters
401
Unauthorized
Invalid or missing API key
403
Forbidden
Insufficient permissions
404
Not Found
Resource not found
429
Too Many Requests
Rate limit exceeded
500
Internal Server Error
Server error
Cod'Blog