The [`YouTubeVideoUploadService`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService.md.txt)
lets you upload videos directly to YouTube through the Google Ads API. These
videos can then be used to create video assets in various ad types, such as
[Performance Max campaigns](https://developers.google.com/google-ads/api/performance-max) or
[Demand Gen campaigns](https://developers.google.com/google-ads/api/docs/demand-gen/overview.md.txt).

This service streamlines the workflow of creating video ads by handling the
YouTube upload process, ensuring that the videos are correctly associated with
your account.

## Key concepts

Before you begin, it's important to understand how video uploads are managed
and the different states they can go through.

### End-to-end workflow

Uploading a video file and serving it in an ad requires four sequential steps:

| Step | Service and method | Summary |
|---|---|---|
| **1. [Initiate and upload video bytes](https://developers.google.com/google-ads/api/docs/assets/upload-videos.md.txt#upload-a-video)** | [`CreateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/CreateYouTubeVideoUpload.md.txt) on [`YouTubeVideoUploadService`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService.md.txt) | Upload the video metadata and binary file to create a [`YouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUpload.md.txt) resource (`customers/{customer_id}/youTubeVideoUploads/{video_upload_id}`). |
| **2. [Poll processing state](https://developers.google.com/google-ads/api/docs/assets/upload-videos.md.txt#retrieve-video-upload-state)** | [`SearchStream`](https://developers.google.com/google-ads/api/reference/rpc/v26/GoogleAdsService/SearchStream.md.txt) on [`GoogleAdsService`](https://developers.google.com/google-ads/api/reference/rpc/v26/GoogleAdsService.md.txt) | Query `you_tube_video_upload` until `state = 'PROCESSED'` and the 11-character YouTube `video_id` is populated. |
| **3. [Create a reusable video asset](https://developers.google.com/google-ads/api/docs/assets/upload-videos.md.txt#use-uploaded-video)** | [`MutateAssets`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetService/MutateAssets.md.txt) on [`AssetService`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetService.md.txt) | Create a [`YoutubeVideoAsset`](https://developers.google.com/google-ads/api/reference/rpc/v26/YoutubeVideoAsset.md.txt) (`youtube_video_id = video_id`) to get an asset resource name (`customers/{customer_id}/assets/{asset_id}`). |
| **4. [Link the asset to campaigns or ads](https://developers.google.com/google-ads/api/docs/assets/upload-videos.md.txt#use-uploaded-video)** | [`MutateAssetGroupAssets`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetGroupAssetService/MutateAssetGroupAssets.md.txt) on [`AssetGroupAssetService`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetGroupAssetService.md.txt), or [`MutateAdGroupAds`](https://developers.google.com/google-ads/api/reference/rpc/v26/AdGroupAdService/MutateAdGroupAds.md.txt) on [`AdGroupAdService`](https://developers.google.com/google-ads/api/reference/rpc/v26/AdGroupAdService.md.txt) | Link the asset to a Performance Max [`AssetGroupAsset`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetGroupAsset.md.txt) (`field_type = YOUTUBE_VIDEO`) or a Demand Gen [`DemandGenVideoResponsiveAdInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/DemandGenVideoResponsiveAdInfo.md.txt) (`videos[].asset`). |

### Channel ownership

When you upload a video, you can specify the destination YouTube channel using
the `channel_id` field in the [`YouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUpload.md.txt)
resource:

- **Advertiser-owned (brand) channel:** Provide the `channel_id` of an existing YouTube channel owned by the advertiser. This is only supported with the [user authentication](https://developers.google.com/google-ads/api/docs/oauth/user-authentication.md.txt) flow; it cannot be used with service accounts.
- **Google-managed channel:** If `channel_id` is omitted, the video is uploaded to a Google-managed YouTube channel associated with the Google Ads account.

### Upload states

The lifecycle of a YouTube video upload is tracked by the `state` field.
The [`YouTubeVideoUploadState`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadStateEnum.YouTubeVideoUploadState.md.txt)
enum defines the following states:

| State | Description |
|---|---|
| `PENDING` | The video is being uploaded. |
| `UPLOADED` | The video has been successfully uploaded and is being processed by YouTube. |
| `PROCESSED` | The video has been successfully processed and is ready for use. |
| `FAILED` | The upload or processing failed and cannot be completed. |
| `REJECTED` | The video was rejected due to validation or policy reasons. |
| `UNAVAILABLE` | The video state is unavailable; it may have been removed from YouTube. |

### Privacy settings

The `video_privacy` field controls who can see the uploaded video. The
[`YouTubeVideoPrivacy`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoPrivacyEnum.YouTubeVideoPrivacy.md.txt)
enum supports:

- **`PUBLIC`**: The video is available to anyone on YouTube. (Only allowed for brand channels).
- **`UNLISTED`**: The video is not searchable but can be viewed by anyone with the link. This is the default and the only option for Google-managed channels.

### Title and description requirements

When creating a video upload, you can specify metadata using the `video_title`
and `video_description` fields in the [`YouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUpload.md.txt) resource:

- **`video_title`** : Required. The title must not exceed 100 characters and must not contain invalid characters (angle brackets `<` or `>`). It can only be set when creating the video upload and is immutable after creation.
- **`video_description`** : Optional. The description must not exceed 5,000 characters and must not contain invalid characters (angle brackets `<` or `>`). It can only be set when creating the video upload and is immutable after creation.

Both fields are immutable after creation. Including `video_title` or
`video_description` in the update mask of an
[`UpdateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/UpdateYouTubeVideoUpload.md.txt) request fails with a
[`FieldError.IMMUTABLE_FIELD`](https://developers.google.com/google-ads/api/reference/rpc/v26/FieldErrorEnum.FieldError.md.txt#immutable_field) error.

For more information on YouTube title and description formatting and invalid
characters, see the [YouTube Help Center](https://support.google.com/youtube/answer/57407).

### Generative content disclosure

Starting in v26, under regulations such as the EU AI Act, advertisers can
disclose whether uploaded video content contains AI-generated or synthetic
media.

The `video_content_metadata` field in [`YouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUpload.md.txt) contains
[`VideoContentMetadata`](https://developers.google.com/google-ads/api/reference/rpc/v26/VideoContentMetadata.md.txt). Set its nested
[`generative_content_disclosure`](https://developers.google.com/google-ads/api/reference/rpc/v26/VideoContentMetadata.md.txt#generative_content_disclosure) field using the
[`YouTubeVideoUploadGenerativeContentDisclosure`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadGenerativeContentDisclosureEnum.YouTubeVideoUploadGenerativeContentDisclosure.md.txt)
enum:

| Value | Description |
|---|---|
| `NON_GENERATIVE_CONTENT` | The advertiser disclosed the video as non-AI generated. |
| `GENERATIVE_CONTENT` | The advertiser disclosed the video as AI generated. |

> [!NOTE]
> **Note:** `video_content_metadata` is input-only and can only be set when creating the YouTube video upload. It is immutable after creation.

## Upload a video

To upload a video, you must use a multi-part request to the
`CreateYouTubeVideoUpload` method. The request contains both the metadata for
the upload and the video file itself.

### 1. Initiate the upload

Construct a [`CreateYouTubeVideoUploadRequest`](https://developers.google.com/google-ads/api/reference/rpc/v26/CreateYouTubeVideoUploadRequest.md.txt) with the following fields:

| Field | Requirement | Description and constraints |
|---|---|---|
| `customer_id` | Required | The 10-digit Google Ads customer ID (without hyphens). |
| `you_tube_video_upload.video_title` | Required (`INPUT_ONLY`, `IMMUTABLE`) | Title of the uploaded video on YouTube (up to 100 characters; cannot contain `<` or `>`). |
| `you_tube_video_upload.video_description` | Optional (`INPUT_ONLY`, `IMMUTABLE`) | Description of the video on YouTube (up to 5,000 characters; cannot contain `<` or `>`). |
| `you_tube_video_upload.channel_id` | Optional (`IMMUTABLE`) | Destination 24-character YouTube channel ID (starting with `UC`) for an advertiser-owned brand channel. If omitted, uploads to a Google-managed channel. |
| `you_tube_video_upload.video_privacy` | Optional (mutable on brand channels) | [`YouTubeVideoPrivacy`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoPrivacyEnum.YouTubeVideoPrivacy.md.txt) setting (`UNLISTED` default, or `PUBLIC` for advertiser-owned brand channels). |
| `you_tube_video_upload.video_content_metadata` | Optional (`INPUT_ONLY`, `IMMUTABLE`) | Available starting in v26: [`VideoContentMetadata`](https://developers.google.com/google-ads/api/reference/rpc/v26/VideoContentMetadata.md.txt) with [`generative_content_disclosure`](https://developers.google.com/google-ads/api/reference/rpc/v26/VideoContentMetadata.md.txt#generative_content_disclosure) set to a [`YouTubeVideoUploadGenerativeContentDisclosure`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadGenerativeContentDisclosureEnum.YouTubeVideoUploadGenerativeContentDisclosure.md.txt) value (`NON_GENERATIVE_CONTENT` or `GENERATIVE_CONTENT`). |

If you are using a client library, call the
[`CreateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/CreateYouTubeVideoUpload.md.txt)
method, passing your video file, and the video upload will be handled
internally.


### Java

```java
String videoUploadResourceName;
try (YouTubeVideoUploadServiceClient youTubeVideoUploadServiceClient =
    googleAdsClient.getLatestVersion().createYouTubeVideoUploadServiceClient()) {
  YouTubeVideoUpload youTubeVideoUpload =
      YouTubeVideoUpload.newBuilder()
          .setVideoTitle("Test Video")
          .setVideoDescription("Test Video Description")
          .setVideoPrivacy(YouTubeVideoPrivacy.UNLISTED)
          .build();

  CreateYouTubeVideoUploadRequest createUploadRequest =
      CreateYouTubeVideoUploadRequest.newBuilder()
          .setCustomerId(Long.toString(customerId))
          .setYouTubeVideoUpload(youTubeVideoUpload)
          .build();

  CreateYouTubeVideoUploadResponse response =
      youTubeVideoUploadServiceClient.createYouTubeVideoUpload(
          createUploadRequest, () -> new FileInputStream(videoFile), null);

  videoUploadResourceName = response.getResourceName();
  System.out.printf("Created YouTube video upload: %s%n", videoUploadResourceName);
}
      
```

### C#

```c#
YouTubeVideoUploadServiceClient ytService = client.GetService(
    Services.V26.YouTubeVideoUploadService);

CreateYouTubeVideoUploadRequest createUploadRequest =
    new CreateYouTubeVideoUploadRequest()
    {
        CustomerId = customerId.ToString(),
        YouTubeVideoUpload = new YouTubeVideoUpload()
        {
            VideoTitle = "Test Video",
            VideoDescription = "Test Video Description",
            VideoPrivacy = YouTubeVideoPrivacy.Unlisted
        }
    };

string videoUploadResourceName;
using (FileStream stream = File.OpenRead(videoFilePath))
{
    ResumableUploadSession<CreateYouTubeVideoUploadRequest, CreateYouTubeVideoUploadResponse> session =
        ytService.CreateYouTubeVideoUpload();
    CreateYouTubeVideoUploadResponse response =
        session.BeginUploadAsync(createUploadRequest, stream).Result;

    videoUploadResourceName = response.ResourceName;
    Console.WriteLine($"Created YouTube video upload: {videoUploadResourceName}");
}
      
```

### PHP

```php
$youTubeVideoUploadServiceClient = $googleAdsClient->getYouTubeVideoUploadServiceClient();

$youTubeVideoUpload = new YouTubeVideoUpload([
    'video_title' => 'Test Video',
    'video_description' => 'Test Video Description',
    'video_privacy' => YouTubeVideoPrivacy::UNLISTED
]);

$createYouTubeVideoUploadRequest = CreateYouTubeVideoUploadRequest::build(
    $customerId,
    $youTubeVideoUpload
);

/** @var ResumableUpload $resumableUpload */
$resumableUpload = $youTubeVideoUploadServiceClient->createYouTubeVideoUpload(
    $createYouTubeVideoUploadRequest
);

$stream = Utils::streamFor(fopen($videoFilePath, 'rb'));
/** @var CreateYouTubeVideoUploadResponse $response */
$response = $resumableUpload->startUpload($stream);

$videoUploadResourceName = $response->getResourceName();
printf("Created YouTube video upload: '%s'%s", $videoUploadResourceName, PHP_EOL);
      
```

### Python

```python
yt_service: YouTubeVideoUploadServiceClient = client.get_service(
    "YouTubeVideoUploadService"
)

create_upload_request: CreateYouTubeVideoUploadRequest = client.get_type(
    "CreateYouTubeVideoUploadRequest"
)
create_upload_request.customer_id = customer_id
create_upload_request.you_tube_video_upload.video_title = "Test Video"
create_upload_request.you_tube_video_upload.video_description = (
    "Test Video Description"
)
create_upload_request.you_tube_video_upload.video_privacy = (
    client.enums.YouTubeVideoPrivacyEnum.UNLISTED
)

upload_session: ResumableUploadSession = (
    yt_service.create_you_tube_video_upload(request=create_upload_request)
)
video_upload_resource_name: str
with open(video_file_path, "rb") as stream:
    response: CreateYouTubeVideoUploadResponse = upload_session.upload(
        stream
    )
    video_upload_resource_name = response.resource_name
    print(f"Created YouTube video upload: {video_upload_resource_name}")
      
```

### Ruby

```ruby
video_upload = client.resource.you_tube_video_upload do |v|
  v.video_title = "Test Video"
  v.video_description = "Test Video Description"
  v.video_privacy = :UNLISTED
end

upload = client.service.you_tube_video_upload.create_you_tube_video_upload(
  customer_id: customer_id,
  you_tube_video_upload: video_upload,
)

video_upload_resource_name = nil
File.open(video_file_path, "rb") do |stream|
  response = upload.start(
    stream: stream,
    content_type: "application/octet-stream",
  )
  video_upload_resource_name = response.resource_name
  puts "Created YouTube video upload: #{video_upload_resource_name}"
end
      
```

### Perl

```
This example is not yet available in Perl; you can take a look at the other languages.
    
```

### curl

```console
# 
# Use the --i curl parameter to capture response headers in the $RESPONSE
# variable.
FILE_SIZE=$(wc -c < "${VIDEO_FILE_NAME}" | tr -d '\r')
RESPONSE=$(curl -i -f -v -s --request POST \
"https://googleads.googleapis.com/resumable/upload/v${API_VERSION}/customers/${CUSTOMER_ID}/youTubeVideoUploads:create" \
--header "Content-Type: application/json" \
--header "login-customer-id: ${MANAGER_CUSTOMER_ID}" \
--header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
--header "X-Goog-Upload-Protocol: resumable" \
--header "X-Goog-Upload-Command: start" \
--header "X-Goog-Upload-Header-Content-Length: ${FILE_SIZE}" \
--data @- <<EOF
{
  "customer_id": "${CUSTOMER_ID}",
  "you_tube_video_upload": {
    "video_title": "${VIDEO_TITLE}",
    "video_description": "${VIDEO_DESCRIPTION}",
    "video_privacy": "UNLISTED"
  }
}
EOF
)

# Extract the value of the "x-goog-upload-url" header from the HTTP response.
UPLOAD_URL=$(echo "${RESPONSE}" \
  | grep -i '^x-goog-upload-url' \
  | awk '{print $2}' \
  | tr -d '\r')
CHUNK_SIZE=$(echo "${RESPONSE}" \
  | grep -i '^x-goog-upload-chunk-granularity' \
  | awk '{print $2}' \
  | tr -d '\r')
      
```

<br />

If you are using REST, the following section describes how to manage the
video upload.

### 2. Upload the video

When uploading with REST, [`CreateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/CreateYouTubeVideoUpload.md.txt) uses a two-step
[resumable upload protocol](https://cloud.google.com/storage/docs/performing-resumable-uploads):

1. **Start the upload session:** Send an initial `POST` request to the [`CreateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/CreateYouTubeVideoUpload.md.txt) upload endpoint (`/upload/v26/customers/{customer_id}/youTubeVideoUploads:create`) with `x-goog-upload-command: start` (and optionally `x-goog-upload-header-content-length` to declare the file size). The response returns the upload session URL in the `x-goog-upload-url` HTTP response header, along with metadata such as the expected chunk size.
2. **Stream and finalize the video bytes:** Send the binary video payload to the `x-goog-upload-url` endpoint with `x-goog-upload-command: upload, finalize`. When the upload finishes, the response returns the created `resource_name` (`customers/{customer_id}/youTubeVideoUploads/{video_upload_id}`).

For a full description of the HTTP headers used in the video upload protocol,
refer to the following code example:


```
# Take the first ${CHUNK_SIZE} bytes of the video file and upload them.
head -c ${CHUNK_SIZE} ${VIDEO_FILE_NAME} | curl -i -v -X PUT "${UPLOAD_URL}" \
--header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
--header "X-Goog-Upload-Offset: 0" \
--header "X-Goog-Upload-Command: upload" \
--header "Content-Length: ${CHUNK_SIZE}" \
--data-binary @-

# Query the status of the upload.
QUERY_RESPONSE=$(curl -i -s -X POST "${UPLOAD_URL}" \
--header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
--header "X-Goog-Upload-Command: query")

# Extract the value of the "x-goog-upload-size-received" header from the HTTP
# response.
UPLOADED_BYTES=$(echo "${QUERY_RESPONSE}" \
  | grep -i '^x-goog-upload-size-received' \
  | awk '{print $2}' \
  | tr -d '\r')

echo "Uploaded ${UPLOADED_BYTES} bytes."

REMAINING_BYTES=$((FILE_SIZE - UPLOADED_BYTES))
echo "${REMAINING_BYTES} bytes remaining to upload."

FINALIZE_RESPONSE=$(tail -c ${REMAINING_BYTES} ${VIDEO_FILE_NAME} | curl -v -X PUT "${UPLOAD_URL}" \
--header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
--header "X-Goog-Upload-Offset: ${UPLOADED_BYTES}" \
--header "X-Goog-Upload-Command: upload, finalize" \
--data-binary @-)
UPLOADED_VIDEO_RESOURCE_NAME=$(echo $FINALIZE_RESPONSE | jq -r '.resourceName')
      
```

<br />

### 3. Retrieve the video upload state

After starting a video upload, poll its processing state by querying the
selectable fields of the [`you_tube_video_upload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUpload.md.txt) resource
(`resource_name`, `video_upload_id`, `channel_id`, `video_id`, `state`, and
`video_privacy`) with GAQL until `state = 'PROCESSED'` (`INPUT_ONLY` fields
cannot be selected in GAQL):


### Java

```java
// Retrieve the metadata of the newly uploaded video.
String query =
    String.format(
        "SELECT "
            + "you_tube_video_upload.resource_name, "
            + "you_tube_video_upload.video_id, "
            + "you_tube_video_upload.state "
            + "FROM you_tube_video_upload "
            + "WHERE you_tube_video_upload.resource_name = '%s'",
        videoUploadResourceName);

try (GoogleAdsServiceClient googleAdsServiceClient =
    googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
  SearchGoogleAdsStreamRequest request =
      SearchGoogleAdsStreamRequest.newBuilder()
          .setCustomerId(Long.toString(customerId))
          .setQuery(query)
          .build();

  ServerStream<SearchGoogleAdsStreamResponse> stream =
      googleAdsServiceClient.searchStreamCallable().call(request);

  for (SearchGoogleAdsStreamResponse response : stream) {
    for (GoogleAdsRow googleAdsRow : response.getResultsList()) {
      YouTubeVideoUpload video = googleAdsRow.getYouTubeVideoUpload();
      System.out.printf(
          "Video with ID %s was found in state %s.%n",
          video.getVideoId(), video.getState());
    }
  }
}
      
```

### C#

```c#
// Retrieve the metadata of the newly uploaded video.
string query = $@"
    SELECT
      you_tube_video_upload.resource_name,
      you_tube_video_upload.video_id,
      you_tube_video_upload.state
    FROM you_tube_video_upload
    WHERE you_tube_video_upload.resource_name = '{videoUploadResourceName}'";

GoogleAdsServiceClient gaService = client.GetService(
    Services.V26.GoogleAdsService);

gaService.SearchStream(customerId.ToString(), query,
    delegate (SearchGoogleAdsStreamResponse resp)
    {
        foreach (GoogleAdsRow row in resp.Results)
        {
            Console.WriteLine(
                $"Video with ID {row.YouTubeVideoUpload.VideoId} was found in " +
                $"state {row.YouTubeVideoUpload.State}.");
        }
    }
);
      
```

### PHP

```php
// Retrieve the metadata of the newly uploaded video.
$query = sprintf(
    "SELECT you_tube_video_upload.resource_name, "
    . "you_tube_video_upload.video_id, "
    . "you_tube_video_upload.state "
    . "FROM you_tube_video_upload "
    . "WHERE you_tube_video_upload.resource_name = '%s'",
    $videoUploadResourceName
);

$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
$stream = $googleAdsServiceClient->searchStream(
    SearchGoogleAdsStreamRequest::build($customerId, $query)
);

foreach ($stream->iterateAllElements() as $googleAdsRow) {
    /** @var GoogleAdsRow $googleAdsRow */
    printf(
        "Video with ID '%s' was found in state '%s'.%s",
        $googleAdsRow->getYouTubeVideoUpload()->getVideoId(),
        YouTubeVideoUploadState::name($googleAdsRow->getYouTubeVideoUpload()->getState()),
        PHP_EOL
    );
}
      
```

### Python

```python
# Retrieve the metadata of the newly uploaded video.
query: str = f"""
    SELECT
      you_tube_video_upload.resource_name,
      you_tube_video_upload.video_id,
      you_tube_video_upload.state
    FROM you_tube_video_upload
    WHERE you_tube_video_upload.resource_name = '{video_upload_resource_name}'"""

ga_service: GoogleAdsServiceClient = client.get_service("GoogleAdsService")
stream: Iterator[SearchGoogleAdsStreamResponse] = ga_service.search_stream(
    customer_id=customer_id, query=query
)

for row in itertools.chain.from_iterable(
    batch.results for batch in stream
):
    print(
        f"Video with ID {row.you_tube_video_upload.video_id} was found in "
        f"state {row.you_tube_video_upload.state}."
    )
      
```

### Ruby

```ruby
# Retrieve the metadata of the newly uploaded video.
query = <<~EOQ
  SELECT
    you_tube_video_upload.resource_name,
    you_tube_video_upload.video_id,
    you_tube_video_upload.state
  FROM you_tube_video_upload
  WHERE you_tube_video_upload.resource_name = '#{video_upload_resource_name}'
EOQ

responses = client.service.google_ads.search_stream(
  customer_id: customer_id,
  query: query,
)

responses.each do |batch|
  batch.results.each do |row|
    puts "Video with ID #{row.you_tube_video_upload.video_id} was found in " \
      "state #{row.you_tube_video_upload.state}."
  end
end
      
```

### Perl

```
This example is not yet available in Perl; you can take a look at the other languages.
    
```

### curl

```console
curl -i -v -X POST \
"https://googleads.googleapis.com/v${API_VERSION}/customers/${CUSTOMER_ID}/googleAds:search" \
--header "Content-Type: application/json" \
  --header "login-customer-id: ${MANAGER_CUSTOMER_ID}" \
  --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
  --data @- <<EOF
{
  "query": "SELECT you_tube_video_upload.resource_name, you_tube_video_upload.video_id, you_tube_video_upload.state FROM you_tube_video_upload WHERE you_tube_video_upload.resource_name = '$UPLOADED_VIDEO_RESOURCE_NAME'"
}
EOF
      
```

<br />

## Manage uploads

After a video upload is completed, you can use it as a video asset.

### Use the uploaded video

After a video reaches the `PROCESSED` state, you can find its 11-character
YouTube video ID in the `video_id` field of the `YouTubeVideoUpload` resource.

Use this `video_id` to create a [`YoutubeVideoAsset`](https://developers.google.com/google-ads/api/reference/rpc/v26/YoutubeVideoAsset.md.txt) with
[`MutateAssets`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetService/MutateAssets.md.txt) and link the resulting asset resource name
(`customers/{customer_id}/assets/{asset_id}`) to your campaigns or ads:

1. **Create the `YoutubeVideoAsset`** by passing `youtube_video_id` to
   [`AssetService.MutateAssets`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetService/MutateAssets.md.txt):

   ```json
   POST https://googleads.googleapis.com/v26/customers/{customer_id}/assets:mutate
   {
     "operations": [
       {
         "create": {
           "name": "Product Launch Video",
           "youtubeVideoAsset": {
             "youtubeVideoId": "dQw4w9WgXcQ"
           }
         }
       }
     ]
   }
   ```
2. **Link the `Asset` to an `AssetGroup` or `AdGroupAd`**:

   - **Performance Max:** Create an [`AssetGroupAsset`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetGroupAsset.md.txt)
     using
     [`AssetGroupAssetService.MutateAssetGroupAssets`](https://developers.google.com/google-ads/api/reference/rpc/v26/AssetGroupAssetService/MutateAssetGroupAssets.md.txt)
     with `field_type` set to `YOUTUBE_VIDEO`:

     ```json
     POST https://googleads.googleapis.com/v26/customers/{customer_id}/assetGroupAssets:mutate
     {
       "operations": [
         {
           "create": {
             "assetGroup": "customers/{customer_id}/assetGroups/{asset_group_id}",
             "asset": "customers/{customer_id}/assets/{asset_id}",
             "fieldType": "YOUTUBE_VIDEO"
           }
         }
       ]
     }
     ```
   - **Demand Gen:** Reference `customers/{customer_id}/assets/{asset_id}` in
     the `videos` list of a
     [`DemandGenVideoResponsiveAdInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/DemandGenVideoResponsiveAdInfo.md.txt) object when
     calling [`AdGroupAdService.MutateAdGroupAds`](https://developers.google.com/google-ads/api/reference/rpc/v26/AdGroupAdService/MutateAdGroupAds.md.txt):

     ```json
     POST https://googleads.googleapis.com/v26/customers/{customer_id}/adGroupAds:mutate
     {
       "operations": [
         {
           "create": {
             "adGroup": "customers/{customer_id}/adGroups/{ad_group_id}",
             "status": "PAUSED",
             "ad": {
               "name": "Demand Gen Video Ad",
               "finalUrls": ["https://www.example.com"],
               "demandGenVideoResponsiveAd": {
                 "videos": [
                   {
                     "asset": "customers/{customer_id}/assets/{asset_id}"
                   }
                 ],
                 "logoImages": [
                   {
                     "asset": "customers/{customer_id}/assets/{logo_asset_id}"
                   }
                 ],
                 "headlines": [
                   {
                     "text": "Example Headline"
                   }
                 ],
                 "longHeadlines": [
                   {
                     "text": "Example Long Headline"
                   }
                 ],
                 "descriptions": [
                   {
                     "text": "Example Description"
                   }
                 ],
                 "businessName": {
                   "text": "Example Business"
                 }
               }
             }
           }
         }
       ]
     }
     ```
3. **Query existing `YOUTUBE_VIDEO` assets** using
   [`GoogleAdsService.SearchStream`](https://developers.google.com/google-ads/api/reference/rpc/v26/GoogleAdsService/SearchStream.md.txt):

   ```sql
   SELECT
     asset.id,
     asset.resource_name,
     asset.name,
     asset.type,
     asset.youtube_video_asset.youtube_video_id,
     asset.youtube_video_asset.youtube_video_title
   FROM asset
   WHERE asset.type = 'YOUTUBE_VIDEO'
     AND asset.youtube_video_asset.youtube_video_id = '{video_id}'
   ```

### Update metadata

You can update the privacy setting of a video uploaded through this API using
the [`UpdateYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/UpdateYouTubeVideoUpload.md.txt) method with `update_mask`
set to `"video_privacy"`. Only the `video_privacy` field (`PUBLIC` or
`UNLISTED`, for videos uploaded to an advertiser-owned brand channel) can be
updated after creation; `video_title`, `video_description`, `channel_id`, and
`video_content_metadata` are immutable. Including `video_title` or
`video_description` in the update mask fails with a
[`FieldError.IMMUTABLE_FIELD`](https://developers.google.com/google-ads/api/reference/rpc/v26/FieldErrorEnum.FieldError.md.txt#immutable_field) error.

### Remove uploads

If you need to delete videos uploaded with the Google Ads API, pass their
`customers/{customer_id}/youTubeVideoUploads/{video_upload_id}` resource names
in the `resource_names` field of the
[`RemoveYouTubeVideoUpload`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeVideoUploadService/RemoveYouTubeVideoUpload.md.txt) method. This removes the
video from both the Google Ads asset library and YouTube.