Create post
Create a post, and optionally publish it in the same request. A post published immediately (publishNow: true) comes back with platformPostUrl in the response.
content is optional in four cases:
- media is attached
- all platforms have
customContent - every platform entry is an X Article (
platformSpecificData.article) - every platform entry is a LinkedIn text-free reshare (
platformSpecificData.reshareUrlwith no text)
See each platform's schema for media constraints.
Scheduling
Pick one of:
scheduledFor: publish at the scheduled timepublishNow: true: publish synchronously, inside this requestqueuedFromProfile: publish in the profile's next queue slot
With none of them and isDraft unset, the post is saved as a draft. platforms is required unless the post is a draft.
Precedence: isDraft: true wins over publishNow and scheduledFor (the post is saved, never published), and publishNow: true wins over scheduledFor. A scheduledFor already in the past is not rejected: the post is published synchronously in the same request, exactly like publishNow.
Idempotency
Three layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post.
1. Idempotency-Key (recommended for retries, 24-hour window).
Send an Idempotency-Key header (a UUID per logical post, up to 255 characters) and reuse it when you retry after a 5xx, a timeout or a dropped connection. A retry with the same key within 24 hours returns HTTP 200 with the original post in the post field, and no new post is created. The match is on the key alone, not on the body: a retry returns the original post even if its body differs (for example a re-uploaded media URL). It covers drafts, and a post that was saved even though the original response was a 5xx or timed out.
If the first request is still being processed, the retry gets HTTP 409 with code: idempotency_conflict and a Retry-After header; retry after that delay. If the first request failed without creating a post, the retry is processed as a new request. Keys are scoped to your user. Never reuse a key for a different post: that post would not be created, and the earlier one would be returned instead.
2. x-request-id retry detection (24-hour window).
If a request carries the same x-request-id as an earlier one AND collides with it on the content fingerprint below (same account, content and media URLs), we return HTTP 200 with the original post in the post field (or HTTP 202 with postId if the original is still being saved) instead of a 409. A request with the same x-request-id but different content is a new post. When both headers are sent, Idempotency-Key wins and x-request-id is ignored for matching.
The official Zernio SDKs auto-generate a unique x-request-id per call. On a generic HTTP client (curl, n8n's HTTP node, Zapier, custom code), either:
- Set a unique
x-request-idper logical call (UUIDv4 is fine) - Or omit the header, and we'll treat each request as new
3. Content-hash dedup (24-hour window).
Independently, we hash (platform, accountId, content + media URLs) and reject duplicates within 24 hours with HTTP 409. This catches genuine "same content posted twice to the same account" cases regardless of x-request-id. The response carries error, accountId, platform, and existingPostId so you can find the original.
To intentionally re-post identical content within 24h, change something (the caption, the media, the account), because the dedup is keyed on the full content fingerprint.
Order: an Idempotency-Key match (200) is checked first, then same-x-request-id retries (200), then the content-hash dedup (409).
Authorization
bearerAuth API key authentication: send your Zernio API key in the Authorization header, prefixed with Bearer.
In: header
Header Parameters
Optional client-generated key (a UUID per logical post) that makes retries safe. Reuse it on every retry of the same post, especially after a 5xx or a timeout. A retry with the same key within 24 hours returns the original post (HTTP 200) whatever its body; while the original is still being processed it returns 409 idempotency_conflict with Retry-After. Longer than 255 characters returns 400. Takes precedence over x-request-id. See the operation description for the full contract.
length <= 255Optional client-generated request identifier. A request with the same value as an earlier one within 24 hours that also matches its content fingerprint (same account, content and media URLs) returns the original post (HTTP 200) instead of a duplicate-content 409. It does not protect a retry whose body differs; use Idempotency-Key for that. SDKs set it automatically. See the operation description for the full idempotency contract.
uuidRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
import Zernio from '@zernio/node';const zernio = new Zernio({ apiKey: process.env.ZERNIO_API_KEY });const { data } = await zernio.posts.createPost({ body: { title: 'Example', content: 'Hello, world!', mediaItems: [ { type: 'image', url: 'https://example.com', title: 'Example', }, ], },});console.log(data);{ "dryRun": true, "canPublish": true, "tiktok": [ { "accountId": "string", "canPublish": true, "alreadyPostedToday": true, "reason": "string" } ]}List posts
Returns a paginated list of posts. Published posts include platformPostUrl with the public URL on each platform. A query parameter that is not listed here returns 400 naming it and the accepted parameters, so a misspelled filter never silently returns the unfiltered list.
Get post
Fetch a single post by ID. For published posts, this returns platformPostUrl for each platform.