Skip to main content
POST
/
api
/
partner
/
v1
/
posts
import fetch from "node-fetch";

const url = "https://www.genviral.io/api/partner/v1/posts";
const payload = {
  caption: "Holiday drops start Monday!",
  media: {
    type: "video",
    url: "https://cdn.genviral.com/holidays.mp4",
  },
  music_url: "https://www.tiktok.com/@genviral/video/1234567890",
  accounts: [
    { id: "0f4f54d4-8cce-4fb7-8c7b-befbcb8af812" },
    { id: "6b0c8c9c-55ac-4fcb-85ec-70b5a8b0d089" },
  ],
  scheduled_at: "2025-02-01T15:00:00Z",
  external_id: "partner-run-884",
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: "Bearer <token>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(await response.json());
{
  "ok": true,
  "code": 201,
  "message": "Post scheduled",
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "status": "scheduled",
    "scheduled_at": "2025-02-01T15:00:00Z",
    "warnings": [
      {
        "field": "media",
        "message": "Video duration metadata is missing",
        "code": "VIDEO_DURATION_MISSING"
      }
    ]
  }
}
Schedule a post to TikTok, Instagram, YouTube, Pinterest, LinkedIn, or Facebook - or all of them at once. This is the core posting endpoint that OpenClaw agents and automation scripts use to distribute content across platforms. Posts are stored with source="partner_api" and appear in the dashboard queue immediately. Supports TikTok photo carousels (slideshows), video posts, draft uploads via MEDIA_UPLOAD mode, TikTok music via music_url, and multi-account targeting across all 6 platforms. Instagram’s official publishing API does not currently support programmatic music or sound selection for carousel posts or Reels.

Body Parameters

