Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Reference Posts API
version: 1.0.0
servers:
- url: https://api.beehiiv.com/v2
description: Default
tags:
- name: Posts
paths:
/publications/{publicationId}/posts:
post:
operationId: create
summary: 'Create post <Badge intent="info" minimal outlined>OAuth Scope: posts:write</Badge>'
description: "<Note title=\"Available on the Max and Enterprise plans\">\n This endpoint is available to publications on the Max and Enterprise plans.\n</Note>\nCreate a post for a specific publication. For a detailed walkthrough including setup, testing workflows, and working with custom HTML and templates, see the <a href=\"https://www.beehiiv.com/support/article/36759164012439-using-the-send-api-and-create-post-endpoint\">Using the Send API and Create Post Endpoint</a> guide.\n\n## Asynchronous creation\n\nPost creation is processed in the background. This endpoint returns `201` immediately with the post's `id`, but the post is not finished being created at that moment. The returned `id` is stable — it is the same id the post will have once creation completes.\n\nIf you fetch the post (`GET /publications/{publicationId}/posts/{postId}`) right away, it may not exist yet. In that case the fetch returns `202` (still being created) — wait briefly and retry, honoring the `Retry-After` header. If background creation fails, the fetch returns `404` with the error code `POST_CREATION_FAILED`, so you should stop retrying, verify your request, and try creating the post again.\n\n## Content methods\n\nThere are three ways to provide content for a post. You must provide either `blocks` or `body_content`, but not both.\n\n### 1. Blocks\n\nUse the `blocks` field to build your post with structured content blocks such as paragraphs, images, headings, buttons, tables, and more. Each block has a `type` and its own set of properties. This method gives you fine-grained control over individual content elements and supports features like visual settings, visibility settings, and dynamic content targeting.\n\n### 2. Raw HTML (`body_content`)\n\nUse the `body_content` field to provide a single string of raw HTML. The HTML is wrapped in an `htmlSnippet` block internally. This is useful when you have pre-built HTML content or are migrating from another platform.\n\n### 3. HTML blocks within blocks\n\nUse `type: html` blocks inside the `blocks` array to embed raw HTML snippets alongside other structured blocks. This lets you mix structured content (paragraphs, images, etc.) with custom HTML where needed.\n\n## CSS and styling guardrails\n\nbeehiiv processes all HTML content through a sanitization pipeline. When using `body_content` or `html` blocks, be aware of the following:\n\n- **`<style>` tags are removed.** All `<style>` block elements are stripped during sanitization. Do not rely on embedded stylesheets.\n- **`<link>` tags are removed.** External stylesheet references are not allowed.\n- **Inline styles are preserved.** Styles applied directly to elements via the `style` attribute (e.g., `<div style=\"color: red;\">`) are kept intact.\n- **CSS classes have no effect.** While class attributes are not stripped, no corresponding stylesheets are loaded to apply them.\n- **beehiiv's email template wraps your content.** Your HTML is rendered inside beehiiv's email table structure, which applies its own layout and spacing. This may affect the appearance of your content.\n- **Use inline styles for all visual styling.** Since `<style>` and `<link>` tags are removed, inline styles on individual elements are the only reliable way to control appearance."
tags:
- Posts
parameters:
- name: publicationId
in: path
description: The prefixed ID of the publication object
required: true
schema:
$ref: '#/components/schemas/type_ids_PublicationId'
- name: Authorization
in: header
description: Bearer authentication
required: true
schema:
type: string
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/type_posts_PostsCreateResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'401':
description: Unauthorized. The API key or OAuth access token is missing, invalid, or expired.
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'404':
description: Resource Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'429':
description: Rate Limit Exceeded
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
requestBody:
content:
application/json:
schema:
type: object
properties:
blocks:
type: array
items:
$ref: '#/components/schemas/type_posts_Block'
description: The structured content blocks that make up the post. Supports block types such as paragraph, image, heading, button, html, table, list, columns, and more. You can embed raw HTML snippets within blocks using the `html` block type. Either this field OR the `body_content` field must be provided.
body_content:
type: string
description: The content of the post as a single raw HTML string. The HTML is wrapped in an `htmlSnippet` block internally. Note that `<style>` and `<link>` tags are removed during sanitization — use inline styles for all visual styling. Either this field OR the `blocks` field must be provided.
title:
type: string
description: The title of the post.
subtitle:
type: string
description: The subtitle of the post.
post_template_id:
$ref: '#/components/schemas/type_ids_PostTemplateId'
description: The ID of the template to use for the post. If not provided, the default template will be used.
status:
$ref: '#/components/schemas/type_posts_PostPublishStatus'
description: The status of the post. If not provided, the default (`draft`) value will be used and the post will not be scheduled or published. Explicitly pass `confirmed` to have the post publish immediately (if no `scheduled_at` is provided) or at the `scheduled_at` time.
scheduled_at:
type: string
format: date-time
description: The time in which the post will be published. If not provided, the post will be published immediately unless `status` is set to `draft`. A draft post cannot be scheduled.
custom_link_tracking_enabled:
type: boolean
description: If true, custom link tracking will be enabled for this post. If not provided, the default value will be used.
email_capture_type_override:
$ref: '#/components/schemas/type_posts_PostEmailCaptureTypeOverride'
description: The email capture type to use for this post. If not provided, the default value will be used.
override_scheduled_at:
type: string
format: date-time
description: If you wish to display a date other than the scheduled_at date in the email, you can provide a date here. This will not affect the actual publish date of the post.
social_share:
$ref: '#/components/schemas/type_posts_PostSocialShare'
description: The social share type to use for this post. If not provided, the default value will be used.
thumbnail_image_url:
type: string
description: The URL of the thumbnail image to use for the post. If not provided, the default value will be used.
recipients:
$ref: '#/components/schemas/type_posts_PostRecipients'
description: The recipients to use for this post. If not provided, the default value will be used.
email_settings:
$ref: '#/components/schemas/type_posts_PostEmailSettings'
description: The email settings to use for this post. If not provided, the default value will be used.
web_settings:
$ref: '#/components/schemas/type_posts_PostWebSettings'
description: The web settings to use for this post. If not provided, the default value will be used.
seo_settings:
$ref: '#/components/schemas/type_posts_PostMetadata'
description: The metadata to use for this post. If not provided, the default value will be used.
content_tags:
type: array
items:
type: string
description: The content tags to use for this post. If not provided, the default value will be used.
guest_author_ids:
type: array
items:
$ref: '#/components/schemas/type_ids_GuestAuthorId'
description: The prefixed IDs of the guest authors to associate with this post. Guest authors must belong to the publication. Obtain IDs from the List Authors endpoint. When provided, replaces all existing guest authors on the post.
team_author_ids:
type: array
items:
$ref: '#/components/schemas/type_ids_TeamAuthorId'
description: The prefixed IDs of the team members to associate with this post as authors. Team authors must have access to the publication. Note the List Authors endpoint only returns team members who already have a byline on a published post in this publication, so it will not surface IDs for a team member's first assignment. When provided, replaces all existing team authors on the post.
headers:
type: object
additionalProperties:
type: string
description: The headers to use for this post. If not provided, the default value will be used.
custom_fields:
type: object
additionalProperties:
type: string
description: The custom fields to use for this post. If not provided, the default value will be used.
newsletter_list_id:
type: string
description: The prefixed ID of the newsletter list to associate with this post. When provided, the post will only be sent to subscribers of this list.
required:
- title
get:
operationId: index
summary: 'List posts <Badge intent="info" minimal outlined>OAuth Scope: posts:read</Badge>'
description: Retrieve all posts belonging to a specific publication
tags:
- Posts
parameters:
- name: publicationId
in: path
description: The prefixed ID of the publication object
required: true
schema:
$ref: '#/components/schemas/type_ids_PublicationId'
- name: expand
in: query
description: Optionally expand the results by adding additional information. <br>`stats` - Adds statistics about the post(s). <br>`free_web_content` - Adds the web HTML rendered to a free reader. <br>`free_email_content` - Adds the email HTML rendered to a free reader. <br>`free_rss_content` - Adds the RSS feed HTML. <br>`premium_web_content` - Adds the web HTML rendered to a premium reader. <br>`premium_email_content` - Adds the email HTML rendered to a premium reader.
required: false
schema:
type: array
items:
$ref: '#/components/schemas/type_posts_PostExpandField'
- name: audience
in: query
description: Optionally filter the results by audience
required: false
schema:
$ref: '#/components/schemas/type_posts_PostAudienceFilter'
- name: platform
in: query
description: Optionally filter the results by platform.<br>`web` - Posts only published to web.<br>`email` - Posts only published to email.<br>`both` - Posts published to email and web.<br>`all` - Does not restrict results by platform.
required: false
schema:
$ref: '#/components/schemas/type_posts_PostPlatformFilter'
- name: status
in: query
description: Optionally filter the results by the status of the post.<br>`draft` - not been scheduled.<br>`confirmed` - The post will be active after the `scheduled_at`.<br>`archived` - The post is no longer active.<br>`all` - Does not restrict results by status.
required: false
schema:
$ref: '#/components/schemas/type_posts_PostStatusFilter'
- name: content_tags[]
in: query
description: 'Optionally filter posts by content_tags. Adding a content tag will return any post with that content tag associated to it.<br><br><b>Example</b>: Filtering for `content_tags: ["sales","closing"]` will return results of posts that have *either* `sales` or `closing` content_tags.'
required: false
schema:
type: array
items:
type: string
- name: slugs[]
in: query
description: 'Optionally filter posts by their slugs. Adding a slug will return any post with that exact slug associated to it.<br><br><b>Example:</b> Filtering for `slugs: ["my-first-post","another-post"]` will return results of posts that have *either* `my-first-post` or `another-post` as their slug.'
required: false
schema:
type: array
items:
type: string
- name: authors[]
in: query
description: 'Optionally filter posts by their authors. Adding an author name will return any post with that author associated to it (case-insensitive).<br><br><b>Example:</b> Filtering for `authors: ["John Doe","Jane Smith"]` will return results of posts that have *either* John Doe or Jane Smith as authors.'
required: false
schema:
type: array
items:
type: string
- name: premium_tiers
in: query
description: 'Optionally filter posts by audience based on premium tiers.<br> This takes in an array of Display Names of the premium tiers.<br> It will also scope any expanded content output to the specified premium tiers.<br> Note: This is case insensitive.'
required: false
schema:
type: array
items:
type: string
- name: limit
in: query
description: A limit on the number of objects to be returned. The limit can range between 1 and 100, and the default is 10.
required: false
schema:
type: integer
- name: page
in: query
description: 'Pagination returns the results in pages. Each page contains the number of results specified by the `limit` (default: 10).<br>If not specified, results 1-10 from page 1 will be returned.'
required: false
schema:
type: integer
- name: order_by
in: query
description: The field that the results are sorted by. Defaults to created<br> `created` - The time in which the post was first created.<br> `publish_date` - The time the post was set to be published.<br> `displayed_date` - The time displayed in place of the `publish_date`. If no `displayed_date` was set, it will default to the `publish_date`
required: false
schema:
$ref: '#/components/schemas/type_posts_PostOrderBy'
- name: direction
in: query
description: The direction that the results are sorted in. Defaults to asc<br> `asc` - Ascending, sorts from smallest to largest.<br> `desc` - Descending, sorts from largest to smallest.
required: false
schema:
$ref: '#/components/schemas/type__RequestDirection'
- name: hidden_from_feed
in: query
description: Optionally filter the results by the `hidden_from_feed` attribute of the post.<br>`all` - Does not restrict results by `hidden_from_feed`.<br>`true` - Only return posts hidden from the feed.<br>`false` - Only return posts that are visible on the feed.
required: false
schema:
$ref: '#/components/schemas/type_posts_PostsListRequestHiddenFromFeed'
- name: Authorization
in: header
description: Bearer authentication
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/type_posts_PostsListResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'401':
description: Unauthorized. The API key or OAuth access token is missing, invalid, or expired.
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'404':
description: Resource Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'429':
description: Rate Limit Exceeded
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
/publications/{publicationId}/posts/{postId}:
patch:
operationId: update
summary: 'Update post <Badge intent="info" minimal outlined>OAuth Scope: posts:write</Badge>'
description: "<Note title=\"Available on the Max and Enterprise plans\">\n This endpoint is available to publications on the Max and Enterprise plans.\n</Note>\nUpdate an existing post for a specific publication. Only the fields provided in the request body will be updated — all other fields remain unchanged. For a detailed walkthrough of content methods and working with custom HTML, see the <a href=\"https://www.beehiiv.com/support/article/36759164012439-using-the-send-api-and-create-post-endpoint\">Using the Send API and Create Post Endpoint</a> guide.\n\nTo update post content, provide either `blocks` or `body_content` (not both). If neither is provided, the existing content is preserved. The same content methods and CSS guardrails described in the create endpoint apply here."
tags:
- Posts
parameters:
- name: publicationId
in: path
description: The prefixed ID of the publication object
required: true
schema:
$ref: '#/components/schemas/type_ids_PublicationId'
- name: postId
in: path
description: The prefixed ID of the post to update
required: true
schema:
$ref: '#/components/schemas/type_ids_PostId'
- name: Authorization
in: header
description: Bearer authentication
required: true
schema:
type: string
responses:
'200':
description: Updated
content:
application/json:
schema:
$ref: '#/components/schemas/type_posts_PostsGetResponse'
'202':
description: The post was created via the Send API and is still being processed in the background. Wait briefly and retry, honoring the `Retry-After` header.
content:
application/json:
schema:
$ref: '#/components/schemas/type__PostProcessing'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'401':
description: Unauthorized. The API key or OAuth access token is missing, invalid, or expired.
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'404':
description: Resource Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'429':
description: Rate Limit Exceeded
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
requestBody:
content:
application/json:
schema:
type: object
properties:
content_merge_strategy:
$ref: '#/components/schemas/type_posts_ContentMergeStrategy'
description: Controls how provided `blocks` interact with the post's existing content. Defaults to `replace`. Use `append_to_template` to preserve template header blocks when updating posts that were created from a template.
blocks:
type: array
items:
$ref: '#/components/schemas/type_posts_Block'
description: 'The structured content blocks for the post. How these interact with existing content depends on `content_merge_strategy` (default: full replacement). Supports block types such as paragraph, image, heading, button, html, table, list, columns, and more. Cannot be provided together with `body_content`.'
body_content:
type: string
description: Raw HTML to replace the post's current content. The HTML is wrapped in an `htmlSnippet` block internally. Note that `<style>` and `<link>` tags are removed during sanitization — use inline styles for all visual styling. Cannot be provided together with `blocks`.
title:
type: string
description: The title of the post.
subtitle:
type: string
description: The subtitle of the post.
scheduled_at:
type: string
format: date-time
description: The time at which the post will be published. Cannot be updated after the post has already been sent.
custom_link_tracking_enabled:
type: boolean
description: If true, custom link tracking will be enabled for this post.
email_capture_type_override:
$ref: '#/components/schemas/type_posts_PostEmailCaptureTypeOverride'
description: The email capture type to use for this post.
override_scheduled_at:
type: string
format: date-time
description: A display date that overrides the scheduled_at date shown in the email. Does not affect the actual publish date.
social_share:
$ref: '#/components/schemas/type_posts_PostSocialShare'
description: The social share type to use for this post.
thumbnail_image_url:
type: string
description: The URL of the thumbnail image to use for the post.
email_settings:
$ref: '#/components/schemas/type_posts_PostEmailSettings'
description: The email settings to use for this post. Only the provided fields within this object will be updated.
web_settings:
$ref: '#/components/schemas/type_posts_PostWebSettings'
description: The web settings to use for this post. Only the provided fields within this object will be updated.
seo_settings:
$ref: '#/components/schemas/type_posts_PostMetadata'
description: The SEO metadata to use for this post. Only the provided fields within this object will be updated.
status:
$ref: '#/components/schemas/type_posts_PostPublishStatus'
description: Transition the post's status. Use `confirmed` to schedule or publish a draft post. Only the `draft` → `confirmed` transition is supported — a confirmed post cannot be moved back to `draft`.
content_tags:
type: array
items:
type: string
description: The content tags for this post. When provided, this replaces all existing content tags on the post.
headers:
type: object
additionalProperties:
type: string
description: Custom email headers for this post. Merges with publication-level headers at send time. System-managed headers (e.g. List-Unsubscribe, X-SMTPAPI) cannot be overridden.
guest_author_ids:
type: array
items:
$ref: '#/components/schemas/type_ids_GuestAuthorId'
description: The prefixed IDs of the guest authors to associate with this post. Guest authors must belong to the publication. When provided, replaces all existing guest authors on the post.
team_author_ids:
type: array
items:
$ref: '#/components/schemas/type_ids_TeamAuthorId'
description: The prefixed IDs of the team members to associate with this post as authors. Team authors must have access to the publication. When provided, replaces all existing team authors on the post.
recipients:
$ref: '#/components/schemas/type_posts_PostRecipients'
description: The recipients for this post. When provided, replaces all existing web and email audience targets with the specified values. If omitted, existing targets are unchanged.
newsletter_list_id:
type: string
description: The prefixed ID of the newsletter list to associate with this post. When provided, updates the newsletter list association. Pass null to remove the association.
get:
operationId: show
summary: 'Get post <Badge intent="info" minimal outlined>OAuth Scope: posts:read</Badge>'
description: 'Retrieve a single Post belonging to a specific publication.
Posts created via the Send API are built asynchronously, so a recently created post may not exist yet. When the post is still being created, this endpoint returns `202` (still being created) with a `Retry-After` header — wait briefly and retry. If background creation failed, it returns `404` with the error code `POST_CREATION_FAILED`, indicating the post will never become available; verify your request and try creating it again.'
tags:
- Posts
parameters:
- name: publicationId
in: path
description: The prefixed ID of the publication object
required: true
schema:
$ref: '#/components/schemas/type_ids_PublicationId'
- name: postId
in: path
description: The prefixed ID of the post object
required: true
schema:
$ref: '#/components/schemas/type_ids_PostId'
- name: expand
in: query
description: Optionally expand the results by adding additional information. <br>`stats` - Adds statistics about the post(s). <br>`free_web_content` - Adds the web HTML rendered to a free reader. <br>`free_email_content` - Adds the email HTML rendered to a free reader. <br>`free_rss_content` - Adds the RSS feed HTML. <br>`premium_web_content` - Adds the web HTML rendered to a premium reader. <br>`premium_email_content` - Adds the email HTML rendered to a premium reader.
required: false
schema:
type: array
items:
$ref: '#/components/schemas/type_posts_PostExpandField'
- name: premium_tiers
in: query
description: 'Scope any expanded content output to the specified premium tiers.<br> This takes in an array of Display Names of the premium tiers.<br> Note: This is case insensitive.'
required: false
schema:
type: array
items:
type: string
- name: Authorization
in: header
description: Bearer authentication
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/type_posts_PostsGetResponse'
'202':
description: The post was created via the Send API and is still being processed in the background. Wait briefly and retry, honoring the `Retry-After` header.
content:
application/json:
schema:
$ref: '#/components/schemas/type__PostProcessing'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'401':
description: Unauthorized. The API key or OAuth access token is missing, invalid, or expired.
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'404':
description: Resource Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'429':
description: Rate Limit Exceeded
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/type__Error'
delete:
operationId: delete
summary: 'Delete post <Badge intent="info" minimal outlined>OAuth Scope: posts:write</Badge>'
description: Delete or Archive a post. Any post that has been confirmed will have it's status changed to `archived`. Posts in the `draft` status will be permanently deleted.
tags:
- Posts
parameters:
- name: publicationId
in: path
description: The prefixed ID of the publication object
required: true
schema:
$ref: '#/components/schemas/type_ids_PublicationId'
- name: postId
in: path
description: The prefixed ID of the post object
required: true
schema:
$ref: '#/components/schemas/type_ids_PostId'
- name: Authorization
in: header
description: Bearer authentication
required: true
schema:
type: string
responses:
'202':
description: The post was created via the Send API and is still being processed in the background. Wait briefly and retry, honoring the `Retry-After` header.
content:
application/json:
schema:
$ref: '#/components/schemas/type__PostProcessing'
'204':
description: No Content
content:
application/
# --- truncated at 32 KB (127 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/beehiiv/refs/heads/main/openapi/beehiiv-posts-api-openapi.yml