SchedulePost API
Publish, schedule, or queue videos to all your connected platforms from any script or automation.
API Keys
Sign in to generate and manage your API keys.
Authentication
Pass your API key in every request:
x-api-key: sp_your_api_key_here
Base URL
https://schedulepost.fulinlabs.com/api/v1
Endpoints
/uploadUpload a media file (video or image) as multipart/form-data or a raw binary body. A Content-Length header bounded by the 512 MB limit is required and is validated before the body is read. JSON presign requests are no longer accepted here — see /api/v2/upload.
Parameters
file(File)— Media file (multipart form field) or raw binary with x-filenameExample
# Multipart form upload curl -X POST -H "x-api-key: sp_xxx" \ -F "file=@/path/to/video.mp4" \ https://schedulepost.fulinlabs.com/api/v1/upload # Raw binary upload curl -X POST -H "x-api-key: sp_xxx" \ -H "x-filename: my-video.mp4" \ --data-binary @/path/to/video.mp4 \ https://schedulepost.fulinlabs.com/api/v1/upload
Response
{
"success": true,
"videoUrl": "https://pub-xxx.r2.dev/user-id/uuid.mp4",
"path": "user-id/uuid.mp4",
"message": "Video uploaded. Use the videoUrl in /publish, /schedule, or /queue."
}/uploadv1 JSON presign requests (Content-Type: application/json) are rejected with 426 Upgrade Required — the v1 contract had no way to bind an exact upload size. Migrate to /api/v2/upload.
Example
curl -X POST -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{"filename":"my-video.mp4","contentType":"video/mp4"}' \
https://schedulepost.fulinlabs.com/api/v1/upload
# -> 426 Upgrade RequiredResponse
{
"error": "UPLOAD_CONTRACT_UPGRADED",
"message": "JSON presign requests are no longer supported on /api/v1/upload because they cannot bind an exact upload size. Use /api/v2/upload with a bounded contentLength instead.",
"upgradeTo": "/api/v2/upload"
}/api/v2/uploadRequest a presigned R2 upload target for large media. Requires an exact contentLength (bytes), which is bound to the signed PUT so R2 rejects a body that doesn't match.
Parameters
contentLength(number, required)— Exact size of the file in bytes, bounded by the 512 MB limitfilename(string)— Original filename, used only to infer the storage key extensioncontentType(string)— MIME type, must be video/* or image/*Example
curl -X POST -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{"filename":"my-video.mp4","contentType":"video/mp4","contentLength":10485760}' \
https://schedulepost.fulinlabs.com/api/v2/upload
# PUT the file to the returned uploadUrl with matching Content-Type and
# Content-Length headers, then publish or schedule using the returned videoUrl.Response
{
"success": true,
"uploadUrl": "https://...r2.cloudflarestorage.com/...?X-Amz-Signature=...",
"videoUrl": "https://pub-xxx.r2.dev/user-id/uuid.mp4",
"path": "user-id/uuid.mp4",
"expiresIn": 900,
"message": "Upload the media with PUT and a matching Content-Length header, then use videoUrl in /publish, /schedule, or /queue."
}/connectionsList all connected platform accounts. Use the returned IDs in publish/schedule/queue requests.
Example
curl -H "x-api-key: sp_xxx" \ https://schedulepost.fulinlabs.com/api/v1/connections
Response
{
"connections": [
{
"id": "uuid-1",
"platform": "youtube",
"account_name": "My Channel",
"account_id": "UC..."
},
{
"id": "uuid-2",
"platform": "tiktok",
"account_name": "@myhandle",
"account_id": "123..."
}
]
}/publishPublish immediately with text, single media, or carousel media. `videoUrl` is still supported for backward compatibility. TikTok defaults to direct public posting.
Parameters
videoUrl(string)— Legacy single-video URL (backward compatibility)mediaUrls(string[])— Array of public media URLs. Type is inferred from file extension.mediaItems({url,mediaType}[])— Preferred media payload for precise control. mediaType: "IMAGE" or "VIDEO". Use 2+ items for carousels.caption(string)— Post text — you can also use "title", "text", or "description" as aliaseshashtags(string)— Hashtags string (e.g. "#shorts #viral")connectionIds(string[], required)— Array of connection IDs from /connectionsyoutubeTitle(string)— Optional YouTube-only title override (max 100 chars)youtubeDescription(string)— Optional YouTube-only description overrideyoutubePrivacy(string)— YouTube privacy status: "private" (default), "unlisted", or "public"tiktokPostMode(string)— TikTok mode: "DIRECT_POST" (default) or "MEDIA_UPLOAD" (draft to TikTok inbox)tiktokPrivacy(string)— TikTok Direct Post privacy. Defaults to "PUBLIC_TO_EVERYONE". Must match one creator_info option.tiktokCommercialType(number)— TikTok commercial content type: 0=none, 1=your brand, 2=branded content, 3=bothtiktokDisableComment(boolean)— Disable comments on TikTok post (default: false)tiktokDisableDuet(boolean)— Disable duet on TikTok post (default: false)tiktokDisableStitch(boolean)— Disable stitch on TikTok post (default: false)Example
curl -X POST -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{
"caption": "Check out the new feature demo",
"mediaItems": [
{ "url": "https://example.com/video-1.mp4", "mediaType": "VIDEO" }
],
"hashtags": "#shorts #viral",
"connectionIds": ["uuid-1", "uuid-2"],
"youtubeTitle": "Quarterly feature demo",
"youtubeDescription": "Long-form YouTube description for the same upload.",
"youtubePrivacy": "private",
"tiktokPostMode": "DIRECT_POST",
"tiktokPrivacy": "PUBLIC_TO_EVERYONE"
}' \
https://schedulepost.fulinlabs.com/api/v1/publishResponse
{
"success": true,
"allPublished": true,
"postId": "post-uuid",
"platforms": [
{
"connectionId": "uuid-1",
"platform": "youtube",
"success": true,
"platformPostId": "yt-abc"
},
{
"connectionId": "uuid-2",
"platform": "tiktok",
"success": true,
"platformPostId": "tt-123"
}
]
}/scheduleSchedule text, single media, or carousel media for a specific date/time. `videoUrl` remains supported.
Parameters
videoUrl(string)— Legacy single-video URLmediaUrls(string[])— Array of public media URLsmediaItems({url,mediaType}[])— Preferred media payload. mediaType: "IMAGE" | "VIDEO"caption(string)— Post text — you can also use "title", "text", or "description" as aliaseshashtags(string)— Hashtags stringconnectionIds(string[], required)— Array of connection IDsscheduledFor(string, required)— ISO 8601 datetime (e.g. 2026-03-20T15:00:00Z)youtubeTitle(string)— Optional YouTube-only title overrideyoutubeDescription(string)— Optional YouTube-only description overrideyoutubePrivacy(string)— YouTube privacy status: "private" (default), "unlisted", or "public"tiktokPostMode(string)— TikTok mode: "DIRECT_POST" (default) or "MEDIA_UPLOAD" (draft to TikTok inbox)tiktokPrivacy(string)— TikTok Direct Post privacy. Defaults to "PUBLIC_TO_EVERYONE". Must match one creator_info option.tiktokCommercialType(number)— TikTok commercial content type: 0=none, 1=your brand, 2=branded content, 3=bothtiktokDisableComment(boolean)— Disable comments on TikTok post (default: false)tiktokDisableDuet(boolean)— Disable duet on TikTok post (default: false)tiktokDisableStitch(boolean)— Disable stitch on TikTok post (default: false)Example
curl -X POST -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{
"mediaItems": [
{ "url": "https://example.com/image-a.jpg", "mediaType": "IMAGE" },
{ "url": "https://example.com/image-b.jpg", "mediaType": "IMAGE" }
],
"caption": "Dropping this tomorrow!",
"connectionIds": ["uuid-1"],
"scheduledFor": "2026-03-20T15:00:00Z",
"youtubePrivacy": "private",
"tiktokPostMode": "DIRECT_POST",
"tiktokPrivacy": "PUBLIC_TO_EVERYONE"
}' \
https://schedulepost.fulinlabs.com/api/v1/scheduleResponse
{
"success": true,
"postId": "post-uuid",
"scheduledFor": "2026-03-20T15:00:00.000Z",
"platforms": ["youtube"],
"connectionIds": ["uuid-1"]
}/queueSmart queue — automatically picks the next available slot for text, single media, or carousels. No need to specify a date.
Parameters
videoUrl(string)— Legacy single-video URLmediaUrls(string[])— Array of public media URLsmediaItems({url,mediaType}[])— Preferred media payload. mediaType: "IMAGE" | "VIDEO"caption(string)— Post text — you can also use "title", "text", or "description" as aliaseshashtags(string)— Hashtags stringconnectionIds(string[], required)— Array of connection IDssettings(object)— Override smart schedule settings (postsPerDay: 1|2|3, allowedDays)youtubeTitle(string)— Optional YouTube-only title overrideyoutubeDescription(string)— Optional YouTube-only description overrideyoutubePrivacy(string)— YouTube privacy status: "private" (default), "unlisted", or "public"tiktokPostMode(string)— TikTok mode: "DIRECT_POST" (default) or "MEDIA_UPLOAD" (draft to TikTok inbox)tiktokPrivacy(string)— TikTok Direct Post privacy. Defaults to "PUBLIC_TO_EVERYONE". Must match one creator_info option.tiktokCommercialType(number)— TikTok commercial content type: 0=none, 1=your brand, 2=branded content, 3=bothtiktokDisableComment(boolean)— Disable comments on TikTok post (default: false)tiktokDisableDuet(boolean)— Disable duet on TikTok post (default: false)tiktokDisableStitch(boolean)— Disable stitch on TikTok post (default: false)Example
curl -X POST -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{
"mediaItems": [
{ "url": "https://example.com/photo-1.jpg", "mediaType": "IMAGE" },
{ "url": "https://example.com/photo-2.jpg", "mediaType": "IMAGE" }
],
"caption": "Auto-queued!",
"connectionIds": ["uuid-1", "uuid-2"],
"youtubePrivacy": "private",
"tiktokPostMode": "DIRECT_POST",
"tiktokPrivacy": "PUBLIC_TO_EVERYONE",
"settings": {
"postsPerDay": 2,
"allowedDays": [1,2,3,4,5]
}
}' \
https://schedulepost.fulinlabs.com/api/v1/queueResponse
{
"success": true,
"postId": "post-uuid",
"scheduledFor": "2026-03-17T14:32:00.000Z",
"message": "Post added to queue with smart scheduling.",
"platforms": ["youtube", "tiktok"],
"connectionIds": ["uuid-1", "uuid-2"],
"scheduleSettings": {
"postsPerDay": 2,
"allowedDays": [1,2,3,4,5]
}
}/postsList your posts. Optionally filter by status.
Parameters
status(string)— Query param: "draft", "scheduled", "publishing", "published", "failed", or "partial"Example
curl -H "x-api-key: sp_xxx" \ "https://schedulepost.fulinlabs.com/api/v1/posts?status=scheduled"
Response
{
"posts": [
{
"id": "post-uuid",
"video_url": "https://...",
"media_items": [{"url":"https://...","mediaType":"IMAGE"}],
"caption": "...",
"youtube_title": "My YouTube title",
"youtube_description": "My YouTube description",
"youtube_privacy": "private",
"status": "scheduled",
"scheduled_for": "2026-03-20T15:00:00Z",
"platforms": ["youtube"],
"connection_ids": ["uuid-1"],
"created_at": "2026-03-31T22:00:00.000Z"
}
]
}/postsCancel a scheduled post (only works for posts with status 'scheduled').
Parameters
id(string, required)— Post ID to cancelExample
curl -X DELETE -H "x-api-key: sp_xxx" \
-H "Content-Type: application/json" \
-d '{"id": "post-uuid"}' \
https://schedulepost.fulinlabs.com/api/v1/postsResponse
{ "success": true }/groupsList your account groups — named bundles of platform connection IDs you can target when scheduling or queueing posts. Requires the posts:read scope.
Example
curl -H "x-api-key: sp_xxx" \\ https://schedulepost.fulinlabs.com/api/v1/groups
Response
{
"groups": [
{
"id": "group-uuid",
"name": "Main Channel",
"connection_ids": ["uuid-1", "uuid-2"]
}
]
}TypeScript Example
const API_KEY = "sp_your_key";
const BASE = "https://schedulepost.fulinlabs.com/api/v1";
// 1. Request a direct upload target (recommended for large videos)
const fs = require("fs");
const upload = await fetch(`${BASE}/upload`, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ filename: "video.mp4", contentType: "video/mp4" }),
}).then(r => r.json());
await fetch(upload.uploadUrl, {
method: "PUT",
headers: { "Content-Type": "video/mp4" },
body: fs.readFileSync("./video.mp4"),
});
console.log(upload.videoUrl);
// → "https://pub-xxx.r2.dev/user-id/uuid.mp4"
// 2. List connections to find account IDs
const connections = await fetch(`${BASE}/connections`, {
headers: { "x-api-key": API_KEY },
}).then(r => r.json());
// 3. Publish the uploaded video with YouTube-specific metadata
const result = await fetch(`${BASE}/publish`, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
mediaItems: [{ url: upload.videoUrl, mediaType: "VIDEO" }],
caption: "Feature launch video",
connectionIds: [connections.connections[0].id],
youtubeTitle: "Feature launch video",
youtubeDescription: "Longer YouTube description for the same upload.",
youtubePrivacy: "private",
}),
}).then(r => r.json());
console.log(result);
// → { success: true, postId: "...", scheduledFor: "2026-03-17T14:32:00Z", ... }Error Codes
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 400 | Bad request (missing params, invalid connections, no slots) |
| 404 | Unknown endpoint |
| 405 | Method not allowed |
| 500 | Server error |