Reelspan uploads to YouTube with the YouTube Data API. You send the video once with a title; Reelspan uploads it to the channel with your metadata, sets the thumbnail and playlists, and returns the video's link. A vertical video up to 3 minutes gets a youtube.com/shorts/… link, anything else a watch?v= link.
What you can post
| Post | How | Notes |
|---|---|---|
| Short | POST /v1/posts/video |
A vertical video up to 3 minutes |
| Long-form video | POST /v1/posts/video |
Any other video |
YouTube takes videos only: no photos, text or Community posts through the API. Reelspan handles for you:
- Resumable upload of the file to YouTube, so large videos go up in one session.
- Clean metadata. YouTube refuses
<and>in descriptions; Reelspan removes them. Titles over 100 characters and descriptions over 5,000 are cut. - Extras that never fail the post. The thumbnail and playlists are added after the upload. If YouTube refuses one, the video stays up and the result says what didn't work.
Connect the account
YouTube connects with a Google sign-in: in the dashboard go to Accounts → YouTube, or send a white-label connect link. Sign in with the Google account that owns or manages the channel. The account must already have a YouTube channel; create one at youtube.com first if it doesn't.
Example
A public Short with tags, in a playlist, with a custom thumbnail:
curl -X POST https://api.reelspan.com/v1/posts/video \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-F "user=my-brand" \
-F "platform[]=youtube" \
-F "youtube_title=How we ship a release in 60 seconds" \
-F "youtube_description=Our release checklist, start to finish." \
-F "tags[]=devops" -F "tags[]=release" \
-F "privacyStatus=public" \
-F "categoryId=28" \
-F "youtube_playlist_id=PLxxxxxxxxxxxxxxxx" \
-F "[email protected]" \
-F "[email protected]"import { Reelspan } from 'reelspan';
import { readFile } from 'node:fs/promises';
const reelspan = new Reelspan();
const post = await reelspan.posts.video({
user: 'my-brand',
platforms: ['youtube'],
title: 'How we ship a release in 60 seconds',
description: 'Our release checklist, start to finish.',
video: { data: await readFile('launch.mp4'), name: 'launch.mp4', type: 'video/mp4' },
thumbnail: { data: await readFile('thumbnail.jpg'), name: 'thumbnail.jpg', type: 'image/jpeg' },
extra: {
tags: ['devops', 'release'],
privacyStatus: 'public',
categoryId: '28',
youtube_playlist_id: 'PLxxxxxxxxxxxxxxxx',
},
});from reelspan import Reelspan
reelspan = Reelspan()
post = reelspan.posts.video(
"my-brand", ["youtube"], "launch.mp4", "How we ship a release in 60 seconds",
description="Our release checklist, start to finish.",
thumbnail="thumbnail.jpg",
extra={
"tags": ["devops", "release"],
"privacyStatus": "public",
"categoryId": "28",
"youtube_playlist_id": "PLxxxxxxxxxxxxxxxx",
},
)Fields
YouTube needs a title: youtube_title or title. A request with YouTube and neither is refused with 400 before anything is uploaded.
| Field | Type | Values | What it does |
|---|---|---|---|
youtube_title |
string | up to 100 characters | The video's title. Falls back to title. |
youtube_description |
string | up to 5,000 characters | The description. Falls back to description, then title. |
tags[] |
list | repeated field | Tags for the video. youtube_tags[] works too; both lists are combined. |
privacyStatus |
string | public (default), unlisted, private |
Who can watch it. youtube_privacy_status works too. |
youtube_publish_at |
string | ISO 8601 date and time | Uploads the video as private and lets YouTube make it public at that time. |
categoryId |
string | a YouTube category id, default 22 (People & Blogs) |
The video's category. |
defaultLanguage |
string | language code, e.g. en |
The language of the title and description. |
defaultAudioLanguage |
string | language code | The language spoken in the video. |
selfDeclaredMadeForKids |
boolean | default false |
Marks the video as made for children. |
containsSyntheticMedia |
boolean | Discloses realistic altered or AI-made content. is_ai_generated works too. Left unset unless you send it. |
|
embeddable |
boolean | default true |
Whether other sites may embed the video. |
license |
string | youtube (default), creativeCommon |
The video's license. |
publicStatsViewable |
boolean | default true |
Whether the like and view counts are public. |
recordingDate |
string | ISO 8601 date | When the video was recorded. |
youtube_notify_subscribers |
boolean | default true |
Whether subscribers are notified. |
youtube_playlist_id |
string | one id, or several separated by commas | Adds the video to these playlists. |
hasPaidProductPlacement |
boolean | Discloses paid product placement in the video. Left unset unless you send it. | |
thumbnail |
file | JPG or PNG | A custom thumbnail. Or send thumbnail_url (a public URL) or thumbnail_media_id (from Upload media). |
youtube_first_comment |
string | up to 10,000 characters | A comment posted under the video. Falls back to first_comment. |
Limits
- Title: 100 characters. Description: 5,000 characters.
- Shorts: vertical (9:16) and up to 3 minutes. Longer or horizontal videos are regular videos.
- Thumbnail: JPG or PNG, 16:9, up to 2 MB. Custom thumbnails need a verified channel, and YouTube doesn't show them on Shorts.
- Video size: your plan's upload limit (see Plans and limits).
Good to know
- Daily limits. YouTube caps uploads twice: a daily API quota for the app, and a daily upload limit per channel. Either one fails the post with a code that says which (
youtube_quotaExceeded,youtube_uploadLimitExceeded); both reset the next day. - Scheduled release vs scheduled post.
youtube_publish_atuploads now and YouTube releases the video later. Reelspan'sscheduled_datewaits and uploads at that time. Use one or the other. - Result extras:
video_id,privacy_status, andplaylistswith one entry per playlist (success,playlist_id, and anerrorwhen it failed). - Thumbnail warnings. If YouTube refuses the thumbnail, the video stays up with a warning.
- First comment: Reelspan posts it as a top-level comment. If YouTube refuses it, the video stays up and the result carries
first_comment: "failed"and a warning. - Analytics: channel subscribers, views and video count; per video, views, likes, comments and favorites.
After posting
- Change the thumbnail of a published video with YouTube thumbnail, by its video id or the
request_idthat published it. - Videos can't be edited or deleted through Reelspan: that needs a wider YouTube permission than the upload access Reelspan asks for. Change or remove them in YouTube Studio.
Errors
error_code |
What happened | What to do |
|---|---|---|
youtube_quotaExceeded |
The daily YouTube API quota ran out | Post again tomorrow. |
youtube_uploadLimitExceeded |
The channel reached YouTube's daily upload limit | Post again tomorrow. |
youtube_youtubeSignupRequired |
The Google account has no YouTube channel | Create the channel, then reconnect. |
youtube_forbidden |
YouTube refused the upload for this channel | Read error; check the channel's standing in YouTube Studio. |
youtube_rejected |
YouTube rejected the video after upload (for example a duplicate or a copyright claim) | Read error for YouTube's reason. |
youtube_… (other) |
YouTube refused the request; the code carries YouTube's reason | Read error for YouTube's message. |
youtube_error |
YouTube failed without a reason | Reelspan retries server errors; otherwise post again. |
reauth_required |
The Google sign-in expired or was revoked | Reconnect YouTube. |