đ Blogs
Blog post management with slug-based routing, view tracking, helpful/not-helpful feedback, full-text search, and admin bulk import. Tags and types are stored as JSON strings and matched via LIKE.
List Blogs
âļReturns a paginated list of blog posts. Defaults to status=published. Supports full-text search across title, excerpt, content, and focusKeyword. Tags and types are matched via LIKE (substring match).
| Param | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 20 | Results per page (max 100) |
| status | string | published | draft | published | archived |
| category | string | â | Exact category match |
| tag | string | â | Substring match against JSON tags array |
| type | string | â | general | video | lawyer | real-estate | marketing | healthcare |
| featured | string | â | "true" or "false" |
| search | string | â | Full-text search (max 200 chars) across title, excerpt, content, focusKeyword |
| sortBy | string | publishedAt | createdAt | publishedAt | viewCount | helpfulCount | title |
| sortOrder | string | desc | asc | desc |
{
"success": true,
"data": {
"blogs": [
{
"id": "blog_abc",
"slug": "my-first-post",
"title": "My First Post",
"excerpt": "A short summary of the post.",
"coverImageUrl": "https://cdn.example.com/cover.jpg",
"category": "AI News",
"tags": ["ai", "productivity"],
"types": ["general"],
"status": "published",
"featured": false,
"viewCount": 142,
"helpfulCount": 38,
"notHelpfulCount": 2,
"focusKeyword": "ai tools",
"publishedAt": "2026-04-01T10:00:00.000Z",
"createdAt": "2026-04-01T09:00:00.000Z",
"user": {
"id": "usr_1",
"username": "admin",
"avatar": "https://cdn.example.com/avatar.jpg"
}
}
],
"pagination": {
"total": 84,
"page": 1,
"limit": 20,
"totalPages": 5
}
}
}
X-Total-Count header with the raw total count.
Check Slug Availability
âļCheck whether a given slug is already taken before creating or updating a blog post.
| Param | Type | Required | Description |
|---|---|---|---|
| slug | string | required | 2â160 chars, lowercase letters, numbers, and hyphens only ([a-z0-9-]) |
{ "success": true, "data": { "slug": "my-post-title", "available": true } }
{ "success": true, "data": { "slug": "my-post-title", "available": false } }
Get Blog by Slug
âļFetch a single published blog post. Only posts with status = "published" are returned. Tracks unique views per visitor via a viewed_blogs cookie (24h TTL, stores up to 200 slugs). A view is only counted once per slug per cookie window.
| Param | Type | Description |
|---|---|---|
| slug | string | The blog post slug |
{
"success": true,
"data": {
"blog": {
"id": "blog_abc",
"slug": "my-first-post",
"title": "My First Post",
"excerpt": "A short summary.",
"content": "Full HTML or markdown content...",
"coverImageUrl": "https://cdn.example.com/cover.jpg",
"metaTitle": "My First Post | AI Tools",
"metaDescription": "A short SEO description.",
"focusKeyword": "ai tools",
"canonicalUrl": null,
"category": "AI News",
"tags": ["ai", "productivity"],
"types": ["general"],
"status": "published",
"featured": false,
"viewCount": 143,
"helpfulCount": 38,
"notHelpfulCount": 2,
"publishedAt": "2026-04-01T10:00:00.000Z",
"createdAt": "2026-04-01T09:00:00.000Z",
"updatedAt": "2026-04-10T08:00:00.000Z",
"user": {
"id": "usr_1",
"username": "admin",
"avatar": "https://cdn.example.com/avatar.jpg"
}
}
}
}
{ "success": false, "message": "Blog post not found." }
Submit Feedback
âļSubmit a helpful or not-helpful vote on a blog post. Works for both authenticated users and anonymous visitors. Submitting the same vote twice is a no-op. Submitting the opposite vote flips the existing vote and adjusts counters accordingly.
| Param | Type | Description |
|---|---|---|
| id | string | Blog post ID |
| Field | Type | Required | Description |
|---|---|---|---|
| isHelpful | boolean | required | true = helpful, false = not helpful |
| Visitor type | Dedup key |
|---|---|
| Authenticated | userId |
| Anonymous | SHA-256 of CF-Connecting-IP + User-Agent (first 32 hex chars) |
{
"success": true,
"data": {
"helpfulCount": 39,
"notHelpfulCount": 2
}
}
Create Blog
đĄ Admin Only âļCreate a new blog post. If slug is omitted, one is auto-generated from the title. If the resolved slug is already taken, a short timestamp suffix is appended automatically.
| Field | Type | Required | Notes |
|---|---|---|---|
| title | string | required | 5â200 characters |
| content | string | required | Min 50 characters |
| slug | string | optional | 2â160 chars, [a-z0-9-]. Auto-generated from title if omitted |
| excerpt | string | optional | Max 300 chars |
| coverImageUrl | URL string | optional | Must be a valid URL |
| metaTitle | string | optional | Max 120 chars â SEO |
| metaDescription | string | optional | Max 160 chars â SEO |
| focusKeyword | string | optional | Max 100 chars â SEO |
| canonicalUrl | URL string | optional | Must be a valid URL â SEO |
| category | string | optional | Max 80 chars |
| tags | string[] | optional | Max 20 items, each max 50 chars. Default [] |
| types | enum[] | optional | Max 10 items from general | video | lawyer | real-estate | marketing | healthcare. Default [] |
| status | string | optional | draft | published | archived. Default draft |
| featured | boolean | optional | Default false |
| publishedAt | ISO datetime | optional | Defaults to now when status = "published" |
{
"success": true,
"message": "Blog post created.",
"data": { "blog": { /* full blog object */ } }
}
Update Blog
đĄ Admin Only âļPartially update a blog post. All fields from Create Blog are accepted except slug (slug is immutable after creation). publishedAt is set automatically the first time status changes to "published" if it was not already set.
| Param | Type | Description |
|---|---|---|
| id | string | Blog post ID |
All fields are optional â send only the fields you want to change. Same field rules as Create Blog apply, minus slug.
{
"success": true,
"message": "Blog post updated.",
"data": { "blog": { /* updated blog object */ } }
}
{ "success": false, "message": "You do not have permission to edit this blog post." }
{ "success": false, "message": "Blog post not found." }
Delete Blog
đĄ Admin Only âļPermanently delete a blog post. Only the post author or an admin can delete. This action is irreversible.
| Param | Type | Description |
|---|---|---|
| id | string | Blog post ID |
{ "success": true, "message": "Blog post deleted." }
{ "success": false, "message": "You do not have permission to delete this blog post." }
{ "success": false, "message": "Blog post not found." }
Bulk Import Blogs
đĄ Admin Only âļImport up to 50 blog posts in a single request. Each post is processed individually â a failure on one entry does not abort the rest. Posts whose resolved slug already exists are silently skipped (not failed). Results are broken down into created, skipped, and failed lists.
| Field | Type | Required | Notes |
|---|---|---|---|
| tools | object[] | required | 1â50 blog objects. Same schema as Create Blog |
| publishImmediately | boolean | optional | Default false. If true, all posts are created with status: "published" and publishedAt set to now |
{
"success": true,
"message": "Bulk import complete. 10 created, 2 skipped, 0 failed.",
"data": {
"summary": {
"total": 12,
"created": 10,
"skipped": 2,
"failed": 0
},
"created": [ /* array of created blog objects */ ],
"skipped": [
{ "title": "Existing Post", "reason": "Slug already exists" }
],
"failed": [
{ "title": "Bad Post", "reason": "Database constraint violation" }
]
}
}
summary and failed arrays in the response.