Publishing API v1
Publish a finished video from your own app to one of your connected YouTube channels. We download the video, upload it to that channel and give you the YouTube link. The same information as JSON: /api/docs.
Keys
- Create a key on the Channels page: the channel's API keys button, then Create key. It is shown once; we store only a hash.
- A key can publish only to the channel it was created for.
- Send it as
Authorization: Bearer sxs_…orX-API-Key: sxs_…. - A missing, unknown or revoked key, or a disconnected channel, returns 401
invalid_api_key. - Revoking a key cancels the videos it sent that haven't started posting (their status becomes
failed).
1. Check a key
GET /api/v1/account
200 {"ok": true, "account": {"id": 5, "handle": "@mychannel", "label": "My Channel",
"platform": "youtube", "status": "verified"}}
2. Publish a video
Send JSON with video_url (recommended), or multipart/form-data (under 100 MB in total) with the file as field video and an optional image as field thumbnail.
| Field | Rules |
|---|---|
| video_url | Direct http(s) link on port 80 or 443 to an MP4, MOV, M4V or WebM file, up to 300 MB. Required unless you upload the file as field video. |
| title | Required, 1 to 100 characters. < and > are removed. |
| description | Optional, alias caption. At most 5,000 characters and 5,000 UTF-8 bytes; < and > become ‹ and ›. |
| thumbnail_url | Optional JPEG, PNG, WebP, GIF or BMP up to 10 MB, resized to fit 1280×1280 under 2 MB. Custom thumbnails need a phone-verified channel. If it can't be used, the video still publishes and the job lists a warning. |
| scheduled_at | Optional ISO-8601 time; no offset means UTC. Missing or in the past means now; at most 180 days ahead. |
| external_id | Optional id from your system, up to 128 characters. Sending it again for the same channel returns the existing job (HTTP 200, "duplicate": true), even if that job failed, so use a new id to try again. |
| privacy | Optional: public (default), unlisted or private. |
| made_for_kids | Optional boolean, default false. |
| tags | Optional list of strings, 500 characters in total. |
| category_id | Optional YouTube category number, default 22. |
curl -X POST https://studio.startx6.com/api/v1/publish \
-H "Authorization: Bearer sxs_YOUR_KEY" -H "Content-Type: application/json" \
-d '{"video_url": "https://example.com/videos/launch.mp4", "title": "Launch day",
"description": "We shipped it.", "thumbnail_url": "https://example.com/videos/launch.jpg",
"external_id": "launch-001"}'
202 {"ok": true, "job": {"id": 42, "status": "downloading", "platform": "youtube", "title": "Launch day",
"external_id": "launch-001", ...}, "status_url": "https://studio.startx6.com/api/v1/publish/42"}
Returns 202 for a link (status downloading), 201 for an uploaded file (status queued) and 200 for a duplicate external_id.
3. Job status
Poll every 10 to 30 seconds until the status is published or failed.
GET /api/v1/publish/42
200 {"ok": true, "job": {"id": 42, "status": "published", "platform": "youtube", "title": "Launch day",
"scheduled_at": "2026-11-07T11:07:16+00:00", "created_at": "2026-11-07T11:07:16+00:00",
"finished_at": "2026-11-07T11:07:28+00:00", "post_url": "https://www.youtube.com/watch?v=XXXXXXXXXXX",
"post_id": "XXXXXXXXXXX", "error": "", "external_id": "launch-001", "warnings": []}}
- Statuses:
downloading→queued→running→publishedorfailed. A field not filled yet is an empty string. - When YouTube's per-channel upload limit is reached, the job fails with an
errorcontaining "upload limit". Send it again later with a newexternal_id. - Download or file problems found after the 202 show up as status
failedwitherror.
Errors
Always {"ok": false, "error": "snake_case_code", "message": "Human-readable explanation"}.
| Code | When |
|---|---|
| 400 | Validation: missing_title, missing_video_url, invalid_url, invalid_scheduled_at, invalid_description, invalid_video, invalid_json |
| 401 | invalid_api_key |
| 404 | not_found |
| 405 | method_not_allowed |
| 413 | too_large |
| 429 | too_many_pending (25 waiting per channel) or rate_limited |
| 500 | internal_error; retry later |
Limits
25 waiting jobs per channel, 300 MB per video from a link, 100 MB per multipart upload (use video_url for bigger files), 10 MB JSON body, 600-second download timeout, 3 redirects followed. Private and local network addresses are refused. Video files are deleted shortly after the job finishes.