---
title: "Create Post"
method: POST
path: "/posts"
tags: ["Posts"]
---

# Create Post

`POST /posts`

Create a new blog post.

**Required Fields:**
- title: Post title
- slug: URL slug (must be unique within the blog)

**Image Processing:**
External image URLs are uploaded to Inblog R2 storage by default.
To preserve original URLs, use the `preserve_external_images=true` query parameter.

**Limitations:**
- The id field is not allowed. Post ID is generated automatically by the server.
- To update an existing post, use PATCH /posts/{id}.

**Response Codes:**
- 201: Post created successfully
- 400: Bad request (e.g., id field included)
- 409: Slug already exists

External image URLs will be automatically saved to Inblog storage.

## Query parameters

- `preserve_external_images` boolean

## Request body

- PostCreateRequest
  - `jsonapi` object, required — JSON:API version metadata included in every JSON:API response.
    - `version` string, required — Implemented JSON:API version.
  - `data` object, required — Post resource payload to create.
    - `type` 'posts', required — Resource type identifier. Always `posts`.
    - `attributes` object, required — Post fields to create.
      - `title` string, required — Post title (required)
      - `slug` string, required — URL slug (required)
      - `description` string — Post description
      - `content_html` string — "Post content HTML (generated with TipTap editor) **Generation Method:** - Inblog uses a TipTap-based WYSIWYG editor to generate HTML - Real-time HTML conversion using editor.getHTML() method - All content is managed in structured block units **Basic HTML Tags:** - Paragraph: `<p>` - Headings: `<h2>`, `<h3>`, `<h4>` (h1, h5, h6 not supported) - Lists: `<ul>`, `<ol>`, `<li>` (nesting supported) - Text styles: `<strong>`, `<em>`, `<u>`, `<s>`, `<code>` - Links: `<a>` (no rel attribute, nofollow removed) - Quote: `<blockquote>` - Horizontal rule: `<hr>` - Line break: `<br>` **Advanced Formatting:** - Highlight: `<mark>` (multiple colors supported) - Text color: `<span style="color: #hex;">` - Text alignment: `style="text-align: left|center|right;"` - Code block: `<pre><code class="language-*">` (syntax highlighting supported) **Tables:** - `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` - Resizable columns (colwidth attribute) - Automatic horizontal scroll wrapping **Custom Blocks (distinguished by data-type attribute):** - Image: `<img data-type="imageBlock">` - Single image block - Image grid: `<div data-type="imageGrid">` - Grid layout - Image carousel: `<div data-type="imageCarousel">` - Slide format - Callout: `<div data-type="callOut">` - Emphasis box - Link button: `<div data-type="linkButton">` - CTA button - Bookmark: `<div data-type="bookmark">` - URL preview **Embed Support:** - YouTube: `<div data-type="youtube" data-youtube-video-id="*">` - Form services: - Google Forms: `<div data-type="googleForm">` - HubSpot: `<div data-type="hubspot">` - Tally: `<div data-type="tally">` - Typeform: `<div data-type="typeform">` - HTML code block: `<section data-type="code-block"><div>...</div></section>` - Custom HTML insertion (iframes, etc). Must use `section` tag + `data-type="code-block"` + inner `<div>` wrapper to match the tiptap schema. `<div data-type="htmlCodeBlock">` will NOT work **Security and Limitations:** - `<script>` tags are automatically removed (XSS prevention) - Images should use Inblog CDN (source.inblog.dev) - External stylesheet links are restricted - iframes are only allowed for permitted embed services\n"
      - `published` boolean — Publication status
      - `image` union
        - string, uri — Image URL (external URLs are uploaded to Inblog storage by default, source.inblog.dev or image.inblog.dev URLs are used as-is)
        - string — Base64 data URL (max 10MB, always uploaded to Inblog storage)
        - object — Image object (URL field follows the same rules as above)
          - `url` string, uri — Image URL
          - `blurhash` string — Blur hash (optional)
          - `created_at` string, date-time — Creation time (optional)
      - `canonical_url` string, uri — Canonical URL
      - `meta_title` string — SEO meta title
      - `meta_description` string — SEO meta description
      - `cta_text` string — CTA button text
      - `cta_link` string, uri — CTA button destination URL
      - `cta_color` string — CTA button background color (hex color) - 6-digit hex code (#RRGGBB)
      - `cta_color_type` 'custom' | 'preset' | 'default' — CTA button background color type
      - `cta_text_color` string — CTA text color (hex color) - 6-digit hex code (#RRGGBB)
      - `cta_text_color_type` 'custom' | 'preset' | 'default' — CTA button text color type
      - `form_id` integer, nullable — Form ID to attach to the post. Set to null to remove the form.
      - `custom_scripts` object, nullable — Custom scripts inserted into the post
        - `head_start_script` string, nullable — Script inserted at the start of <head> tag (script tag format)
        - `head_end_script` string, nullable — Script inserted at the end of <head> tag (script tag format)
        - `body_start_script` string, nullable — Script inserted at the start of <body> tag (script tag format)
        - `body_end_script` string, nullable — Script inserted at the end of <body> tag (script tag format)
        - `json_ld_script` object — Structured data in JSON-LD format for SEO optimization
      - `notion_url` string, uri, nullable — Notion page URL for syncing content from Notion. **Behavior:** - When provided: Fetches the Notion page content and sets content_type to 'notion' - When set to null: Clears Notion data and resets content_type to 'tiptap' (converts to regular post) - When omitted: No changes to existing Notion settings **Supported URL formats:** - https://www.notion.so/page-name-{page_id} - https://notion.so/page-name-{page_id} - https://*.notion.site/page-name-{page_id} **Note:** The Notion page must be published to web for access.
    - `relationships` object — **How to link tags/authors:** 1. Get tag list via `GET /v1/tags` 2. Get author list via `GET /v1/authors` 3. Use the actual ID values from the response **Note:** Using non-existent IDs or IDs not belonging to this blog will result in a 400 error.
      - `tags` object — Tag relationships to attach to the post.
        - `data` ResourceIdentifier[] — Tag resource identifiers to attach.
          - `type` string, required — Resource type (required) - posts, tags, authors etc.
          - `id` string, required — Resource ID (required) - String format - Must be unique within its type **Important:** The example value (123) is a sample. For actual use: - Get Tag IDs: `GET /v1/tags` - Get Author IDs: `GET /v1/authors` Using non-existent IDs will result in a 400 error.
      - `authors` object — Author relationships to attach to the post.
        - `data` ResourceIdentifier[] — Author resource identifiers to attach.
          - `type` string, required — Resource type (required) - posts, tags, authors etc.
          - `id` string, required — Resource ID (required) - String format - Must be unique within its type **Important:** The example value (123) is a sample. For actual use: - Get Tag IDs: `GET /v1/tags` - Get Author IDs: `GET /v1/authors` Using non-existent IDs will result in a 400 error.

## Response `201`

Post created successfully

## Other responses

- `400` — Bad request
- `401` — Authentication failed
- `403` — Insufficient permissions
- `405` — Method not allowed
- `409` — Resource conflict
- `500` — 서버 내부 오류

---

[API](https://skmtc.dev/inblog/apis/inblog-api.md) · [All operations](https://skmtc.dev/inblog/apis/inblog-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/inblog/inblog-api/revisions/445daf773d30/schema)
