Merchant API code sample to insert product input asynchronously.

### Go

    // Copyright 2025 Google LLC
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //     https://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.

    package v1


    import (
    	"context"
    	"fmt"
    	"log"
    	"math/rand"
    	"sync"
    	"time"

    	"cloud.google.com/go/shopping/merchant/products/apiv1/productspb"
    	"cloud.google.com/go/shopping/type/typepb"
    	"google.golang.org/api/option"

    	products "cloud.google.com/go/shopping/merchant/products/apiv1"

    	"github.com/google/merchant-api-samples/go/collection"
    	"github.com/google/merchant-api-samples/go/examples/googleauth"
    )

    const (
    	// The ID of the account that owns the data source.
    	accountForInsertAsync = "accounts/1234567890"
    	// The ID of the data source that will own the product.
    	// You can only insert products into datasource types of Input "API", and of Type
    	// "Primary" or "Supplemental."
    	// This field takes the `name` field of the datasource.
    	dataSourceForInsertAsync = "accounts/1234567890/dataSources/1234567890"
    )

    var seededRand *rand.Rand = rand.New(
    	rand.NewSource(time.Now().UnixNano()))

    const charset = "abcdefghijklmnopqrstuvwxyz" +
    	"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

    // generateRandomString creates a random string of a given length.
    func generateRandomString(length int) string {
    	b := make([]byte, length)
    	for i := range b {
    		b[i] = charset[seededRand.Intn(len(charset))]
    	}
    	return string(b)
    }

    // createRandomProductInput creates a new ProductInput with a random offer ID.
    func createRandomProductInput() *productspb.ProductInput {
    	price := &typepb.Price{
    		AmountMicros: collection.Ptr(int64(33450000)),
    		CurrencyCode: collection.Ptr("USD"),
    	}

    	shipping1 := &productspb.Shipping{
    		Price:   price,
    		Country: "GB",
    		Service: "1st class post",
    	}

    	shipping2 := &productspb.Shipping{
    		Price:   price,
    		Country: "FR",
    		Service: "1st class post",
    	}

    	attributes := &productspb.ProductAttributes{
    		Title:                 collection.Ptr("A Tale of Two Cities"),
    		Description:           collection.Ptr("A classic novel about the French Revolution"),
    		Link:                  collection.Ptr("https://exampleWebsite.com/tale-of-two-cities.html"),
    		ImageLink:             collection.Ptr("https://exampleWebsite.com/tale-of-two-cities.jpg"),
    		Availability:          collection.Ptr(productspb.Availability_IN_STOCK),
    		Condition:             collection.Ptr(productspb.Condition_NEW),
    		GoogleProductCategory: collection.Ptr("Media > Books"),
    		Gtins:                 []string{"9780007350896"},
    		Shipping:              []*productspb.Shipping{shipping1, shipping2},
    	}

    	return &productspb.ProductInput{
    		ContentLanguage:   "en",
    		FeedLabel:         "CH",
    		OfferId:           generateRandomString(8),
    		ProductAttributes: attributes,
    	}
    }

    type insertProductInputAsyncSample struct{}

    func init() {
    	if err := collection.Add("products.productinputs.v1.insert_product_input_async", &insertProductInputAsyncSample{}); err != nil {
    		log.Fatalf("could not add example: %v", err)
    	}
    }

    func (s *insertProductInputAsyncSample) Description() string {
    	return "This sample demonstrates how to insert a product input asynchronously"
    }

    func (s *insertProductInputAsyncSample) Execute() error {
    	ctx := context.Background()

    	// Authenticates with Google.
    	tokenSource, err := googleauth.AuthWithGoogle(ctx)
    	if err != nil {
    		return fmt.Errorf("failed to authenticate: %w", err)
    	}

    	// Creates a new product inputs client.
    	//
    	// This sample demonstrates how to configure the gRPC client to use a connection
    	// pool. A connection pool manages multiple gRPC connections, which can
    	// enhance throughput for bulk operations.
    	//
    	// We recommend estimating the number of concurrent requests you'll make, divide by 50 (a
    	// conservative estimate of 50% utilization of a connection's capacity), and set the
    	// pool size to that number.
    	productInputsClient, err := products.NewProductInputsClient(ctx,
    		option.WithTokenSource(tokenSource),
    		option.WithGRPCConnectionPool(30),
    	)
    	if err != nil {
    		return fmt.Errorf("could not create product inputs client: %w", err)
    	}
    	defer productInputsClient.Close()

    	parent := accountForInsertAsync

    	// Number of concurrent requests to send.
    	const numRequests = 5

    	var wg sync.WaitGroup
    	resultsChan := make(chan *productspb.ProductInput, numRequests)
    	errChan := make(chan error, numRequests)

    	fmt.Println("Sending insert product input requests")

    	// Launch goroutines to send requests concurrently.
    	for i := 0; i < numRequests; i++ {
    		wg.Add(1)
    		go func() {
    			defer wg.Done()

    			req := &productspb.InsertProductInputRequest{
    				Parent:     parent,
    				DataSource: dataSourceForInsertAsync,
    				// If this product is already owned by another datasource, when re-inserting, the
    				// new datasource will take ownership of the product.
    				ProductInput: createRandomProductInput(),
    			}

    			// Calls the API to insert the product input.
    			resp, err := productInputsClient.InsertProductInput(ctx, req)
    			if err != nil {
    				errChan <- fmt.Errorf("could not insert product input: %w", err)
    				return
    			}
    			resultsChan <- resp
    		}()
    	}

    	// Wait for all goroutines to finish and then close the channels.
    	go func() {
    		wg.Wait()
    		close(resultsChan)
    		close(errChan)
    	}()

    	// Collect all errors.
    	var errs []error
    	for err := range errChan {
    		errs = append(errs, err)
    	}

    	// Collect all successful results.
    	var results []*productspb.ProductInput
    	for result := range resultsChan {
    		results = append(results, result)
    	}

    	// Handle results and errors after all requests are complete.
    	if len(errs) > 0 {
    		for _, err := range errs {
    			fmt.Println(err)
    		}
    		return fmt.Errorf("encountered %d errors during async insert", len(errs))
    	}

    	fmt.Println("Inserted products below")
    	fmt.Println(results)

    	return nil
    }


