Publishing
Publishing posts content to the social accounts an agent has connected, immediately or at a scheduled time, and reports per platform whether the post went live, with the link to it.
One request can target several platforms at once. Every request is validated against each platform’s rules (media type, item count, caption and title limits, required fields) before anything is queued, so a bad request fails fast with the platform and field named.
Platforms
Section titled “Platforms”| Platform | platform | Content | Media per post | Caption limit | Title | Notes |
|---|---|---|---|---|---|---|
| TikTok | tiktok | one video or one image | 1 | 2,200 | ignored | Privacy level and interaction settings are required by TikTok. |
instagram | one video (posts as a Reel), one image, or a carousel of 2-10 images and videos | 10 | 2,200 | ignored | Up to 30 hashtags. Optional instagram_music mixes a track under the video items. | |
| Facebook Page | facebook | one video | 1 | 63,206 | 255 | Posts to the connected Page. |
| YouTube | youtube | one video | 1 | 5,000 (description) | 100, required | thumbnail_url (https, .jpg/.jpeg/.png, under 2 MB) becomes the custom thumbnail. youtube_options: visibility, shorts, tags, made_for_kids, notify_subscribers, category_id. |
| X | x | text, up to 4 images, or one video; threads and replies | 4 | 280 per tweet | ignored | Text-only posts allowed. |
linkedin | text, one video, 1-9 images (a multi-image grid, or a swipeable carousel with linkedin_options.carousel), or one document (PDF, PPT, PPTX, DOC, DOCX) | 9 | 3,000 | 400 | Text-only posts allowed. Posts as the connected member. LinkedIn has no image carousel for organic posts: carousel: true builds one PDF from the images and posts it as a document. Images sent with a video: the images post, the video is left out and reported in warning. A document cannot be mixed with other media. linkedin_options: visibility, alt_text, carousel. |
These rules are served live and are what the API validates against:
GET /v1/schedule/platformsNo authentication required. Each entry has platform, label, text_only, media (video, image, document), max_media_items, max_text, max_title, required, optional, ignored, options (the platform’s options object and the values each field accepts), editable_after_publish (the fields that can still be changed once the post is live — see Edit a post that is already live) and notes.
curl "https://api.gen.pro/v1/schedule/platforms"{ "platforms": [ { "platform": "linkedin", "label": "LinkedIn", "text_only": true, "media": ["video", "image", "document"], "max_media_items": 9, "max_text": 3000, "max_title": 400, "required": [], "optional": ["description", "media_url", "media_urls", "title", "thumbnail_url", "linkedin_options"], "ignored": ["thread", "reply_to_tweet_id"], "editable_after_publish": [], "options": { "linkedin_options": { "visibility": "public (default) | connections | loggedin", "alt_text": "string[] — alternative text per image, in media order (images only)", "carousel": "bool — post 2-9 images as a swipeable LinkedIn carousel (a PDF document built from the images, one page each) instead of the multi-image grid" } }, "notes": "Posts as the connected member. Text-only, one video, 1-9 images (a multi-image grid, or with linkedin_options.carousel a swipeable carousel = a PDF document built from the images), or one document URL (PDF, PPT, PPTX, DOC, DOCX; title is the document title, max 400). LinkedIn has no image carousel for organic posts. When images and a video are sent together LinkedIn gets the images and the video is left out (reported in `warning`). A document cannot be mixed with other media." } ]}Create a post
Section titled “Create a post”POST /v1/schedule/with-postHeaders
Section titled “Headers”| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key. |
Content-Type | Yes | application/json |
Idempotency-Key | No | Any string up to 128 characters, unique per agent. Retrying with the same key returns the original post instead of creating a duplicate (idempotent_replay: true). |
X-Timezone-Offset | No | Local UTC offset in hours (for example -7). Times in the response are rendered in this offset; defaults to the agent’s time zone. |
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | Yes | The agent whose connected accounts post. |
platform | string[] | Yes | One or more of tiktok, instagram, facebook, youtube, x, linkedin. |
schedule_type | string | No | now posts immediately. specific_time posts at scheduled_time. next_available (default) takes the agent’s next free calendar slot. |
scheduled_time | string | Conditional | ISO 8601 UTC, for example 2026-09-20T15:00:00Z. Required for specific_time; must be in the future. |
description | string | Conditional | Caption or post text. Hashtags inline. Required for a text-only X or LinkedIn post. Limited per platform (see the table above). |
title | string | Conditional | Required for YouTube (max 100). Optional caption for Facebook (255) and LinkedIn (400). Ignored elsewhere. |
media_type | string | No | VIDEO (default), IMAGE, REELS, or TWEET_VIDEO. Use IMAGE when sending images. |
media_url | string | Conditional | Public URL of the first (or only) media file: a video, an image, or for LinkedIn a PDF/PPT/PPTX/DOC/DOCX document. Required for TikTok, Instagram, Facebook and YouTube. |
media_urls | string[] | No | Further media URLs for the same post. Instagram: media_url plus media_urls totalling 2-10 images and videos post as one carousel. X: up to 4 images. LinkedIn: up to 9 images. One item elsewhere. The file extension decides image vs video vs document. |
thumbnail_url | string | No | Custom thumbnail. Generated from the video when omitted. YouTube uploads it as the video’s thumbnail (https, .jpg, .jpeg or .png, under 2 MB); a thumbnail problem is reported as warning, the video still publishes. |
thread | object[] | No | X only. Ordered tweet segments, each { "text": "...", "media_urls": ["..."] }. Each segment is limited to 280 characters. |
reply_to_tweet_id | string | No | X only. Post as a reply to this tweet id. |
tiktok_privacy_level | string | TikTok | PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR or SELF_ONLY. |
tiktok_disable_comment | boolean | TikTok | Disable comments. |
tiktok_disable_duet | boolean | TikTok | Disable duets. |
tiktok_disable_stitch | boolean | TikTok | Disable stitches. |
tiktok_disclose_commercial_content | boolean | No | The post promotes goods or services. Default false. |
tiktok_your_brand | boolean | No | Promotes the creator’s own brand. Default false. |
tiktok_branded_content | boolean | No | Paid partnership. Default false. |
instagram_music | object | No | Instagram only. { "mode": "auto" } mixes a track from the GEN music library under every video item; { "track_url": "https://...mp3" } mixes that file instead. Needs at least one video item (Instagram cannot attach audio to images). The track used comes back as music_track on the platform status; when no library track is available the post publishes without music and music_track is null. |
youtube_options | object | No | YouTube only. visibility: public (default), unlisted or private. shorts: true posts as a Short (adds #shorts; keep the video vertical and under 3 minutes). tags: string[], each 2+ characters, 400 characters in total. made_for_kids (default false), notify_subscribers (default true), category_id (default 22). |
linkedin_options | object | No | LinkedIn only. visibility: public (default), connections or loggedin. alt_text: string[] with one entry per image, in media order (images only). carousel: true posts 2-9 images as a swipeable carousel, which on LinkedIn is a PDF document built from the images (one page each), instead of the multi-image grid; the post title becomes the document title. |
strict | boolean | No | Default false: a platform that cannot take this content (for example Instagram with no media) is skipped and reported in platforms_skipped. Set true to get a 422 instead. Recommended for API clients. |
Response
Section titled “Response”{ "message": "Schedule and post created successfully", "post_id": 48213, "platforms": ["x", "linkedin"], "platforms_status": [ { "platform": "x", "status": "scheduled", "normalized_status": "publishing", "platform_post_id": null, "post_url": null, "published_at": null, "error": null, "error_code": null, "scheduled_time": "2026-09-20 08:00:00 -07:00" }, { "platform": "linkedin", "status": "scheduled", "normalized_status": "publishing", "platform_post_id": null, "post_url": null, "published_at": null, "error": null, "error_code": null, "warning": null, "platform_options": { "linkedin_options": { "visibility": "connections" } }, "scheduled_time": "2026-09-20 08:00:00 -07:00" } ], "platforms_skipped": [], "status_url": "/schedule/post/48213", "idempotent_replay": false, "post": { "id": 48213, "status": "scheduled", "description": "..." }}| Field | Description |
|---|---|
post_id | Use it with the status, update and delete endpoints. |
platforms | The platforms you requested. |
platforms_status | One entry per platform that will be published. normalized_status, platform_post_id, post_url and published_at fill in as each platform completes. warning carries a non-fatal problem (for example thumbnail_failed: ... on YouTube while the video is live). platform_options echoes the options object you sent. |
platforms_skipped | Platforms that cannot take this content, with a reason. Empty when strict is true (they return 422 instead). |
status_url | Poll this (under /v1) for the final outcome. |
idempotent_replay | true when an Idempotency-Key matched an earlier post and that post was returned. |
Validation errors
Section titled “Validation errors”Anything a platform would reject is refused before queueing with 422:
{ "detail": { "error": "YouTube requires a title", "error_code": "platform_validation_failed", "platform": "youtube", "field": "title", "errors": [ { "platform": "youtube", "field": "title", "error": "YouTube requires a title" } ] }}Checked per platform: caption over max_text, title over max_title, missing YouTube title, more media items than max_media_items (more than 10 on Instagram, 4 images on X, 9 images on LinkedIn, more than one elsewhere), images on a video-only platform (Facebook, YouTube), a document on a platform other than LinkedIn, mixing a document with other media on LinkedIn, more than 30 Instagram hashtags, instagram_music without a video item, a YouTube thumbnail_url that is not https or has a non-image extension (anything but .jpg/.jpeg/.png; extension-less URLs are checked by content type at upload), invalid option values (youtube_options.visibility, youtube_options.tags, youtube_options.category_id, linkedin_options.visibility, linkedin_options.alt_text on non-image media), tweet segments over 280 characters, unknown platform. The field is dotted for option objects, for example youtube_options.visibility. With strict: true, “no media on a media-only platform” is also a 422.
Check the outcome
Section titled “Check the outcome”GET /v1/schedule/post/{post_id}Returns the post with platforms_status, the same per-platform block as the create response, now filled in.
normalized_status | Meaning |
|---|---|
accepted | Stored, not started. |
publishing | Scheduled, or the worker is uploading to the platform. |
published | Live on the platform. platform_post_id and post_url are set. |
failed | The platform rejected it. error (and error_code, failed_step when known) explain why. |
{ "id": 48213, "status": "success", "platforms_status": [ { "platform": "youtube", "normalized_status": "published", "platform_post_id": "dQw4w9WgXcQ", "post_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "published_at": "2026-09-20T15:00:12Z", "error": null }, { "platform": "instagram", "normalized_status": "failed", "platform_post_id": null, "post_url": null, "error": "Media download failed: 403 Forbidden", "error_code": "media_fetch_failed", "failed_step": "download" } ]}post_url is the public link to the live post: a YouTube watch URL, an Instagram permalink, a Facebook Page video URL, a TikTok video URL, an X status URL, or a LinkedIn post URL.
List, update and delete
Section titled “List, update and delete”GET /v1/schedule/posts?agent_id={agent_id}&start_date={ISO date}&end_date={ISO date}PATCH /v1/schedule/post/{post_id}DELETE /v1/schedule/post/{post_id}GET /schedule/posts lists the content calendar for a window of at most 60 days. PATCH accepts the same content fields as create plus scheduled_time and schedule_type; it only works before the post goes out (a post already sent, or created with schedule_type: now, cannot be edited). DELETE removes a scheduled post.
Edit a post that is already live
Section titled “Edit a post that is already live”PATCH /v1/schedule/post/{post_id}/publishedFix a typo in a published description, retitle a video, change its tags or category — where the destination network supports it. This is the published-post counterpart to PATCH /schedule/post/{post_id}, which still refuses posts that have gone out.
Which fields each platform can change is served as editable_after_publish on every entry of GET /v1/schedule/platforms:
| Platform | Editable after publishing | Why |
|---|---|---|
| YouTube | title, description, tags, category_id | The Data API supports updating a live video’s snippet. |
| TikTok | — | No post-publication edit for this content. |
| — | No post-publication edit for this content. | |
| Facebook Page | — | No post-publication edit for this content. |
| X | — | Editing a sent tweet is a paid plan feature and is not exposed. |
| — | The Posts API can partially update commentary; GEN has not wired that adapter yet. |
The edit is sparse: only the fields in the body change. GEN reads the platform’s current metadata, merges your change onto it, writes it back, and returns what the platform held afterwards — so a title you did not send is never cleared, and sending the same request twice converges on the same result instead of stacking edits.
To clear a field rather than change it, send an explicit null ("description": null) or an empty list ("tags": []). title and category_id cannot be cleared: YouTube requires both, so a null there is a 422.
YouTube’s description limit is 5,000 bytes, not characters — emoji and CJK cost several bytes each, so a 2,000-character description of emoji is over the limit.
curl -X PATCH "https://api.gen.pro/v1/schedule/post/48213/published" \ -H "X-API-Key: $GEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"description": "Corrected sourcing figures in the pinned comment."}'{ "post_id": 48213, "results": [ { "platform": "youtube", "platform_post_id": "dQw4w9WgXcQ", "result": "updated", "updated_fields": ["description"], "metadata": { "title": "Factory price reveal", "description": "Corrected sourcing figures in the pinned comment.", "tags": ["sourcing", "beauty"], "category_id": 22 }, "quota_units": 50 } ]}One entry per platform the post went out on:
result | Meaning |
|---|---|
updated | The platform accepted the edit. metadata is read back from the platform after the write, not echoed from the request. |
unsupported_after_publish | This network has no post-publication edit for this content. Nothing was touched — this is a capability answer, not an authentication failure. |
reauthorization_required | GEN’s saved authorization for that account can no longer edit the post — the connection is missing, expired beyond refresh, or was granted without the write scope. The user reconnects it in the GEN app under Setup → Accounts. |
failed | The platform rejected the edit; message carries its reason. This covers the 403s that reconnecting would not fix — quotaExceeded, a video the connected channel does not own, a suspended channel, a policy restriction. |
A 422 means the request body itself is wrong: post_not_published (the post has not gone out — use PATCH /schedule/post/{post_id}), an empty body, or platform_validation_failed naming the field, either because the platform cannot change it after publishing or because it breaks the platform’s limits (the same limits a fresh publish is held to).
Upload media
Section titled “Upload media”Posts reference media by URL, which must be publicly fetchable at post time. A render URL from the Generations API works directly. To publish a local file, upload it first:
POST /v1/social/upload_assetSend multipart/form-data with file and agent_id. The file is also saved to the agent’s Assets.
{ "url": "https://cdn.gen.pro/....jpg", "cdn_url": "https://cdn.gen.pro/....jpg", "asset_id": "12345", "content_resource_id": 12345, "asset_registered": true}Use url as media_url or inside media_urls.
Examples
Section titled “Examples”YouTube video, now
Section titled “YouTube video, now”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: launch-video-2026-09-20" \ -d '{ "agent_id": "your-agent-id", "platform": ["youtube"], "schedule_type": "now", "media_type": "VIDEO", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "title": "Factory price reveal", "description": "What your $12 beauty sponge really costs.", "strict": true }'TikTok video, scheduled
Section titled “TikTok video, scheduled”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["tiktok"], "schedule_type": "specific_time", "scheduled_time": "2026-09-20T15:00:00Z", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "description": "The $2 taco at 2am #streetfood #tacotok", "tiktok_privacy_level": "PUBLIC_TO_EVERYONE", "tiktok_disable_comment": false, "tiktok_disable_duet": false, "tiktok_disable_stitch": false, "strict": true }'Instagram Reel
Section titled “Instagram Reel”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["instagram"], "schedule_type": "now", "media_type": "REELS", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "description": "Behind the scenes #reels", "strict": true }'Instagram carousel with auto-selected music
Section titled “Instagram carousel with auto-selected music”Two to ten items (media_url plus media_urls, images and videos mixed) post as one carousel. instagram_music lays a track under every video item before upload.
curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["instagram"], "schedule_type": "now", "media_url": "https://cdn.gen.pro/outputs/clip_1.mp4", "media_urls": ["https://cdn.gen.pro/outputs/still_2.jpg", "https://cdn.gen.pro/outputs/still_3.png"], "description": "Three looks, one drop #carousel", "instagram_music": { "mode": "auto" }, "strict": true }'YouTube Short with a custom thumbnail
Section titled “YouTube Short with a custom thumbnail”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["youtube"], "schedule_type": "now", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "thumbnail_url": "https://cdn.gen.pro/outputs/cover.jpg", "title": "Factory price reveal", "description": "What your $12 beauty sponge really costs.", "youtube_options": { "visibility": "public", "shorts": true, "tags": ["sourcing", "beauty"] }, "strict": true }'The published post_url is https://www.youtube.com/shorts/{id} for a Short and https://www.youtube.com/watch?v={id} otherwise. If the thumbnail could not be set (wrong type, over 2 MB, channel not verified for custom thumbnails), the platform status carries warning: "thumbnail_failed: ..." and the video is still live.
Facebook Page video
Section titled “Facebook Page video”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["facebook"], "schedule_type": "now", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "title": "Launch day", "description": "We are live.", "strict": true }'LinkedIn, text only
Section titled “LinkedIn, text only”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["linkedin"], "schedule_type": "now", "description": "Three things we learned shipping our publishing API this week...", "strict": true }'LinkedIn with an image
Section titled “LinkedIn with an image”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["linkedin"], "schedule_type": "now", "media_type": "IMAGE", "media_url": "https://cdn.gen.pro/outputs/chart.png", "title": "Q3 in one chart", "description": "Our Q3 numbers, in one chart.", "strict": true }'LinkedIn document (PDF carousel)
Section titled “LinkedIn document (PDF carousel)”A PDF, PPT, PPTX, DOC or DOCX in media_url posts as LinkedIn’s swipeable document. title is the document title (max 400; the file name when omitted).
curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["linkedin"], "schedule_type": "now", "media_url": "https://cdn.gen.pro/outputs/q3-deck.pdf", "title": "Q3 in ten slides", "description": "Our Q3 numbers, one slide at a time.", "linkedin_options": { "visibility": "connections" }, "strict": true }'LinkedIn carousel from images
Section titled “LinkedIn carousel from images”LinkedIn renders several images as a grid; its swipeable carousel is a document. carousel: true builds one PDF from the picked images and posts it as that document.
curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["linkedin"], "schedule_type": "now", "media_type": "IMAGE", "media_url": "https://cdn.gen.pro/outputs/slide-1.png", "media_urls": ["https://cdn.gen.pro/outputs/slide-2.png", "https://cdn.gen.pro/outputs/slide-3.png"], "title": "Q3 in three slides", "description": "Swipe through our Q3.", "linkedin_options": { "carousel": true }, "strict": true }'LinkedIn with several images and alt text (grid)
Section titled “LinkedIn with several images and alt text (grid)”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["linkedin"], "schedule_type": "now", "media_type": "IMAGE", "media_url": "https://cdn.gen.pro/outputs/chart-1.png", "media_urls": ["https://cdn.gen.pro/outputs/chart-2.png", "https://cdn.gen.pro/outputs/chart-3.png"], "description": "Q3 in three charts.", "linkedin_options": { "alt_text": ["Revenue by month", "Retention by cohort", "Pipeline by stage"] }, "strict": true }'X: text, images, thread, reply
Section titled “X: text, images, thread, reply”X supports a plain tweet, a tweet with up to 4 images, a tweet with a video, a thread, and a reply.
# Text-only tweetcurl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["x"], "schedule_type": "now", "description": "gm from the API" }'
# Up to 4 imagescurl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["x"], "schedule_type": "now", "description": "four pics", "media_type": "IMAGE", "media_urls": [ "https://cdn.example.com/1.jpg", "https://cdn.example.com/2.jpg", "https://cdn.example.com/3.jpg", "https://cdn.example.com/4.jpg" ] }'
# Threadcurl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["x"], "schedule_type": "now", "thread": [ { "text": "1/ Here is a thread about our launch." }, { "text": "2/ With a screenshot", "media_urls": ["https://cdn.example.com/shot.jpg"] }, { "text": "3/ Wrapping up. Thanks for reading!" } ] }'
# Replycurl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["x"], "schedule_type": "now", "description": "Great point. Here is our take.", "reply_to_tweet_id": "1780000000000000000" }'thread and reply_to_tweet_id apply to X only and are ignored for other platforms. If a thread fails partway, posting stops at the failed segment; the tweets already posted are recorded and the post is marked failed (not retried, to avoid duplicates).
One video to several platforms
Section titled “One video to several platforms”curl -X POST "https://api.gen.pro/v1/schedule/with-post" \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your-agent-id", "platform": ["tiktok", "instagram", "youtube", "x", "linkedin"], "schedule_type": "specific_time", "scheduled_time": "2026-09-20T15:00:00Z", "media_url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "title": "Factory price reveal", "description": "What your $12 beauty sponge really costs. #sourcing", "tiktok_privacy_level": "PUBLIC_TO_EVERYONE", "tiktok_disable_comment": false, "tiktok_disable_duet": false, "tiktok_disable_stitch": false }'The 280-character X limit and the 100-character YouTube title apply to the shared description and title, so keep them within the tightest platform in the list, or send separate requests per platform.
Upload, post, then read the link back
Section titled “Upload, post, then read the link back”KEY=your-api-key; AGENT=your-agent-id
# 1. Upload the fileURL=$(curl -s -X POST "https://api.gen.pro/v1/social/upload_asset" -H "X-API-Key: $KEY" \ -F "file=@image.jpg" -F "agent_id=$AGENT" | python3 -c "import sys,json;print(json.load(sys.stdin)['url'])")
# 2. Post it to XPOST_ID=$(curl -s -X POST "https://api.gen.pro/v1/schedule/with-post" -H "X-API-Key: $KEY" \ -H "Content-Type: application/json" \ -d "{\"agent_id\":\"$AGENT\",\"platform\":[\"x\"],\"schedule_type\":\"now\",\"media_type\":\"IMAGE\",\"media_urls\":[\"$URL\"],\"description\":\"from the API\",\"strict\":true}" \ | python3 -c "import sys,json;print(json.load(sys.stdin)['post_id'])")
# 3. Poll until published or failed, then print the linkcurl -s "https://api.gen.pro/v1/schedule/post/$POST_ID" -H "X-API-Key: $KEY" \ | python3 -c "import sys,json; [print(p['platform'], p['normalized_status'], p['post_url'] or p['error']) for p in json.load(sys.stdin)['platforms_status']]"Errors
Section titled “Errors”| Status | Error code | Description |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
403 | permission_denied | You do not have access to this agent. |
404 | not_found | Unknown post_id. |
422 | platform_validation_failed | A platform would reject this content. detail.platform, detail.field and detail.errors say which and why. |
422 | validation_error | Invalid or missing fields. |
422 | no_active_credit_purchase | The agent’s workspace has no active credit purchase. |
Legacy: publish through user_jobs
Section titled “Legacy: publish through user_jobs”POST /v1/user_jobs with user_job_type: publish_content still works for single-platform TikTok posts. New integrations should use POST /v1/schedule/with-post, which covers every platform, validates up front, and returns the live post_url.