Fields, Geo & Polls
Platform-specific fields, geo-restriction, polls, and media URL requirements
Platform-Specific Fields
All fields go inside platformSpecificData on the Twitter platform entry.
| Field | Type | Description |
|---|---|---|
replyToTweetId | string | ID of an existing tweet to reply to. The published tweet will appear as a reply in that tweet's thread. For threads, only the first tweet replies to the target; subsequent tweets chain normally. |
quoteTweetId | string | ID (or full status URL) of an existing tweet to quote-repost. Mutually exclusive with media and poll. For threads, applies to the first tweet only. |
replySettings | "following" | "mentionedUsers" | "subscribers" | "verified" | Controls who can reply to the tweet. Omit for default (everyone can reply). For threads, applies to the first tweet only. Cannot be combined with replyToTweetId. |
threadItems | Array<{content, mediaItems?}> | Complete sequence of tweets in a thread. The first item becomes the root tweet and must be provided as threadItems[0]. When threadItems is provided, top-level content is for display/search only and is NOT published. |
poll | object | Create a poll with this tweet. Mutually exclusive with media attachments and threads. |
poll.options | string[] | Poll options (2-4 choices, max 25 characters each). |
poll.duration_minutes | number | Poll duration in minutes (5 min to 7 days). |
longVideo | boolean | Enable long video uploads (over 140 seconds) using amplify_video. Requires X Premium; may require allowlisting. |
geoRestriction | object | Restrict media visibility to specific countries. Only applies when media is attached (ignored for text-only tweets). geoRestriction.countries: array of uppercase ISO 3166-1 alpha-2 codes, max 25. The media is hidden in restricted countries; the tweet text remains visible globally. |
paidPartnership | boolean | When true, label the post as a paid partnership / paid promotion. For threads, applies to the root tweet only. Field availability may depend on your X API access tier. |
madeWithAi | boolean | When true, label the post as containing AI-generated media (not AI-written text). For threads, applies to the root tweet only. |
sensitiveMedia | object | Marks attached media with a sensitive-content warning (requires media; ignored for text-only tweets). At least one flag must be true. |
sensitiveMedia.adultContent | boolean | Content contains adult material. |
sensitiveMedia.graphicViolence | boolean | Content depicts graphic violence. |
sensitiveMedia.other | boolean | Content has other sensitive characteristics. |
Geo-Restriction
Restrict who can see your tweet's media by country. This applies at the media level: the media is hidden for users outside the specified countries, but the tweet text remains visible globally. Requires media to be attached.
{
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"geoRestriction": {
"countries": ["US", "ES"]
}
}
}],
"mediaItems": [{"type": "image", "url": "https://example.com/photo.jpg"}]
}Paid partnership, AI labels, and sensitive media
Zernio supports additional X labels via platformSpecificData.
Note:
paidPartnershipandmadeWithAiapply to the root tweet only (for threads).
Paid partnership label
Set platformSpecificData.paidPartnership: true to label the post as a paid partnership / paid promotion.
Note: Field availability may depend on your X API access tier.
curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Sponsored: our new feature is live",
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"paidPartnership": true
}
}],
"publishNow": true
}'const { post } = await zernio.posts.createPost({
content: 'Sponsored: our new feature is live',
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
paidPartnership: true
}
}],
publishNow: true
});
console.log('Tweet posted!', post._id);result = client.posts.create_post(
content="Sponsored: our new feature is live",
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"paidPartnership": True
}
}],
publish_now=True
)
post = result.post
print(f"Tweet posted! {post['_id']}")AI-generated media label
Set platformSpecificData.madeWithAi: true to label the post as containing AI-generated media.
Note: Per X, this label is for AI-generated media, not AI-written text.
curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "AI-generated image (labeled)",
"mediaItems": [
{"type": "image", "url": "https://cdn.example.com/ai-image.jpg"}
],
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"madeWithAi": true
}
}],
"publishNow": true
}'const { post } = await zernio.posts.createPost({
content: 'AI-generated image (labeled)',
mediaItems: [{ type: 'image', url: 'https://cdn.example.com/ai-image.jpg' }],
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
madeWithAi: true
}
}],
publishNow: true
});
console.log('Tweet posted!', post._id);result = client.posts.create_post(
content="AI-generated image (labeled)",
media_items=[{"type": "image", "url": "https://cdn.example.com/ai-image.jpg"}],
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"madeWithAi": True
}
}],
publish_now=True
)
post = result.post
print(f"Tweet posted! {post['_id']}")Sensitive media warning
Use platformSpecificData.sensitiveMedia to mark attached media with a sensitive-content warning.
Note: Requires media (ignored for text-only tweets). At least one flag must be
true.
curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Sensitive media example",
"mediaItems": [
{"type": "image", "url": "https://cdn.example.com/photo.jpg"}
],
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"sensitiveMedia": {
"adultContent": true,
"graphicViolence": false,
"other": false
}
}
}],
"publishNow": true
}'const { post } = await zernio.posts.createPost({
content: 'Sensitive media example',
mediaItems: [{ type: 'image', url: 'https://cdn.example.com/photo.jpg' }],
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
sensitiveMedia: {
adultContent: true,
graphicViolence: false,
other: false
}
}
}],
publishNow: true
});
console.log('Tweet posted!', post._id);result = client.posts.create_post(
content="Sensitive media example",
media_items=[{"type": "image", "url": "https://cdn.example.com/photo.jpg"}],
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"sensitiveMedia": {
"adultContent": True,
"graphicViolence": False,
"other": False
}
}
}],
publish_now=True
)
post = result.post
print(f"Tweet posted! {post['_id']}")Polls
Create a Twitter/X poll by providing platformSpecificData.poll.
Note: Polls are mutually exclusive with
mediaItemsandplatformSpecificData.threadItems.
curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Which feature should we ship next?",
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"poll": {
"options": ["Dark mode", "New analytics", "More integrations"],
"duration_minutes": 1440
}
}
}],
"publishNow": true
}'const { post } = await zernio.posts.createPost({
content: 'Which feature should we ship next?',
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
poll: {
options: ['Dark mode', 'New analytics', 'More integrations'],
duration_minutes: 1440
}
}
}],
publishNow: true
});
console.log('Poll posted!', post._id);result = client.posts.create_post(
content="Which feature should we ship next?",
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"poll": {
"options": ["Dark mode", "New analytics", "More integrations"],
"duration_minutes": 1440
}
}
}],
publish_now=True
)
post = result.post
print(f"Poll posted! {post['_id']}")Media URL Requirements
These do not work as media URLs:
- Google Drive -- returns an HTML download page, not the file
- Dropbox -- returns an HTML preview page
- OneDrive / SharePoint -- returns HTML
- iCloud -- returns HTML
Test your URL in an incognito browser window. If you see a webpage instead of the raw image or video, it will not work.
Media URLs must be:
- Publicly accessible (no authentication required)
- Returning actual media bytes with the correct
Content-Typeheader - Not behind redirects that resolve to HTML pages
- Hosted on a fast, reliable CDN
Supabase URLs: Zernio auto-proxies Supabase storage URLs, so they work without additional configuration.