### Java

    // Copyright 2024 Google LLC
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //     https://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.

    package shopping.merchant.samples.products.v1;

    import com.google.api.core.ApiFuture;
    import com.google.api.core.ApiFutureCallback;
    import com.google.api.core.ApiFutures;
    import com.google.api.gax.core.FixedCredentialsProvider;
    import com.google.api.gax.grpc.ChannelPoolSettings;
    import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
    import com.google.auth.oauth2.GoogleCredentials;
    import com.google.common.util.concurrent.MoreExecutors;
    import com.google.shopping.merchant.products.v1.Availability;
    import com.google.shopping.merchant.products.v1.Condition;
    import com.google.shopping.merchant.products.v1.InsertProductInputRequest;
    import com.google.shopping.merchant.products.v1.ProductAttributes;
    import com.google.shopping.merchant.products.v1.ProductInput;
    import com.google.shopping.merchant.products.v1.ProductInputsServiceClient;
    import com.google.shopping.merchant.products.v1.ProductInputsServiceSettings;
    import com.google.shopping.merchant.products.v1.Shipping;
    import com.google.shopping.type.Price;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.stream.Collectors;
    import shopping.merchant.samples.utils.Authenticator;
    import shopping.merchant.samples.utils.Config;

    /** This class demonstrates how to insert a product input */
    public class InsertProductInputAsyncSample {

      private static String getParent(String accountId) {
        return String.format("accounts/%s", accountId);
      }

      private static String generateRandomString() {
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder(8);
        for (int i = 0; i < 8; i++) {
          sb.append(characters.charAt(random.nextInt(characters.length())));
        }
        return sb.toString();
      }

      private static ProductInput createRandomProduct() {
        Price price = Price.newBuilder().setAmountMicros(33_450_000).setCurrencyCode("USD").build();

        Shipping shipping =
            Shipping.newBuilder().setPrice(price).setCountry("GB").setService("1st class post").build();

        Shipping shipping2 =
            Shipping.newBuilder().setPrice(price).setCountry("FR").setService("1st class post").build();

        ProductAttributes attributes =
            ProductAttributes.newBuilder()
                .setTitle("A Tale of Two Cities")
                .setDescription("A classic novel about the French Revolution")
                .setLink("https://exampleWebsite.com/tale-of-two-cities.html")
                .setImageLink("https://exampleWebsite.com/tale-of-two-cities.jpg")
                .setAvailability(Availability.IN_STOCK)
                .setCondition(Condition.NEW)
                .setGoogleProductCategory("Media > Books")
                .addGtins("9780007350896")
                .addShipping(shipping)
                .addShipping(shipping2)
                .build();

        return ProductInput.newBuilder()
            .setContentLanguage("en")
            .setFeedLabel("CH")
            .setOfferId(generateRandomString())
            .setProductAttributes(attributes)
            .build();
      }

      public static void asyncInsertProductInput(Config config, String dataSource) throws Exception {

        // Obtains OAuth token based on the user's configuration.
        GoogleCredentials credential = new Authenticator().authenticate();

        // Creates a channel provider. This provider manages a pool of gRPC channels
        // to enhance throughput for bulk operations. Each individual channel in the pool
        // can handle up to approximately 100 concurrent requests.
        //
        // Channel: A single connection pathway to the service.
        // Pool: A collection of multiple channels managed by this provider.
        //   Requests are distributed across the channels in the pool.
        //
        // We recommend estimating the number of concurrent requests you'll make, divide by 50 (50%
        // utilization of channel capacity), and set the pool size to that number.
        InstantiatingGrpcChannelProvider channelProvider =
            InstantiatingGrpcChannelProvider.newBuilder()
                .setChannelPoolSettings(ChannelPoolSettings.staticallySized(30))
                .build();

        // Creates service settings using the credentials retrieved above.
        ProductInputsServiceSettings productInputsServiceSettings =
            ProductInputsServiceSettings.newBuilder()
                .setCredentialsProvider(FixedCredentialsProvider.create(credential))
                .setTransportChannelProvider(channelProvider)
                .build();

        // Creates parent to identify where to insert the product.
        String parent = getParent(config.getAccountId().toString());

        // Calls the API and catches and prints any network failures/errors.
        try (ProductInputsServiceClient productInputsServiceClient =
            ProductInputsServiceClient.create(productInputsServiceSettings)) {

          // Creates five insert product input requests with random product IDs.
          List<InsertProductInputRequest> requests = new ArrayList<>(5);
          for (int i = 0; i < 5; i++) {
            InsertProductInputRequest request =
                InsertProductInputRequest.newBuilder()
                    .setParent(parent)
                    // You can only insert products into datasource types of Input "API", and of Type
                    // "Primary" or "Supplemental."
                    // This field takes the `name` field of the datasource.
                    .setDataSource(dataSource)
                    // If this product is already owned by another datasource, when re-inserting, the
                    // new datasource will take ownership of the product.
                    .setProductInput(createRandomProduct())
                    .build();

            requests.add(request);
          }

          System.out.println("Sending insert product input requests");
          List<ApiFuture<ProductInput>> futures =
              requests.stream()
                  .map(
                      request ->
                          productInputsServiceClient.insertProductInputCallable().futureCall(request))
                  .collect(Collectors.toList());

          // Creates callback to handle the responses when all are ready.
          ApiFuture<List<ProductInput>> responses = ApiFutures.allAsList(futures);
          ApiFutures.addCallback(
              responses,
              new ApiFutureCallback<List<ProductInput>>() {
                @Override
                public void onSuccess(List<ProductInput> results) {
                  System.out.println("Inserted products below");
                  System.out.println(results);
                }

                @Override
                public void onFailure(Throwable throwable) {
                  System.out.println(throwable);
                }
              },
              MoreExecutors.directExecutor());

        } catch (Exception e) {
          System.out.println(e);
        }
      }

      public static void main(String[] args) throws Exception {
        Config config = Config.load();
        // Identifies the data source that will own the product input.
        String dataSource = "accounts/" + config.getAccountId() + "/dataSources/{datasourceId}";

        asyncInsertProductInput(config, dataSource);
      }
    }

