Use the Import API

Practice working with the Import API and SDK to create and update a Customer.

  • After completing this page, you should be able to:

    • Use the Import API to create and update a Customer in Composable Commerce.
  • On this page, you will work through an example use case to get a better feel for using the Import API. We will use an SDK (you can use either the Java or TypeScript SDK) to import new Customers into your Composable Commerce Project with the Import API.

    The example use case will be similar to the Customer example given at the beginning of this module, except you will start with importing just one Customer before attempting to import 100,000 of them! However, we will practice making both a create and an update request, so you can see how both work.

    We will attempt to execute these two main tasks in our example:

    • Import the Customer knowing that it does not yet exist. Then we will check if the Customer has been created or not.
    • Modify the Customer. We will now re-run our code and hence, re-use the Import API to import the Customer knowing that it now exists. Then we will check if the Customer has been updated or not.

    Here is the Customer data for our exercise:

    {
    "key": "imported-customer-01",
    "firstName": "Sam",
    "lastName": "Davies",
    "email": "sdavies@example.com",
    "password": "secret123",
    "addresses": [{ "key": "imported-customer-01-address", "country": "DE" }]
    }

    Remember that the Import API uses key for identifying and referencing resources. That’s why the example above not only has a key for the Customer but also for each address.

    Prepare an Import API Client

    If you’ve been working through the Developer Essentials learning path sequentially, you would have already prepared your Composable Commerce Project and environment. If not, you should do the following before continuing with the practical exercise:

    Create an API Client in the Merchant Center

    For this exercise, you should create a new API Client with the following scopes: manage_import_containers, manage_customers, and manage_customer_groups. It is best practice to not give API Clients more scopes than necessary, as it reduces the chance of accidents and misuse. Remember to copy or download the API Client credentials, as you will be unable to access them once you leave the API Client details page.

    Create an import client in your SDK

    Now, let's create an import client for the SDK. The following Java and TypeScript code will create a client that uses the Import API. Notice that it is very similar to what we have already done in the Prepare your work environment module, the main difference here is that the Import API domain is slightly different:

    Java

    import com.commercetools.importapi.client.ProjectApiRoot;
    import com.commercetools.importapi.defaultconfig.ImportApiRootBuilder;
    import com.commercetools.importapi.defaultconfig.ServiceRegion;
    import io.vrap.rmf.base.client.oauth2.*;
    public class ImportClientBuilder {
    public static ProjectApiRoot createImportApiClient() {
    final ProjectApiRoot importApiRoot = ImportApiRootBuilder.of()
    .defaultClient(ClientCredentials.of()
    .withClientId("{clientId}")
    .withClientSecret("{clientSecret}")
    .withScopes("{scopes}")
    .build(),ServiceRegion.GCP_EUROPE_WEST1)
    .build("{projectKey}");
    return importApiRoot;
    }
    }

    TypeScript

    You will also need to add the importHost to your environment variables, as the base URL used to access the Import API is different from the HTTP API. The variable name should be CTP_IMPORT_API_URL and the value should be the host that matches your project Region. See Hosts and authorization for further information.

    import fetch from 'node-fetch';
    import { ClientBuilder } from '@commercetools/ts-client';
    import 'dotenv/config';
    import { createApiBuilderFromCtpClient } from '@commercetools/importapi-sdk';
    // --- Configuration ---
    const projectKey = process.env.CTP_PROJECT_KEY;
    const clientId = process.env.CTP_CLIENT_ID;
    const clientSecret = process.env.CTP_CLIENT_SECRET;
    const authUrl = process.env.CTP_AUTH_URL;
    const importApiUrl = process.env.CTP_IMPORT_API_URL;
    const scopes = [];
    // --- Middleware Functions ---
    // Function for custom header middleware
    function createCustomHeaderMiddleware() {
    return (next) => (request) => {
    return next({
    ...request,
    headers: {
    ...request.headers,
    'accept-language': 'en-AU',
    },
    });
    };
    }
    // Function for custom logger middleware
    const customLoggerMiddleware = {
    logLevel: 'debug',
    httpMethods: ['POST', 'GET'],
    maskSensitiveData: true,
    logger: (method, ...args) => {
    console.log(`[CUSTOM LOGGER] ${method}`, ...args);
    },
    };
    // --- Middleware Options ---
    // Auth Middleware Options
    const authMiddlewareOptions = {
    host: authUrl,
    projectKey: projectKey,
    credentials: { clientId, clientSecret },
    scopes: scopes,
    httpClient: fetch,
    };
    // Http Middleware Options
    const httpMiddlewareOptions = {
    host: importApiUrl,
    includeResponseHeaders: true,
    maskSensitiveHeaderData: true,
    includeOriginalRequest: true,
    includeRequestInErrorResponse: true,
    enableRetry: true,
    retryConfig: {
    maxRetries: 3,
    retryDelay: 200,
    backoff: false,
    retryCodes: [500, 503],
    },
    httpClient: fetch,
    };
    // Correlation ID Middleware Options
    const correlationIdMiddlewareOptions = {
    // Replace with your own UUID, ULID, or a generator function. Do not use this example in production!
    generate: () => 'cd260fc9-c575-4ba3-8789-cc4c9980ee4e',
    };
    // Concurrent Modification Middleware Options
    const concurrentModificationMiddlewareOptions = {
    concurrentModificationHandlerFn: (version, request) => {
    console.log(`Concurrent modification error, retry with version ${version}`);
    request.body.version = version;
    return JSON.stringify(request.body);
    },
    };
    // --- Optional Telemetry Middleware (Commented Out) ---
    /*
    const telemetryOptions = {
    createTelemetryMiddleware,
    apm: () => typeof require('newrelic'),
    tracer: () => typeof require('/absolute-path-to-a-tracer-module'),
    };
    */
    // --- Client Creation ---
    const client = new ClientBuilder()
    .withProjectKey(projectKey)
    .withClientCredentialsFlow(authMiddlewareOptions)
    .withLoggerMiddleware(customLoggerMiddleware)
    .withCorrelationIdMiddleware(correlationIdMiddlewareOptions)
    .withMiddleware(createCustomHeaderMiddleware())
    .withHttpMiddleware(httpMiddlewareOptions)
    .withConcurrentModificationMiddleware(concurrentModificationMiddlewareOptions)
    // .withTelemetryMiddleware(telemetryOptions)
    .build();
    // --- API Root Creation ---
    const apiRootImport = createApiBuilderFromCtpClient(client).withProjectKeyValue(
    { projectKey }
    );
    export { apiRootImport };

    Now we have a client for importing data, and we are ready to import our Customer. As defined in our scopes we can use it to create and monitor containers, import Customers, and import Customer Groups. This will allow us to complete our following tasks.

    We will follow the Import API workflow as we continue, so it would be a good idea to have it at hand for easy referral.

    Workflow step 1: Create an Import Container

    We will first create an Import Container that can process the import of our Customer. This is a simple create operation. As you know, Composable Commerce requires you to POST an ImportContainerDraft. For the key, it is good practice to use something descriptive, and we will use myProject-customer-container-learn in the following examples.

    Here’s our code that allows us to do this:

    ProjectApiRoot importApiRoot = ImportClientBuilder.createImportApiClient();
    String containerKey = "myProject-customer-container-learn";
    importApiRoot
    .importContainers()
    .post(
    ImportContainerDraftBuilder
    .of()
    .key(containerKey)
    .resourceType(ImportResourceType.CUSTOMER)
    .build()
    )
    .executeBlocking();
    importApiRoot.close();

    Nice work! Let's now verify that the Import Container exists:

    String containerKey = "myProject-customer-container-learn";
    List<ImportContainer> containerList = importApiRoot
    .importContainers()
    .get()
    .executeBlocking()
    .getBody()
    .getResults();
    for (ImportContainer ic : containerList) {
    if (ic.getKey().equals(containerKey)) System.out.println(
    "Our container (" + containerKey + ") is ready!"
    );
    }

    If successful, you will get a verification response such as the following:

    Import container verified.

    You can also view the Import Container in the Merchant Center by going to Operations > Import > Import logs.

    Import container verified in Merchant Center.

    Great job! Let’s move on to the next step.

    Workflow Step 2: Import the Customer (CREATE)

    Remember the Customer that was mentioned in our use case? Now that our Import Container is ready, we can finally import our Customer into the Project.

    After taking another glance at the workflow, our next step is to create and submit a CustomerImportRequest:

    importApiRoot
    .customers()
    .importContainers()
    .withImportContainerKeyValue(containerKey)
    .post(
    CustomerImportRequestBuilder
    .of()
    .resources(
    CustomerImportBuilder
    .of()
    .key("imported-customer-01")
    .firstName("Sam")
    .lastName("Davis")
    .email("sdavis@example.com")
    .password("secret123")
    .addresses(
    CustomerAddressBuilder
    .of()
    .key("imported-customer-01-address")
    .country("DE")
    .build()
    )
    .build()
    )
    .build()
    )
    .executeBlocking();
    importApiRoot.close();

    After submitting the Import Request, you can view the status of your Import Container and Import Operations in the Merchant Center by going to Operations > Import > Import logs.

    Import status is processing.

    If the operation is successful, the Status will change to Successfully completed.

    Import status is successful.

    We can also see our new Customer by going to Customers > Customer list:

    Customer list screen showing new Customer.

    Workflow Step 3: Import the Customer (UPDATE)

    Our new Customer is looking good, but it looks like we have made a small mistake. If you look again at the JSON data for our Customer at the beginning of our use case, you might notice that our Customer’s last name is actually “Davies” and not “Davis”. We need to correct that mistake as soon as possible! As mentioned previously, we don’t have to use different requests for creating and updating resources.

    In that case all we have to do is update the code we just used to create the Customer with the accurate information:

    "lastName": "Davies",
    "email": "sdavies@example.com",

    Then, we can run the same code from Step 2 again. No other change is required!

    After a few moments we should see our Customer updated in the Merchant Center:

    Customer list screen showing newly updated customer.

    Workflow Step 4: Import Status Monitoring

    Of course the Merchant Center is not the only way of checking the status of your Import Containers and operations. Let’s see how we can use the SDK programmatically to automate the monitoring.

    After submitting an ImportRequest, you can query the Import Container to receive a list of its Import Operations and their statuses. The code would look like this:

    List<ImportOperation> importOperations = importApiRoot
    .importContainers()
    .withImportContainerKeyValue(containerKey)
    .importOperations()
    .get()
    .executeBlocking()
    .getBody()
    .getResults();
    for (ImportOperation io : importOperations) {
    System.out.println(
    "Resource: " + io.getResourceKey() + " State: " + io.getState()
    );
    }
    apiRoot.close();

    The following is an example response to the request we have just made, with the state of each operation highlighted:

    {
    "limit": 20,
    "offset": 0,
    "count": 3,
    "total": 3,
    "results": [
    {
    "version": 2,
    "importContainerKey": "myProject-customer-container-learn",
    "resourceKey": "imported-customer-01",
    "id": "23d3bd12-47d5-44af-aa17-774f02f0cbb7",
    "state": "imported",
    "resourceVersion": 5,
    "createdAt": "2023-12-07T14:28:25.159Z",
    "lastModifiedAt": "2023-12-07T14:29:05.487Z",
    "expiresAt": "2023-12-09T14:28:25.159Z"
    },
    {
    "version": 1,
    "importContainerKey": "myProject-customer-container-learn",
    "resourceKey": "imported-customer-02",
    "id": "3be2c856-3278-41ac-ac47-a7e1bcfff03d",
    "state": "processing",
    "resourceVersion": 5,
    "createdAt": "2023-12-07T14:28:26.143Z",
    "lastModifiedAt": "2023-12-07T14:29:05.421Z",
    "expiresAt": "2023-12-09T14:28:25.159Z"
    },
    {
    "version": 2,
    "importContainerKey": "myProject-customer-container-learn",
    "resourceKey": "imported-customer-03",
    "id": "2a528594-4c01-48cb-9ef9-6cc3620978d1",
    "state": "unresolved",
    "resourceVersion": 5,
    "createdAt": "2023-12-07T14:28:25.111Z",
    "lastModifiedAt": "2023-12-07T14:29:05.487Z",
    "expiresAt": "2023-12-09T14:28:25.112Z"
    }
    ]
    }

    Processing states

    In the result you should see that your Import Operations are either in the processing or imported state. More processing states are available that you should be aware of, like unresolved, as seen in the example.

    What does unresolved mean and how can it be fixed? Let’s find out in the next example where we will create another Customer using the Import API that triggers the unresolved state:

    importApiRoot
    .customers()
    .importContainers()
    .withImportContainerKeyValue(containerKey)
    .post(
    CustomerImportRequestBuilder
    .of()
    .resources(
    CustomerImportBuilder
    .of()
    .key("imported-customer-02")
    .firstName("Kate")
    .lastName("Holmes")
    .email("kholmes@example.com")
    .password("secret123")
    .customerGroup(
    CustomerGroupKeyReferenceBuilder
    .of()
    .key("imported-customers")
    .build()
    )
    .addresses(
    CustomerAddressBuilder
    .of()
    .key("imported-customer-02-address")
    .country("DE")
    .build()
    )
    .build()
    )
    .build()
    )
    .executeBlocking();

    After running this code and waiting for the import request to be processed, run the script to get the Container status or check the Container status in the Merchant Center. You should see the status of unresolved with typeId: "customer-group" and key: "imported-customers".

    This means that the Import Request contains key references to resources which do not exist. In this particular case the Project is missing a Customer Group with a key of imported-customers.

    To rectify this, we need to create a Customer Group with the key imported-customers or the Import Operation will be deleted 48 hours after the time of its creation.

    Checking the status of Import Containers

    What if you would like to see an overall view of how many Import Operations (and in which state) your Import Container has? Well, the Import Summary does just that!

    An Import Summary describes the status of an Import Container by the number of resources in each processing state. It can be used to monitor the progress of import per Import Container. You can use the following code to monitor the status of the Import Container:

    OperationStates operationStates = importApiRoot
    .importContainers()
    .withImportContainerKeyValue(containerKey)
    .importSummaries()
    .get()
    .executeBlocking()
    .getBody()
    .getStates();
    System.out.println(
    "Processing: " + operationStates.getProcessing() +
    "\nValidation failed: " + operationStates.getValidationFailed() +
    "\nUnresolved: " + operationStates.getUnresolved() +
    "\nWaiting for MasterVariant: " + operationStates.getWaitForMasterVariant() +
    "\nImported: " + operationStates.getImported() +
    "\nRejected: " + operationStates.getRejected() +
    );

    Best practices

    As you have learned in this module, the Import API is a very powerful and flexible tool for bulk adding and updating resources. However, to best use the Import API it is important to be aware of limitations and best practices.

    To get the most out of the Import API it’s recommended that you familiarize yourself with the Import API best practices. Pay particular attention to:

    • How to organize your Import Containers so that you can monitor individual behavior and learn for adjusting later imports.
    • How to adjust the size of your import operations per container.

    Test your knowledge