title: PHP + Youtube API - how to allow user to upload videos to my own channel?
body: <p>I am trying to build a web app that allows users to upload videos to my channel. </p>
  
  <p>So I think I must go for <a href="https://developers.google.com/api-client-library/php/auth/service-accounts" rel="nofollow">OAuth 2.0 for Server to Server Applications</a>, just as it says it there:</p>
  
  <blockquote>
    <p>Typically, an application uses a <strong>service account</strong> when the
    application uses Google APIs to work with its <strong>own data</strong> rather than
    a <strong>user's data</strong>.</p>
  </blockquote>
  
  <p>So I have followed the steps to <a href="https://developers.google.com/api-client-library/php/auth/service-accounts#creatinganaccount" rel="nofollow">create a service account</a>. But after completing the steps and have the key and id generated, I got an error below:</p>
  
  <blockquote>
    <p>An client error occurred: Failed to start the resumable upload (HTTP
    401: youtube.header, Unauthorized)</p>
  </blockquote>
  
  <p>This is my entire code:</p>
  
  <pre><code>// Load 'Google/Client.php' and 'Google/Service/YouTube.php' with composer.
  require_once __DIR__ . '/vendor/autoload.php';
  session_start();
  
  $client_email = 'xxx@developer.gserviceaccount.com';
  $private_key = file_get_contents('xxx-74c6a50933e3.p12');
  $scopes = array(
    'https://www.googleapis.com/auth/youtube.upload',
    'https://www.googleapis.com/auth/youtube',
    'https://www.googleapis.com/auth/youtubepartner'
  );
  
  $credentials = new Google_Auth_AssertionCredentials(
      $client_email,
      $scopes,
      $private_key
  );
  
  $client = new Google_Client();
  $client-&gt;setAssertionCredentials($credentials);
  if ($client-&gt;getAuth()-&gt;isAccessTokenExpired()) {
       $client-&gt;getAuth()-&gt;refreshTokenWithAssertion();
  }
  
  $htmlBody = '';
  
  // Define an object that will be used to make all API requests.
  $youtube = new Google_Service_YouTube($client);
  
  $htmlBody = '';
  
  // Check to ensure that the access token was successfully acquired.
  if ($client-&gt;getAccessToken()) {
    try{
      // REPLACE this value with the path to the file you are uploading.
      $videoPath = "/home/xxx/Desktop/Vids/small.mp4";
  
      // Create a snippet with title, description, tags and category ID
      // Create an asset resource and set its snippet metadata and type.
      // This example sets the video's title, description, keyword tags, and
      // video category.
      $snippet = new Google_Service_YouTube_VideoSnippet();
      $snippet-&gt;setTitle("Test title");
      $snippet-&gt;setDescription("Test description");
      $snippet-&gt;setTags(array("tag1", "tag2"));
  
      // Numeric video category. See
      // https://developers.google.com/youtube/v3/docs/videoCategories/list
      $snippet-&gt;setCategoryId("22");
  
      // Set the video's status to "public". Valid statuses are "public",
      // "private" and "unlisted".
      $status = new Google_Service_YouTube_VideoStatus();
      $status-&gt;privacyStatus = "public";
  
      // Associate the snippet and status objects with a new video resource.
      $video = new Google_Service_YouTube_Video();
      $video-&gt;setSnippet($snippet);
      $video-&gt;setStatus($status);
  
      // Specify the size of each chunk of data, in bytes. Set a higher value for
      // reliable connection as fewer chunks lead to faster uploads. Set a lower
      // value for better recovery on less reliable connections.
      $chunkSizeBytes = 1 * 1024 * 1024;
  
      // Setting the defer flag to true tells the client to return a request which can be called
      // with -&gt;execute(); instead of making the API call immediately.
      $client-&gt;setDefer(true);
  
      // Create a request for the API's videos.insert method to create and upload the video.
      $insertRequest = $youtube-&gt;videos-&gt;insert("status,snippet", $video);
  
      // Create a MediaFileUpload object for resumable uploads.
      $media = new Google_Http_MediaFileUpload(
          $client,
          $insertRequest,
          'video/*',
          null,
          true,
          $chunkSizeBytes
      );
      $media-&gt;setFileSize(filesize($videoPath));
  
  
      // Read the media file and upload it chunk by chunk.
      $status = false;
      $handle = fopen($videoPath, "rb");
      while (!$status &amp;&amp; !feof($handle)) {
        $chunk = fread($handle, $chunkSizeBytes);
        $status = $media-&gt;nextChunk($chunk);
      }
  
      fclose($handle);
  
      // If you want to make other calls after the file upload, set setDefer back to false
      $client-&gt;setDefer(false);
  
  
      $htmlBody .= "&lt;h3&gt;Video Uploaded&lt;/h3&gt;&lt;ul&gt;";
      $htmlBody .= sprintf('&lt;li&gt;%s (%s)&lt;/li&gt;',
          $status['snippet']['title'],
          $status['id']);
  
      $htmlBody .= '&lt;/ul&gt;';
  
    } catch (Google_Service_Exception $e) {
      $htmlBody .= sprintf('&lt;p&gt;A service error occurred: &lt;code&gt;%s&lt;/code&gt;&lt;/p&gt;',
          htmlspecialchars($e-&gt;getMessage()));
    } catch (Google_Exception $e) {
      $htmlBody .= sprintf('&lt;p&gt;An client error occurred: &lt;code&gt;%s&lt;/code&gt;&lt;/p&gt;',
          htmlspecialchars($e-&gt;getMessage()));
    }
  
    $_SESSION['token'] = $client-&gt;getAccessToken();
  } else {
    // If the user hasn't authorized the app, initiate the OAuth flow
    $state = mt_rand();
    $client-&gt;setState($state);
    $_SESSION['state'] = $state;
  
    $authUrl = $client-&gt;createAuthUrl();
    $htmlBody = &lt;&lt;&lt;END
    &lt;h3&gt;Authorization Required&lt;/h3&gt;
    &lt;p&gt;You need to &lt;a href="$authUrl"&gt;authorize access&lt;/a&gt; before proceeding.&lt;p&gt;
  END;
  }
  ?&gt;
  
  &lt;!doctype html&gt;
  &lt;html&gt;
  &lt;head&gt;
  &lt;title&gt;Video Uploaded&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;?=$htmlBody?&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>Any ideas what have I missed?</p>