### Node.js

    // Copyright 2026 Google LLC
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //     https://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.

    'use strict';
    const fs = require('fs');
    const grpc = require('@grpc/grpc-js');
    const grpcGcp = require('grpc-gcp')(grpc);
    const authUtils = require('../../authentication/authenticate.js');
    const {ProductInputsServiceClient} = require('@google-shopping/products').v1;

    const {protos} = require('@google-shopping/products');

    const Availability = protos.google.shopping.merchant.products.v1.Availability;
    const Condition = protos.google.shopping.merchant.products.v1.Condition;

    /**
     * This class demonstrates how to insert a product input asynchronously.
     */

    /**
     * Helper function to generate a random string for offerId
     * @returns {string} A sample offerId.
     */
    function generateRandomString() {
      const characters =
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      let result = '';
      const length = 8;
      for (let i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * characters.length));
      }
      return result;
    }

    /**
     * Helper function to create a sample ProductInput object
     * @returns {!object} A sample ProductInput object.
     */
    function createRandomProduct() {
      const shippingPrice = {
        amountMicros: 3000000, // 3 USD
        currency_code: 'USD',
      };

      const price = {
        amountMicros: 33450000, // 33.45 USD
        currency_code: 'USD',
      };

      const shipping = {
        price: shippingPrice,
        country: 'GB',
        service: '1st class post',
      };

      const shipping2 = {
        price: shippingPrice,
        country: 'FR',
        service: '1st class post',
      };

      const attributes = {
        title: 'A Tale of Two Cities',
        description: 'A classic novel about the French Revolution',
        link: 'https://exampleWebsite.com/tale-of-two-cities.html',
        imageLink: 'https://exampleWebsite.com/tale-of-two-cities.jpg',
        availability: Availability.IN_STOCK,
        condition: Condition.NEW,
        googleProductCategory: 'Media > Books',
        gtins: ['9780007350896'],
        shipping: [shipping, shipping2],
        price: price,
      };

      // Construct the ProductInput object
      const productInput = {
        contentLanguage: 'en',
        feedLabel: 'CH',
        offerId: generateRandomString(),
        productAttributes: attributes,
      };

      return productInput;
    }

    /**
     * Inserts multiple product inputs asynchronously.
     * @param {!object} config - Configuration object.
     * @param {string} dataSource - The data source name.
     */
    async function asyncInsertProductInput(config, dataSource) {
      // Read merchant_id from the configuration file.
      const merchantInfo = JSON.parse(
        fs.readFileSync(config.merchantInfoFile, 'utf8'),
      );
      const merchantId = merchantInfo.merchantId;

      // Construct the parent resource name string.
      const parent = `accounts/${merchantId}`;

      // Get OAuth2 credentials.
      const authClient = await authUtils.getOrGenerateUserCredentials();

      // Create GCP API configuration for channel pooling.
      // We recommend estimating the number of concurrent requests you'll make,
      // divide by 50 (50% utilization of channel capacity), and set the pool size
      // to that number.
      const apiConfig = grpcGcp.createGcpApiConfig({
        channelPool: {
          maxSize: 30,
        },
      });

      // Create client options with authentication and channel pooling.
      const options = {
        authClient: authClient,
        'grpc.callInvocationTransformer': grpcGcp.gcpCallInvocationTransformer,
        'grpc.channelFactoryOverride': grpcGcp.gcpChannelFactoryOverride,
        'grpc.gcpApiConfig': apiConfig,
      };

      // Create the ProductInputsServiceClient with the channel pooling configuration.
      const productInputsServiceClient = new ProductInputsServiceClient(options);

      // Create five insert product input requests with random product details.
      const requests = [];
      for (let i = 0; i < 5; i++) {
        const request = {
          parent: parent,
          // You can only insert products into datasource types of Input "API", and
          // of Type "Primary" or "Supplemental."
          // This field takes the `name` field of the datasource, e.g.,
          // accounts/123/dataSources/456
          dataSource: dataSource,
          // If this product is already owned by another datasource, when
          // re-inserting, the new datasource will take ownership of the product.
          productInput: createRandomProduct(),
        };
        requests.push(request);
      }

      console.log('Sending insert product input requests');

      // Create an array of promises by calling the insertProductInput method for
      // each request. The grpc-gcp package automatically distributes these requests
      // across the channel pool.
      const insertPromises = requests.map(request => {
        return productInputsServiceClient.insertProductInput(request);
      });

      // Wait for all insert operations to complete.
      // Promise.all returns an array of results, where each result is the response
      // from the corresponding insertProductInput call (which is the inserted ProductInput).
      // The response from insertProductInput is an array where the first element is the ProductInput.
      const results = await Promise.all(insertPromises);
      const insertedProducts = results.map(result => result[0]); // Extract ProductInput from each response array

      console.log('Inserted products below');
      console.log(insertedProducts);
    }

    /**
     * Main function to call the async insert product input method.
     */
    async function main() {
      // Get configuration settings.
      const config = authUtils.getConfig();
      // Define the data source ID. Replace {datasourceId} with your actual data source ID.
      // The format is accounts/{account_id}/dataSources/{datasource_id}.
      const merchantInfo = JSON.parse(
        fs.readFileSync(config.merchantInfoFile, 'utf8'),
      );
      const merchantId = merchantInfo.merchantId;
      const dataSource = `accounts/${merchantId}/dataSources/{datasourceId}`; // Replace {datasourceId}

      try {
        await asyncInsertProductInput(config, dataSource);
      } catch (error) {
        console.log(error);
      }
    }

    main();

