Posts
Create post draft
Creates a workspace post draft, optionally assigned to a LinkedIn profile, company page or X account. Never publishes by itself. For an X account pass exactly one of content, tweets or article.
/api/public/postscurl -X POST https://postiv.ai/api/public/posts \
-H "Authorization: Bearer pk_postiv_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Launch lessons",
"content": "Three things we learned from launch week...",
"humanize": true
}'{
"success": true,
"post": {
"id": "post1...",
"channel": "linkedin",
"status": "draft",
"content": "Most founders think..."
},
"humanized": false
}Authorization
Requires an organization API key with the posts:write scope, sent as Authorization: Bearer or x-api-key.
Body parameters
typestringOnly 'document' is accepted; omit to default.
documenttitlestringMax 240 chars.
contentstringdefault: ""LinkedIn post text, max 100000 chars. For an X account, plain text that is auto-split into tweets.
tweetsstring|arrayX accounts only, instead of content: a tweet string, or an array (a thread) of tweet strings or { text, assets? }, where assets is [{ assetId, alt (max 2000) }]. Needs an X integrationId (else 400 x_account_required).
articleobjectX accounts only, instead of content: { headline (max 300), body (max 100000) }. headline is plain text; body is markdown: # ## ### headings, bold, *italic*, text links, - and 1. lists, > quotes, a blank line between paragraphs and a single newline for a line break. Markdown images are refused (400 invalid_x_content): place images with attach-post-media.
integrationIdint | nullAlias: integration_id. A LinkedIn profile, company page or X account. Omit or pass null for an unassigned LinkedIn draft; an X draft needs an X account. Scheduling requires a connected account.
templateIduuidAlias: template_id, from search_templates. LinkedIn only: with an X account it returns 400 template_linkedin_only.
planItemIduuidAlias: plan_item_id, from get_content_plan_item.
humanizebooleandefault: falseOpt-in: runs the humanizer pass over content before saving. Defaults to false in both REST and MCP create_post. LinkedIn only: with an X account it returns 400 humanize_linkedin_only.
linkPreviewUrlstring | nullLinkedIn only: which link in content publishes as a preview card (title and thumbnail). Omit to put the card on the first link, pass one of the links in content to choose another, or null for no card. Media (image, video, carousel, PDF) publishes instead of the card. 400 invalid_link_preview_url when the link is not in content; 400 link_preview_linkedin_only with an X account.
Response
The created post.
postobjectFull post object: same shape as GET /api/public/posts/:id.
humanizedbooleanWhether the humanizer changed the content.
Errors
| 400 | content_too_long_to_humanize: content over 20000 chars with humanize on |
| 400 | invalid_x_content: the tweets, article or content do not make a valid X post, or more than one of content, tweets and article was given (details.issues lists the problems) |
| 400 | humanize_linkedin_only / template_linkedin_only: humanize or templateId sent with an X account |
| 400 | x_account_required: tweets or article sent without an X account |
| 402 | subscription_required (from checkSubscriptionAccess inside createPostDraft) |
| 404 | linkedin_profile_not_found (LinkedIn) / integration_not_found (X account missing or needing reconnection) / post_template_not_found |
Good to know
- For LinkedIn posts, Markdown emphasis (bold, *italic*) is always converted to Unicode glyphs before saving: LinkedIn renders everything else as literal characters. planItemId links the draft back to Bob's plan and marks that item drafted.
- X drafts save as written: no humanizer and no Markdown-to-Unicode conversion. A tweet over 280 characters (a link counts 23) is allowed in a draft, but scheduling refuses it. The returned post carries channel and format.