caption
string
required
Text caption applied to every platform.
Target-specific limit. Hosted Accounts targets cap captions at 500 characters. BYO caps follow the selected platform and media type: Facebook 63,206; Instagram 2,200; TikTok 2,200 for video captions / 4,000 for photo-post descriptions; LinkedIn 3,000; Pinterest 800; YouTube 5,000-byte descriptions.
media
object
required
Provide either a single video clip or a slideshow bundle.
music_url
string
Optional TikTok post URL (e.g., https://www.tiktok.com/@genviral/video/1234567890) used to resolve the track for TikTok-only requests. The API validates that the link points to TikTok and rejects requests that target any non-TikTok account. Instagram’s official publishing API does not support music/sound selection for carousels or Reels, so any request that includes Instagram accounts with music_url will be rejected.
accounts
array
required
Up to 10 account objects referencing IDs returned from /accounts.All IDs must belong to the authenticated key scope. Unknown or inactive accounts cause the request to fail.
tiktok
object
Optional TikTok publishing settings.
Supported only when every targeted account is a TikTok BYO account.
pinterest
object
Optional Pinterest publishing settings.
Supported only when at least one targeted account is a Pinterest account.
tiktok and pinterest settings are mutually exclusive in one request. Include only the platform-specific object that matches your targeted accounts. Other platform-specific objects (for example facebook, instagram, linkedin, or youtube settings) are rejected with 422 invalid_payload.
scheduled_at
string
ISO 8601 timestamp (e.g., 2025-02-01T15:00:00Z). If omitted or within 30 seconds of “now”, the post is treated as pending for immediate publish.
external_id
string
Optional but strongly recommended idempotency key / correlation ID for your system. Replaying the same request with the same external_id returns the original post instead of creating a duplicate. Reusing the same external_id for a materially different request returns 409.

Media Hosting

  • BYO Accounts: Media is automatically ingested into Genviral storage.
  • Hosted-Only: Provided URLs are validated for reachability but not re-hosted unless necessary.
  • Google Drive: Share links are auto-converted to direct download links.

Limits

  • Caption: enforced against the targeted accounts. Hosted Accounts targets stay at 500 characters. BYO caps follow platform limits: Facebook 63,206; Instagram 2,200; TikTok 2,200 for video captions and 4,000 for photo-post descriptions; LinkedIn 3,000; Pinterest 800; YouTube 5,000-byte descriptions.
  • Video: MP4/MOV/M4V/AVI, under 100MB. If duration metadata is present, we enforce 15–60 seconds; when duration is missing we proceed with a warning. ~9:16 aspect recommended.
  • Slideshow: 1–35 images, JPG/JPEG/PNG, each under 5MB. ~9:16 aspect recommended.
  • Music: TikTok-only. Instagram’s official API does not support music/sound selection for carousel posts or Reels, so requests with music_url are rejected when any Instagram account is selected.
  • TikTok settings: supported only when all selected accounts are TikTok BYO accounts.
  • tiktok.post_mode = MEDIA_UPLOAD: supported only for media.type = slideshow (photo posts).
  • tiktok.video_cover_timestamp_ms: supported for video DIRECT_POST requests.
  • Pinterest settings: supported only when at least one selected account is Pinterest.
  • pinterest.tags: up to 30 tags, each 1–100 characters (spaces allowed). Tags are appended to the final Pinterest description, which still must fit 800 characters.

Platform Matrix

PlatformGenviral common text maps toVerified platform limit / noteOther publish limits surfaced hereDedicated Partner API object
TikTokVideo post_info.title; photo post_info.descriptionVideo title max 2,200 UTF-16 runes. Photo title max 90 UTF-16 runes. Photo description max 4,000 UTF-16 runes.Photo posts support up to 35 images. MEDIA_UPLOAD is photo-only.tiktok
InstagramcaptionMeta’s current fetched publishing docs expose the content-publishing surface but did not yield a clean numeric caption cap in tooling. Genviral currently enforces 2,200.Carousel posts support up to 10 images/videos.None today
FacebookPage feed message, photo caption, video descriptionMeta’s Page Feed/Page Videos refs do not publish an explicit text max. Genviral currently keeps the existing 63,206-character BYO ceiling.Multiple photos are supported; mixed image+video payloads are not.None today
LinkedIncommentaryMicrosoft Learn’s UGC Post API docs cap commentary text at 3,000 characters; the newer Posts API refs do not restate that limit.Multi-image posts support 2–20 images.None today
PinterestdescriptionDescription max 800 characters.Title max 100, link max 2,048. Pinterest alt text is capped at 500 by the platform, but Partner API does not expose an alt_text field today.pinterest
YouTubesnippet.descriptionDescription max 5,000 bytes.Title max 100 characters; tags max 500 characters total. YouTube-specific tags are not exposed via Partner API today.None today

Canonical payload shape

Use this canonical structure when calling POST /api/partner/v1/posts:
{
  "caption": "3 looks for the long weekend",
  "media": {
    "type": "slideshow",
    "urls": [
      "https://cdn.example.com/slide-1.jpg",
      "https://cdn.example.com/slide-2.jpg"
    ]
  },
  "accounts": [{ "id": "3e30c8d1-0bc0-42d8-9a1a-bb63fe6c3c16" }],
  "tiktok": {
    "post_mode": "MEDIA_UPLOAD",
    "privacy_level": "SELF_ONLY"
  },
  "scheduled_at": "2026-02-20T15:00:00Z",
  "external_id": "carousel-campaign-12"
}
Shape notes:
  • caption: required, 1..63206 chars at schema level; the final allowed length is resolved from the targeted accounts after account lookup.
  • media: required object.
  • video media: { "type": "video", "url": "https://..." }.
  • slideshow media: { "type": "slideshow", "urls": ["https://...", "..."] }.
  • accounts: required array of { "id": "<uuid>" } objects.
  • scheduled_at: optional ISO 8601 string; omit for immediate queueing.
  • tiktok: optional object for TikTok-only settings.
  • pinterest: optional object for Pinterest-specific board/title/link/tag settings.
  • tiktok and pinterest cannot be sent together because their account constraints are mutually exclusive.

Response

Fresh creates return 201. Idempotent replays return 200. Successful responses include:
  • id - canonical Genviral post ID
  • status - either pending or scheduled, matching the scheduling logic above
  • scheduled_at - ISO timestamp stored on the post. Immediate requests are stamped with the server “publish ASAP” timestamp (not null).
  • duplicate - true when Genviral deduplicated an idempotent replay and returned the original post instead of creating a new one.
  • warnings - optional array describing non-blocking validation issues
If you omit external_id, Genviral still applies a short-window best-effort duplicate guard using the full request fingerprint, but only external_id provides a stable API idempotency contract.

Examples

Single video post (default)

Use a single video object when targeting TikTok, Instagram Reels, or YouTube Shorts. Add music_url only for TikTok-only requests. Instagram’s official publishing API does not support programmatic music/sound selection for Reels.
import fetch from "node-fetch";

const url = "https://www.genviral.io/api/partner/v1/posts";
const payload = {
  caption: "Holiday drops start Monday!",
  media: {
    type: "video",
    url: "https://cdn.genviral.com/holidays.mp4",
  },
  music_url: "https://www.tiktok.com/@genviral/video/1234567890",
  accounts: [
    { id: "0f4f54d4-8cce-4fb7-8c7b-befbcb8af812" },
    { id: "6b0c8c9c-55ac-4fcb-85ec-70b5a8b0d089" },
  ],
  scheduled_at: "2025-02-01T15:00:00Z",
  external_id: "partner-run-884",
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: "Bearer <token>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(await response.json());
curl --request POST \
  --url https://www.genviral.io/api/partner/v1/posts \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "caption": "Holiday drops start Monday!",
  "media": {
    "type": "video",
    "url": "https://cdn.genviral.com/holidays.mp4"
  },
  "music_url": "https://www.tiktok.com/@genviral/video/1234567890",
  "accounts": [
    { "id": "0f4f54d4-8cce-4fb7-8c7b-befbcb8af812" },
    { "id": "6b0c8c9c-55ac-4fcb-85ec-70b5a8b0d089" }
  ],
  "scheduled_at": "2025-02-01T15:00:00Z",
  "external_id": "partner-run-884"
}'

Slideshow post (photo carousel)

Provide up to 35 slideshow items to schedule a TikTok Photo Mode or Instagram carousel. Order is preserved as given in the media array.
import fetch from "node-fetch";

const url = "https://www.genviral.io/api/partner/v1/posts";
const payload = {
  caption: "3 looks for the long weekend ☀️",
  media: {
    type: "slideshow",
    urls: [
      "https://cdn.genviral.com/slides/look-01.jpg",
      "https://cdn.genviral.com/slides/look-02.jpg",
      "https://cdn.genviral.com/slides/look-03.jpg",
    ],
  },
  music_url: "https://www.tiktok.com/@genviral/video/9876543210",
  accounts: [{ id: "3e30c8d1-0bc0-42d8-9a1a-bb63fe6c3c16" }],
  scheduled_at: "2025-02-14T18:30:00Z",
  external_id: "carousel-campaign-12",
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: "Bearer <token>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(await response.json());
curl --request POST \
  --url https://www.genviral.io/api/partner/v1/posts \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "caption": "3 looks for the long weekend ☀️",
  "media": {
    "type": "slideshow",
    "urls": [
      "https://cdn.genviral.com/slides/look-01.jpg",
      "https://cdn.genviral.com/slides/look-02.jpg",
      "https://cdn.genviral.com/slides/look-03.jpg"
    ]
  },
  "music_url": "https://www.tiktok.com/@genviral/video/9876543210",
  "accounts": [
    { "id": "3e30c8d1-0bc0-42d8-9a1a-bb63fe6c3c16" }
  ],
  "scheduled_at": "2025-02-14T18:30:00Z",
  "external_id": "carousel-campaign-12"
}'
{
  "ok": true,
  "code": 201,
  "message": "Post scheduled",
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "status": "scheduled",
    "scheduled_at": "2025-02-01T15:00:00Z",
    "warnings": [
      {
        "field": "media",
        "message": "Video duration metadata is missing",
        "code": "VIDEO_DURATION_MISSING"
      }
    ]
  }
}
Responses may include a warnings array when media metadata (size/duration/aspect) is missing. These are informational so you can decide whether to re-upload media before Hosted Accounts enforce limits.

Error Responses

  • 400 invalid_json - body is not valid JSON
  • 422 invalid_payload - schema validation failed (caption/media/accounts)
  • 400 unknown_accounts - at least one account.id is outside the authenticated key scope
  • 400 validation_failed - caption/media/music rules failed (TikTok-only music, bad aspect ratio, etc.)
  • 400 missing_accounts - resolved account array is empty after validation
  • 400 invalid_music_url or 400 media_unreachable - media or music URLs failed reachability checks
  • 401 - authentication failed (missing/invalid/revoked token)
  • 402 subscription_required - active Creator/Professional/Business plan required
  • 403 tier_not_allowed - Scheduler tier cannot use Partner API
  • 500 create_failed - upstream scheduler error (retry after inspecting logs)