### PHP

    <?php
    /**
     * Copyright 2025 Google LLC
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * https://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    require_once __DIR__ . '/../../../vendor/autoload.php';
    require_once __DIR__ . '/../../Authentication/Authentication.php';
    require_once __DIR__ . '/../../Authentication/Config.php';
    use Google\ApiCore\ApiException;
    use Google\Shopping\Merchant\Products\V1\Availability;
    use Google\Shopping\Merchant\Products\V1\Condition;
    use Google\Shopping\Merchant\Products\V1\ProductAttributes;
    use Google\Shopping\Merchant\Products\V1\InsertProductInputRequest;
    use Google\Shopping\Merchant\Products\V1\ProductInput;
    use Google\Shopping\Merchant\Products\V1\Client\ProductInputsServiceClient;
    use Google\Shopping\Merchant\Products\V1\Shipping;
    use Google\Shopping\Type\Price;
    use GuzzleHttp\Promise\Utils;

    /**
     * This class demonstrates how to insert multiple product inputs asynchronously.
     */
    class InsertProductInputAsyncSample
    {
        /**
         * A helper function to create the parent string for product input operations.
         *
         * @param string $accountId The Merchant Center account ID.
         * @return string The parent resource name format: `accounts/{account_id}`.
         */
        private static function getParent(string $accountId): string
        {
            return sprintf("accounts/%s", $accountId);
        }

        /**
         * Generates a random string of 8 characters.
         *
         * @return string A random alphanumeric string.
         */
        private static function generateRandomString(): string
        {
            $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
            $randomString = '';
            $charactersLength = strlen($characters);
            for ($i = 0; $i < 8; $i++) {
                $randomString .= $characters[random_int(0, $charactersLength - 1)];
            }
            return $randomString;
        }

        /**
         * Creates a ProductInput object with randomized offer ID and sample attributes.
         *
         * @return ProductInput A new ProductInput object.
         */
        private static function createRandomProduct(): ProductInput
        {
            // Create a price object for shipping. Amount is in micros.
            // e.g., 33,450,000 micros = $33.45 USD
            $price = new Price([
                'amount_micros' => 33450000,
                'currency_code' => 'USD'
            ]);

            // Create shipping details.
            $shipping = new Shipping([
                'price' => $price,
                'country' => 'GB',
                'service' => '1st class post'
            ]);

            $shipping2 = new Shipping([
                'price' => $price,
                'country' => 'FR',
                'service' => '1st class post'
            ]);

            // Create product attributes.
            $attributes = new ProductAttributes([
                'title' => 'A Tale of Two Cities',
                'description' => 'A classic novel about the French Revolution',
                'link' => 'https://exampleWebsite.com/tale-of-two-cities.html',
                'image_link' => 'https://exampleWebsite.com/tale-of-two-cities.jpg',
                'availability' => Availability::IN_STOCK,
                'condition' => Condition::PBNEW,
                'google_product_category' => 'Media > Books',
                'gtins' => ['9780007350896'],
                'shipping' => [$shipping, $shipping2]
            ]);

            // Create the product input object.
            return new ProductInput([
                'content_language' => 'en',
                'feed_label' => 'LABEL',
                'offer_id' => self::generateRandomString(), // Random offer ID for uniqueness
                'product_attributes' => $attributes
            ]);
        }

        /**
         * Inserts multiple product inputs into the specified account and data source asynchronously.
         *
         * @param array $config Authentication and account configuration.
         * @param string $dataSource The target data source name.
         * Format: `accounts/{account}/dataSources/{datasource}`.
         * @return void
         */
        public static function insertProductInputAsyncSample(array $config, string $dataSource): void
        {
            // Enable gRPC connection scaling.
            putenv('GRPC_EXPERIMENTAL_MAX_CONCURRENT_STREAMS_CONNECTION_SCALING=true');
            putenv('GRPC_EXPERIMENTS=subchannel_connection_scaling');

            // Fetches OAuth2 credentials for making API calls.
            $credentials = Authentication::useServiceAccountOrTokenFile();

            // Prepares client options with the fetched credentials.
            $options = ['credentials' => $credentials];

            // Initializes the ProductInputsServiceAsyncClient.
            // This is the key for asynchronous operations.
            $productInputsServiceAsyncClient = new ProductInputsServiceClient($options);

            // Constructs the parent resource string.
            $parent = self::getParent($config['accountId']);

            $promises = [];
            $insertedProductInputs = [];

            print "Sending insert product input requests asynchronously...\n";

            // Create and send 5 insert product input requests asynchronously.
            for ($i = 0; $i < 5; $i++) {
                $productInput = self::createRandomProduct();
                // Create the request object.
                $request = new InsertProductInputRequest([
                    'parent' => $parent,
                    'data_source' => $dataSource,
                    'product_input' => $productInput
                ]);

                // Make the asynchronous API call. This returns a Promise.
                $promise = $productInputsServiceAsyncClient->insertProductInputAsync($request);

                // Attach success and error handlers to the promise.
                $promise->then(
                    function (ProductInput $response) use (&$insertedProductInputs) {
                        // This callback is executed when the promise resolves (success).
                        $insertedProductInputs[] = $response;
                        print "Successfully inserted product with offer ID: " . $response->getOfferId() . "\n";
                    },
                    function (ApiException $e) {
                        // This callback is executed if the promise rejects (failure).
                        echo "ApiException occurred for one of the requests:\n";
                        echo $e;
                    }
                );
                $promises[] = $promise;
            }

            // Wait for all promises to complete (whether resolved or rejected).
            try {
                Utils::settle($promises)->wait();
                print "All asynchronous requests have completed.\n";
                print "Inserted products below:\n";
                foreach ($insertedProductInputs as $p) {
                    print_r($p);
                }
            } catch (\Throwable $e) {
                echo "An error occurred while executing requests: " . $e->getMessage() . "\n";
            } finally {
                // Ensure the client is closed after all promises settle.
                $productInputsServiceAsyncClient->close();
            }
        }

        /**
         * Executes the sample code to insert multiple product inputs.
         *
         * @return void
         */
        public function callSample(): void
        {
            $config = Config::generateConfig();

            // Define the data source that will own the product inputs.
            // IMPORTANT: Replace `<DATA_SOURCE_ID>` with your actual data source ID.
            $dataSourceId = '<DATA_SOURCE_ID>';
            $dataSourceName = sprintf(
                "accounts/%s/dataSources/%s",
                $config['accountId'], $dataSourceId
            );

            // Call the method to insert multiple product inputs asynchronously.
            self::insertProductInputAsyncSample($config, $dataSourceName);
        }
    }

    $sample = new InsertProductInputAsyncSample();
    $sample->callSample();

