When uploading videos to the YouTube service using the API, the API response indicated that the upload is successful. But on the website channel view the video is stuck at a 0% upload status (see screenshot). The percentage does not increase and the video never becomes viewable.
I am using the typescript/javascript api client library and the upload code looks as follows:
import fs from 'fs';
import { google, Auth } from 'googleapis';
async function uploadYouTubeVideoWithAuth(
auth: Auth.OAuth2Client,
videoLocalLocation: string,
title: string,
description: string
): Promise<string> {
google.options({ auth });
const youtube = google.youtube('v3');
const resp = await youtube.videos.insert({
part: ['id', 'snippet', 'status'],
requestBody: {
snippet: {
title,
description,
},
status: {
privacyStatus: 'private',
selfDeclaredMadeForKids: false,
},
},
media: {
body: fs.createReadStream(videoLocalLocation),
},
});
console.log(JSON.stringify(resp, null, 2));
const videoId = resp.data.id;
if (!videoId) {
throw new Error('failed to get videoId of uploaded video');
}
return videoId;
}
The JSON response from the request looks as follows:
{
"config": {
"url": "https://youtube.googleapis.com/upload/youtube/v3/videos?part=id&part=snippet&part=status&uploadType=multipart",
"method": "POST",
"apiVersion": "",
"userAgentDirectives": [
{
"product": "google-api-nodejs-client",
"version": "7.2.0",
"comment": "gzip"
}
],
"data": {
"_events": {},
"_readableState": {
"highWaterMark": 16384,
"buffer": [],
"bufferIndex": 0,
"length": 0,
"pipes": [],
"awaitDrainWriters": null
},
"_writableState": {
"highWaterMark": 16384,
"length": 0,
"corked": 0,
"writelen": 0,
"bufferedIndex": 0,
"pendingcb": 0
},
"allowHalfOpen": true,
"_eventsCount": 2
},
"headers": {
"x-goog-api-client": "gdcl/7.2.0 gl-node/20.18.3",
"content-type": "multipart/related; boundary=a34455bc-c02c-4c89-896a-2b1b530bf92b",
"Accept-Encoding": "gzip",
"User-Agent": "google-api-nodejs-client/7.2.0 (gzip)",
"Authorization": "Bearer <omit>"
},
"params": {
"part": [
"id",
"snippet",
"status"
],
"uploadType": "multipart"
},
"retry": true,
"body": {
"_events": {},
"_readableState": {
"highWaterMark": 16384,
"buffer": [],
"bufferIndex": 0,
"length": 0,
"pipes": [],
"awaitDrainWriters": null
},
"_writableState": {
"highWaterMark": 16384,
"length": 0,
"corked": 0,
"writelen": 0,
"bufferedIndex": 0,
"pendingcb": 0
},
"allowHalfOpen": true,
"_eventsCount": 2
},
"responseType": "unknown"
},
"data": {
"kind": "youtube#video",
"etag": "yWFiu0ADCdhaQtb1HoXEH_pDoeA",
"id": "YlDM_luRQLE",
"snippet": {
"publishedAt": "2025-04-10T15:53:11Z",
"channelId": "<omitted>",
"title": "<omitted>",
"description": "<omitted>",
"thumbnails": {
"default": {
"url": "https://i9.ytimg.com/vi/YlDM_luRQLE/default.jpg?sqp=CKjR378G&rs=AOn4CLCjq6jG8VlCvegcjsZPuGm7PgqTsw",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i9.ytimg.com/vi/YlDM_luRQLE/mqdefault.jpg?sqp=CKjR378G&rs=AOn4CLD564Jj03lC_HZyByhbW9fDSwCQPg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i9.ytimg.com/vi/YlDM_luRQLE/hqdefault.jpg?sqp=CKjR378G&rs=AOn4CLACHEqSkwjdlrUkujlTdJj7YNf9Vw",
"width": 480,
"height": 360
}
},
"channelTitle": "<omitted_channel_name>",
"categoryId": "22",
"liveBroadcastContent": "none",
"localized": {
"title": "<omitted_title>",
"description": "<omitted_description>"
}
},
"status": {
"uploadStatus": "uploaded",
"privacyStatus": "private",
"license": "youtube",
"embeddable": true,
"publicStatsViewable": true,
"selfDeclaredMadeForKids": false
}
},
"headers": {
"alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
"content-encoding": "gzip",
"content-type": "application/json; charset=UTF-8",
"date": "Thu, 10 Apr 2025 15:53:13 GMT",
"server": "UploadServer",
"transfer-encoding": "chunked",
"vary": "Origin, X-Origin, Referer",
"warning": "214 UploadServer gzipped",
"x-guploader-response-body-transformations": "gzipped",
"x-guploader-uploadid": "AKDAyIurvH8jzOBh3gfTTXIYX_GAf11AgQjdT1kByGNdX6kShi-yEsjUYPn0LO27dYbGXLdP"
},
"status": 200,
"statusText": "OK",
"request": {
"responseURL": "https://youtube.googleapis.com/upload/youtube/v3/videos?part=id&part=snippet&part=status&uploadType=multipart"
}
}
What I expected to happen: Once the response is recieved from the API the video should appear fully uploaded on website channel view, but it is stuck at 0% uploaded and is not playable.
It's not 100% reproducible, the first time I tried uploading a video using the above code, it worked. Everytime since, it has not.
Further details:
I am using a refresh token to create the auth object:
function authorizeWithOAuth(
refreshToken: string,
credentials: ClientCredentials
): Auth.OAuth2Client {
const clientSecret = credentials.client_secret;
const clientId = credentials.client_id;
const redirectUrl = credentials.redirect_uris[0];
const oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
oauth2Client.setCredentials({ refresh_token: refreshToken });
return oauth2Client;
}
Linked google issue: https://issuetracker.google.com/issues/409959470
I have resolved my issue, it was my mistake. The file that was being uploaded had not been correctly downloaded from the origin source first. So i guess the code was attempting to upload a file containing 0 bytes or something similar.