Skip to content
Back to skills

Blog Post

ASecurity

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

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
content-marketingtypescriptbashapi

Works with

  • cursor
  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add compilet-dev/agent-skill-layerproof --skill blog-post --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Blog Post?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Blog Post
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/compilet-dev-blog-post/badge)](https://www.skillsdirectory.com/skills/compilet-dev-blog-post)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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/*`.

```typescript
// --- CRUD ---
type PublicApiBlogPostSummary = {
  id: string;         // UUID
  projectId: string;  // UUID
  title: string;
  description?: string | null;
  status: string;
  createdAt: string; // ISO 8601
  updatedAt: string; // ISO 8601
};
type PublicApiBlogPostListResponse = {
  data: PublicApiBlogPostSummary[];
  nextCursor?: string | null;
  hasMore: boolean;
};
type PublicApiBlogPostResponse = {
  id: string;         // UUID
  projectId: string;  // UUID
  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 };

// --- “Generate” (creates + generates via AI edit) ---
type PublicApiGenerateBlogPostRequest = {
  prompt: string;                 // 1..10000
  title?: string | null;          // 1..255
  description?: string | null;    // max 10000
  outputLanguage?: string | null;
  textDetailLevel?: string | null;
  tone?: string | null;
};
type PublicApiBlogPostGenerateResponse = {
  blogPostId: string;         // UUID
  projectId: string;          // UUID
  aiEditLiveObjectId: string; // UUID (use as ai_edit_id)
  activityId: string;         // UUID (job id)
};
type PublicApiBlogPostGenerationStatusResponse = {
  aiEditLiveObjectId: string; // UUID
  status: string;             // PENDING | STREAMING | ACCEPTED | FAILED (etc)
  proposedContent?: string | null;
};

// --- Outline + section generation (jobs-polling) ---
type PublicApiBlogPostGenerationRequest = { prompt?: string | null }; // max 2000
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[];
};

// --- AI operations (selection-based) ---
type PublicApiAiOperationRequest = {
  command: string;                 // expand | shorten | rewrite
  selection: string;               // 1..50000
  prompt?: string | null;          // max 2000
  idempotencyKey?: string | null;  // max 256 (aka idempotency_key)
};
type PublicApiAiOperationResponse = { aiOperationId: string; activityId: string };
type PublicApiAiOperationStatusResponse = {
  aiOperationId: string;
  status: string;                  // PENDING | STREAMING | ACCEPTED | FAILED | CANCELLED | REJECTED
  proposedContent?: string | null;
};

// --- Images ---
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 }[];
};

// --- Exports ---
type PublicBlogExportStartedResponse = { exportId: string; status: string };
type PublicBlogExportStatusResponse = {
  data: {
    exportId: string;
    status: string;    // PENDING | IN_PROGRESS | COMPLETED | FAILED (server-defined)
    format: string;    // html | markdown | pdf
    downloadUrl?: string | null;
    expiresAt?: string | null;
    errorMessage?: string | null;
  };
};

// --- Social campaign from blog ---
type PublicApiCreateSocialCampaignFromBlogRequest = {
  targetTopicCount?: number | null;
  webSearchEnabled?: boolean;  // default false
  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}` |

```bash
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` |

```bash
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}` |

```bash
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.

```bash
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**.

```bash
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}` |

```bash
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}`**.

```bash
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:**

```bash
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

# Load .env.local if present
if [[ -f .env.local ]]; then
  set -a
  source .env.local
  set +a
fi
```

1. Resolve `blog_post_id` (list or create via generate).
2. For **jobs-based** endpoints (outline/full/section/expand), poll `/api/v2/jobs/{activityId}` until terminal.
3. For **generate** (ai edit) endpoints, poll `/api/v2/blog-posts/{id}/generation/{ai_edit_id}` until `ACCEPTED`, then call accept.
4. For **AI operations**, poll `/ai-operations/{ai_operation_id}` until `ACCEPTED`, then call accept.
5. 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).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.