YouTube video publishing
Upload, publish, schedule, and track a YouTube video through the REST API.
Use a connected YouTube channel and a video in the same workspace. Each request supports one account and one video, and sends a youtube object instead of posts, video, or instagram. Videos may be public, unlisted, or private. A first comment and reposting are not supported by this workflow.
The API key chooses the workspace for every request. Retrieve the account ID from GET /api/v1/accounts and check capabilities.publishPosts or capabilities.schedulePosts. Both are true only while the connection is not flagged for reconnection and was granted the youtube.upload scope; reconnect the channel in Posteady when they are false.
Choose or upload a video
For a video already in your media library, list available videos and use a returned mediaId:
curl "https://www.posteady.com/api/v1/media?limit=20&offset=0" \
-H "Authorization: Bearer $POSTEADY_API_KEY"The response contains media, total, and hasMore. Each video includes its mediaId, fileName, mimeType, fileSize, url, and available duration and dimensions. Only supported active MP4 and QuickTime videos visible in the workspace library are listed.
For a new file, create an upload reservation. This example assumes video.mp4 is exactly 10,485,760 bytes; replace fileSize with your file's actual byte count:
curl -X POST https://www.posteady.com/api/v1/media/uploads \
-H "Authorization: Bearer $POSTEADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "video.mp4",
"contentType": "video/mp4",
"fileSize": 10485760
}'This returns mediaId, fileKey, uploadUrl, expiresInSeconds, and requiredHeaders. The shared library accepts MP4 (video/mp4) and QuickTime (video/quicktime) files up to 2 GiB, but a YouTube request accepts videos up to 500 MB and 12 hours long. Larger files are rejected with VALIDATION_ERROR when you publish.
Upload the file bytes to uploadUrl within 300 seconds, using the exact headers from requiredHeaders:
curl --request PUT "<uploadUrl>" \
--header "Content-Type: video/mp4" \
--header "Content-Length: 10485760" \
--upload-file video.mp4After the PUT succeeds, confirm with the returned fileKey within 24 hours of creating the upload:
curl -X POST https://www.posteady.com/api/v1/media/uploads/confirm \
-H "Authorization: Bearer $POSTEADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileKey": "<fileKey>"
}'Use mediaId from the confirmation response for publishing. Reserving an ID or uploading bytes alone does not make the video ready. Expired unconfirmed uploads are cleaned up and cannot be published.
Publish now
Generate a UUID once for this publishing operation and pass it as youtube.requestId. Keep it when retrying the same request.
curl -X POST https://www.posteady.com/api/v1/posts \
-H "Authorization: Bearer $POSTEADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountIds": ["<youtube-account-uuid>"],
"youtube": {
"requestId": "<request-uuid>",
"mediaId": "<confirmed-media-uuid>",
"title": "A look behind the scenes",
"description": "How we built our latest release.",
"privacyStatus": "public",
"madeForKids": false
}
}'title is required and allows 1–100 characters. description is optional and allows up to 5,000 characters. privacyStatus must be public, unlisted, or private. madeForKids defaults to false; set it to true when the video is made for kids under COPPA rules. Do not include posts, video, or instagram in the same request.
An accepted publishing job returns HTTP 202:
{
"results": [
{
"accountId": "<youtube-account-uuid>",
"provider": "youtube",
"username": "example",
"success": true,
"postIds": [],
"jobId": "<job-uuid>",
"status": "queued"
}
],
"summary": { "succeeded": 1, "failed": 0 }
}Here success: true and summary.succeeded mean the job was accepted. They do not confirm that YouTube published the video. Store jobId and check its status.
Track publishing
curl "https://www.posteady.com/api/v1/publish-jobs/<job-uuid>" \
-H "Authorization: Bearer $POSTEADY_API_KEY"The response includes jobId, scheduledPostId, platform, accountId, username, status, postId, permalink, and scheduledAt. Immediate jobs have scheduledAt: null. postId is the YouTube video ID and permalink is its watch URL; both remain null until available. A failure may include an error with code and message.
| Status | Meaning |
|---|---|
queued | Waiting for processing to start |
scheduled | Waiting for the requested scheduled time |
processing | Upload to YouTube is in progress |
published | Publication is confirmed and the video ID is available |
failed | A known failure prevented completion |
canceled | The job was canceled before submission |
unknown | The submission outcome could not be established |
Failure codes are ACCOUNT_UNAVAILABLE (reconnect the channel), MEDIA_UNAVAILABLE, PUBLISH_QUOTA_EXCEEDED (the channel's daily upload quota or a rate limit), PREPARATION_FAILED, and PUBLISH_FAILED. An unknown job carries PUBLISH_RESULT_UNKNOWN.
Retry an interrupted API request with the same requestId and unchanged input. Posteady returns the existing job. Reusing that key with different input, including a different account, video, title, description, privacy, or schedule, returns 409 REQUEST_CONFLICT.
For unknown, Posteady does not automatically upload another video. Check YouTube Studio before deciding whether to create a new request: a new key could duplicate a video that was already uploaded.
Schedule and cancel
Send the same accountIds and youtube fields to POST /api/v1/scheduled-posts, adding a future scheduledAt and optional IANA timezone:
{
"accountIds": ["<youtube-account-uuid>"],
"youtube": {
"requestId": "<new-request-uuid>",
"mediaId": "<confirmed-media-uuid>",
"title": "A look behind the scenes",
"privacyStatus": "unlisted"
},
"scheduledAt": "<future-time-with-offset>",
"timezone": "Asia/Seoul"
}An accepted schedule returns HTTP 202 with results[0].scheduledPostIds, jobId, and status: "scheduled". Track it using jobId. List YouTube schedules with:
curl "https://www.posteady.com/api/v1/scheduled-posts?provider=youtube" \
-H "Authorization: Bearer $POSTEADY_API_KEY"Cancel using a returned scheduledPostId:
curl -X DELETE "https://www.posteady.com/api/v1/scheduled-posts/<scheduled-post-uuid>?platform=youtube" \
-H "Authorization: Bearer $POSTEADY_API_KEY"Successful cancellation returns { "canceled": 1 }. Once the upload has started, cancellation returns 409 POST_NOT_CANCELLABLE. Cancellation does not delete a published video.
Read posts and metrics
Call GET /api/v1/posts?provider=youtube. Results come from data already synchronized to Posteady; the request does not refresh YouTube. Each video includes postId (the video ID), title, content (the description, up to 280 characters), mediaType (VIDEO or SHORTS), and metricsUpdatedAt. Metrics expose views, likes, and replies (comments) as numbers or null; shares, reposts, quotes, and bookmarks are always null. Treat null as unavailable, not zero.
Use MCP
The equivalent tools are list_media, create_media_upload, confirm_media_upload, publish_post, schedule_post, get_publish_status, list_scheduled_posts, cancel_scheduled_post, and list_posts. Pass the same youtube object to publish_post or schedule_post. Media tools use workspaceId, or your default workspace when omitted. Publishing and scheduling derive the workspace from the account; publishing status derives it from the job. An optional workspaceId must match that resource. The file bytes still require a PUT to the issued upload URL. See What you can do.