| name | blog-post |
| description | Public API blog posts (X-API-KEY). CRUD blog posts, AI generation (outline/full/section), AI operations (expand/shorten/rewrite), images, exports, and social-campaign-from-blog. PublicApiBlogPostController (/api/v2/blog-posts). |
Skill: Blog Posts
Description
Manage blog posts and AI-powered writing workflows. This skill documents PublicApiBlogPostController at /api/v2/blog-posts. Authenticate with X-API-KEY.
Key async behaviors:
- Outline/full/section generation returns
activityId → poll GET /api/v2/jobs/{activityId}.
- Blog post “generate” returns
aiEditLiveObjectId → poll GET /api/v2/blog-posts/{blog_post_id}/generation/{ai_edit_id}, then POST accept to commit.
- AI operations return
aiOperationId → poll GET /api/v2/blog-posts/{blog_post_id}/ai-operations/{ai_operation_id}, then POST accept to apply.
- Exports return
exportId → poll GET /api/v2/blog-posts/{blog_post_id}/exports/{export_id} (not the jobs endpoint).
TypeScript types (selected)
Mirrors the request/response DTOs in service-poc/service/app/module-client/.../publicapi/blogpost/*.
type PublicApiBlogPostSummary = {
id: string;
projectId: string;
title: string;
description?: string | null;
status: string;
createdAt: string;
updatedAt: string;
};
type PublicApiBlogPostListResponse = {
data: PublicApiBlogPostSummary[];
nextCursor?: string | null;
hasMore: boolean;
};
type PublicApiBlogPostResponse = {
id: string;
projectId: string;
title: string;
description?: string | null;
content: string;
status: string;
createdAt: string;
updatedAt: string;
};
type PublicApiUpdateBlogPostContentRequest = { content: string };
type PublicApiUpdateBlogPostMetaRequest = { title?: string | null; description?: string | null };
type PublicApiGenerateBlogPostRequest = {
prompt: string;
title?: string | null;
description?: string | null;
outputLanguage?: string | null;
textDetailLevel?: string | null;
tone?: string | null;
};
type PublicApiBlogPostGenerateResponse = {
blogPostId: string;
projectId: string;
aiEditLiveObjectId: string;
activityId: string;
};
type PublicApiBlogPostGenerationStatusResponse = {
aiEditLiveObjectId: string;
status: string;
proposedContent?: string | null;
};
type PublicApiBlogPostGenerationRequest = { prompt?: string | null };
type PublicApiBlogPostGenerationResponse = {
activityId: string;
liveObjectId: string;
workflowType: string;
message?: string | null;
};
type PublicApiBlogPostOutlineSection = { id: string; title: string; content?: string | null };
type PublicApiBlogPostOutlineResponse = {
liveObjectId: string;
status: string;
title?: string | null;
sections: PublicApiBlogPostOutlineSection[];
};
type PublicApiAiOperationRequest = {
command: string;
selection: string;
prompt?: string | null;
idempotencyKey?: string | null;
};
type PublicApiAiOperationResponse = { aiOperationId: string; activityId: string };
type PublicApiAiOperationStatusResponse = {
aiOperationId: string;
status: string;
proposedContent?: string | null;
};
type PublicApiGenerateBlogImageRequest = { prompt?: string | null; fileId?: string | null };
type PublicApiGenerateBlogImageResponse = { liveObjectId: string; activityId?: string | null };
type PublicApiBlogPostImagesResponse = {
images: { liveObjectId: string; status: string; presignedUrl?: string | null }[];
};
type PublicBlogExportStartedResponse = { exportId: string; status: string };
type PublicBlogExportStatusResponse = {
data: {
exportId: string;
status: string;
format: string;
downloadUrl?: string | null;
expiresAt?: string | null;
errorMessage?: string | null;
};
};
type PublicApiCreateSocialCampaignFromBlogRequest = {
targetTopicCount?: number | null;
webSearchEnabled?: boolean;
themeId?: string | null;
outputLanguage?: string | null;
textDetailLevel?: string | null;
tone?: string | null;
};
type PublicApiCreateSocialCampaignFromBlogResponse = {
projectId: string;
activityId: string;
workflowType: string;
message: string;
};
Blog post CRUD
| Action | Method | Path |
|---|
| List (cursor) | GET | /api/v2/blog-posts?limit=20&cursor= |
| Get by id | GET | /api/v2/blog-posts/{blog_post_id} |
| Update content | PUT | /api/v2/blog-posts/{blog_post_id}/content |
| Update meta | PATCH | /api/v2/blog-posts/{blog_post_id} |
| Delete | DELETE | /api/v2/blog-posts/{blog_post_id} |
curl "$LAYERPROOF_BASE_URL/api/v2/blog-posts?limit=20" \
-H "X-API-KEY: $LAYERPROOF_API_KEY"
Generate blog post (create + AI edit)
| Action | Method | Path |
|---|
| Start generation | POST | /api/v2/blog-posts/generate |
| Poll generation | GET | /api/v2/blog-posts/{blog_post_id}/generation/{ai_edit_id} |
| Accept generation | POST | /api/v2/blog-posts/{blog_post_id}/generation/{ai_edit_id}/accept |
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/blog-posts/generate" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $LAYERPROOF_API_KEY" \
-d '{"prompt":"Write a blog post about cursor pagination in APIs","outputLanguage":"en"}'
Notes:
- Use
aiEditLiveObjectId from the response as {ai_edit_id}.
- When poll status returns
ACCEPTED, call accept to commit it to the blog post.
Outline + section generation (jobs)
| Action | Method | Path | Polling |
|---|
| Generate outline | POST | /api/v2/blog-posts/{blog_post_id}/generation/outline | /api/v2/jobs/{activityId} |
| Get outline | GET | /api/v2/blog-posts/{blog_post_id}/outline | n/a |
| Generate full post | POST | /api/v2/blog-posts/{blog_post_id}/generation/full-post | /api/v2/jobs/{activityId} |
| Generate section content | POST | /api/v2/blog-posts/{blog_post_id}/sections/{section_id}/generation/content | /api/v2/jobs/{activityId} |
| Expand section | POST | /api/v2/blog-posts/{blog_post_id}/sections/{section_id}/generation/expand | /api/v2/jobs/{activityId} |
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/blog-posts/<blog_post_id>/generation/outline" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $LAYERPROOF_API_KEY" \
-d '{"prompt":"Outline for a technical post with examples"}'
AI operations (expand / shorten / rewrite a selection)
| Action | Method | Path |
|---|
| Start operation | POST | /api/v2/blog-posts/{blog_post_id}/ai-operations |
| Poll status | GET | /api/v2/blog-posts/{blog_post_id}/ai-operations/{ai_operation_id} |
| Cancel | POST | /api/v2/blog-posts/{blog_post_id}/ai-operations/{ai_operation_id}/cancel |
| Accept (apply) | POST | /api/v2/blog-posts/{blog_post_id}/ai-operations/{ai_operation_id}/accept |
Behavior details:
command values: expand, shorten, rewrite
- Server maps
shorten → internal summarize.
- Use
idempotencyKey (idempotency_key) to safely retry start calls.
- Accept will return 409 if the original selection no longer exists in the current content.
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/blog-posts/<blog_post_id>/ai-operations" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $LAYERPROOF_API_KEY" \
-d '{"command":"rewrite","selection":"<exact text to replace>","prompt":"Make it more concise","idempotency_key":"rewrite-1"}'
Images
| Action | Method | Path |
|---|
| List resolved images | GET | /api/v2/blog-posts/{blog_post_id}/images |
Cached content (draft autosave)
| Action | Method | Path |
|---|
| Save cached markdown | PUT | /api/v2/blog-posts/{blog_post_id}/cached-content |
Returns 204 No Content.
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/blog-posts/<blog_post_id>/cached-content" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $LAYERPROOF_API_KEY" \
-d '{"markdown":"# Draft\n\nHello..."}'
Exports (async)
| Action | Method | Path |
|---|
| Export HTML | POST | /api/v2/blog-posts/{blog_post_id}/export/html |
| Export Markdown | POST | /api/v2/blog-posts/{blog_post_id}/export/markdown |
| Export PDF | POST | /api/v2/blog-posts/{blog_post_id}/export/pdf |
| Poll export | GET | /api/v2/blog-posts/{blog_post_id}/exports/{export_id} |
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/blog-posts/<blog_post_id>/export/pdf" \
-H "X-API-KEY: $LAYERPROOF_API_KEY"
Social campaign from blog post
| Action | Method | Path |
|---|
| Start campaign generation | POST | /api/v2/blog-posts/{blog_post_id}/social-campaign |
Poll GET /api/v2/jobs/{activityId}.
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/blog-posts/<blog_post_id>/social-campaign" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $LAYERPROOF_API_KEY" \
-d '{"targetTopicCount":5,"webSearchEnabled":true}'
Agent behavior
Before any API call, verify environment variables are set:
if [[ -z "${LAYERPROOF_BASE_URL}" ]]; then
echo "ERROR: LAYERPROOF_BASE_URL is not set."
return 1
fi
if [[ -z "${LAYERPROOF_API_KEY}" ]]; then
echo "ERROR: LAYERPROOF_API_KEY is not set."
return 1
fi
if [[ -f .env.local ]]; then
set -a
source .env.local
set +a
fi
- Resolve
blog_post_id (list or create via generate).
- For jobs-based endpoints (outline/full/section/expand), poll
/api/v2/jobs/{activityId} until terminal.
- For generate (ai edit) endpoints, poll
/api/v2/blog-posts/{id}/generation/{ai_edit_id} until ACCEPTED, then call accept.
- For AI operations, poll
/ai-operations/{ai_operation_id} until ACCEPTED, then call accept.
- For exports, poll
/exports/{export_id} until COMPLETED, then use downloadUrl (presigned) before expiresAt.
Response format (required)
- Always show the raw JSON response (verbatim) in a JSON code block.
- If the response contains a URL to an image (
presignedUrl, etc), render/show the image and also show the JSON response (do not convert to a table).