### Python

    # -*- coding: utf-8 -*-
    # Copyright 2026 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    """A module to insert product inputs asynchronously."""

    import asyncio
    import os
    import random
    import string

    from examples.authentication import configuration
    from examples.authentication import generate_user_credentials
    from google.shopping.merchant_products_v1 import Availability
    from google.shopping.merchant_products_v1 import Condition
    from google.shopping.merchant_products_v1 import InsertProductInputRequest
    from google.shopping.merchant_products_v1 import ProductAttributes
    from google.shopping.merchant_products_v1 import ProductInput
    from google.shopping.merchant_products_v1 import ProductInputsServiceAsyncClient
    from google.shopping.merchant_products_v1 import Shipping
    from google.shopping.type import Price

    # Enable gRPC connection scaling.
    os.environ["GRPC_EXPERIMENTAL_MAX_CONCURRENT_STREAMS_CONNECTION_SCALING"] = (
        "true"
    )
    os.environ["GRPC_EXPERIMENTS"] = "subchannel_connection_scaling"

    # Read merchant account information from the configuration file.
    _ACCOUNT_ID = configuration.Configuration().read_merchant_info()
    # The parent account for the product input.
    # Format: accounts/{account}
    _PARENT = f"accounts/{_ACCOUNT_ID}"


    def _generate_random_string(length: int = 8) -> str:
      """Generates a random string of a given length."""
      characters = string.ascii_letters + string.digits
      return "".join(random.choice(characters) for _ in range(length))


    def _create_random_product() -> ProductInput:
      """Creates a ProductInput with random elements and predefined attributes."""
      price = Price(amount_micros=33450000, currency_code="USD")

      shipping1 = Shipping(price=price, country="GB", service="1st class post")
      shipping2 = Shipping(price=price, country="FR", service="1st class post")

      attributes = ProductAttributes(
          title="A Tale of Two Cities",
          description="A classic novel about the French Revolution",
          link="https://exampleWebsite.com/tale-of-two-cities.html",
          image_link="https://exampleWebsite.com/tale-of-two-cities.jpg",
          availability=Availability.IN_STOCK,
          condition=Condition.NEW,
          google_product_category="Media > Books",
          gtins=["9780007350896"],
          shipping=[shipping1, shipping2],
      )

      return ProductInput(
          content_language="en",
          feed_label="CH",
          offer_id=_generate_random_string(),
          product_attributes=attributes,
      )


    class ClientPool:
      """A simple client pool to distribute requests across multiple clients.

      This implements the Client Pool pattern to enhance throughput for bulk
      operations, mimicking channel pooling.
      """

      def __init__(self, size: int, credentials):
        self._pool = [
            ProductInputsServiceAsyncClient(credentials=credentials)
            for _ in range(size)
        ]
        self._size = size
        self._index = 0

      def get_client(self) -> ProductInputsServiceAsyncClient:
        """Returns the next client in the pool using round-robin."""
        client = self._pool[self._index]
        self._index = (self._index + 1) % self._size
        return client


    async def async_insert_product_input(
        client: ProductInputsServiceAsyncClient, request: InsertProductInputRequest
    ):
      """Inserts product inputs.

      Args:
        client: The ProductInputsServiceAsyncClient to use.
        request: The InsertProductInputRequest to send.

      Returns:
        The response from the insert_product_input request.
      """
      return await client.insert_product_input(request=request)


    async def main():
      # The ID of the data source that will own the product input.
      # This is a placeholder and should be replaced with an actual data source ID.
      datasource_id = "<INSERT_DATA_SOURCE_ID_HERE>"
      data_source_name = f"accounts/{_ACCOUNT_ID}/dataSources/{datasource_id}"

      # Gets OAuth Credentials.
      credentials = generate_user_credentials.main()

      # Creates a client pool with 30 clients to handle concurrent requests.
      # We recommend estimating the number of concurrent requests you'll make,
      # divide by 50 (50% utilization of channel capacity), and set the pool size to
      # that number.
      client_pool = ClientPool(size=30, credentials=credentials)

      tasks = []
      for _ in range(5):
        product_input = _create_random_product()
        request = InsertProductInputRequest(
            parent=_PARENT,
            data_source=data_source_name,
            product_input=product_input,
        )

        # Get a client from the pool and create the async task
        client = client_pool.get_client()
        insert_product_task = asyncio.create_task(
            async_insert_product_input(client, request)
        )
        tasks.append(insert_product_task)

      print("Sending insert product input requests")

      try:
        # Await all tasks to complete concurrently and gather their results
        results = await asyncio.gather(*tasks)
        print("Inserted products below")
        print(results)
      except RuntimeError as e:
        # Catch and print any exceptions that occur during the API calls.
        print(e)


    if __name__ == "__main__":
      asyncio.run(main())