NAV
javascript

Overview

Welcome to the Dealer.com Website Integration API! This API performs common actions on Dealer.com websites, such as loading scripts, adding CSS and inserting markup into common locations.

As this is a browser based API, it is written in JavaScript and has simple data contracts and interfaces.

Requirements

Integrated Partner Program

The use of this API requires that you are in communication with our Integrated Partner Program. To find out more information or to enroll, please contact us at IntegratedPartners@coxautoinc.com or fill out this form.

Once enrolled, you will be provided with an integration key to use when making API calls.

Bootstrapping Your Script

Start by creating a simple JavaScript file that registers with the API and loads additional scripts or styling if necessary. You can leverage the available API capabilities in your script to place content on pages, or include existing integration code if necessary. Your script should eventually be hosted on a content delivery network such as Cloudfront or Akamai, but for development purposes you can host it anywhere.

When you begin development of your script, it's easy to test on any Dealer.com site to see how it will function. To accomplish that, follow these steps:

1.) Load any Dealer.com site where you want to test your integration. If you don't already have a site in mind, a good one to start with is https://www.roimotors.com/.

2.) Open the developer tools in your browser and enter this code in the Console window:

(async APILoader => {
    const API = await APILoader.create();
    API.test('https://www.yourdomain.com/your-javascript-file.js');
})(window.DDC.APILoader);

This will set a cookie in your browser and instruct the site to load your defined script as if the integration was enabled. The code will only load for you in the current browser session so that you are able to iterate and test your code. When it is ready for activation on sites, we can set the integration up in our system and load your script for all users of the site(s) where it should be enabled. The set cookie expires when your browser session expires or you clear your browser cookies.

3.) Add the ?_integrationMode=debug URL parameter to the page you are testing to activate the cookied script for your page view. This will also provide an additional benefit of any logged messages in your script being output to the browser console for troubleshooting and debugging purposes.

Other methods:

Your code should be minimal and perform only actions necessary to bootstrap your integration on to the site. See the Technical Requirements below for more detail on this point.

Technical Requirements

This is an example of an immediately executing function expression:

(async APILoader => {
    const API = await APILoader.create();
    // API.subscribe(...);
    // Your code here
})(window.DDC.APILoader);

Events

By subscribing to events we publish, you can easily obtain the data you need to integrate your solution into the website. These events provide data about the website your integration is loading on and the current page the user is viewing.

There are currently three types of events and each has a consistent data format.

Event Types

Page Event

Example Payload

{
    accountId: 'futuredemodealer',
    siteId: 'futuredemodealer',
    defaultDomain: 'www.roimotors.com',
    indexPage: true,
    searchPage: false,
    detailPage: false,
    franchises: ['honda'],
    design: {
        variationId: 'v9_GLOBAL_0011_V2',
        themekit: 'BLUE_WHITE'
    },
    pageName: 'INDEX',
    layoutType: 'desktop',
    locale: 'en_US'
}
Field Key Example Value Field Format
accountId futuredemodealer String
siteId futuredemodealer String
defaultDomain www.roimotors.com String
indexPage true Boolean
searchPage false Boolean
detailPage false Boolean
design {variationId: 'v9_GLOBAL_0011_V2', themekit: 'BLUE_WHITE'} Object
franchises ['honda'] Array
pageName INDEX String
layoutType desktop String
locale en_US String

Dealership Info Event

Example Payload

{
    dealershipName: 'ROI Motors',
    dealershipAddress1: '1 Howard Street',
    dealershipAddress2: '',
    dealershipCity: 'Burlington',
    dealershipCodes: {
        'dealertrack-post': 'futuredemodealer',
        dtid: '12345',
        'dt-dr-profile': 'futuredemodealer',
        affiliate_promotions: 'avis',
        'at-kbb': '12345678'
    },
    dealershipFranchises: ['honda'],
    dealershipPostalCode: '05401',
    dealershipStateProvince: 'VT',
    dealershipCountry: 'US'
}
Field Key Example Value Field Format
dealershipName ROI Motors String
dealershipAddress1 1 Howard Street String
dealershipAddress2 String
dealershipCity Burlington String
dealershipCodes See Example Object
dealershipFranchises ['honda'] Array
dealershipPostalCode 05401 String
dealershipStateProvince VT String
dealershipCountry US String

Vehicle Event

Example Payload

{
    accountId: 'futuredemodealer',
    address: {
        accountName: 'ROI Motors',
        city: 'Burlington',
        state: 'VT',
        postalCode: '05401',
        country: 'US'
    },
    autodataCaId: '1234567',
    bodyStyle: 'SUV',
    certified: false,
    chromeId: '411601',
    cityFuelEconomy: 28,
    classification: 'primary',
    dealerCodes: {
        'dealertrack-post': 'futuredemodealer',
        dtid: '12345',
        'dt-dr-profile': 'futuredemodealer',
        affiliate_promotions: 'avis',
        'at-kbb': '12345678'
    },
    driveLine: 'Front-wheel Drive',
    engine: 'I-4 cyl',
    engineSize: '1.5L',
    deliveryDateRange: '10/30/2028 - 11/15/2028',
    exteriorColor: 'Crystal Black Pearl',
    finalPrice: 32000,
    fuelType: 'Regular Unleaded',
    highestPrice: 34500,
    highwayFuelEconomy: 34,
    images: [
        'https://pictures.dealer.com/f/futuredemodealer/1182/0686eb936bd7a4905f751493cc28dcb9x.jpg'
    ],
    internetPrice: 33000,
    interiorColor: 'White',
    inventoryType: 'new',
    link: 'https://www.roimotors.com/new/Honda/2020-Honda-Accord-burlington-ab119e0e0a0a00f944d6f3031cd34854.htm',
    make: 'Honda',
    model: 'CR-V',
    modelCode: 'RW1H9LKNW',
    msrp: 34000,
    odometer: 3,
    optionCodes: [
        'ABC',
        '123',
        '321'
    ],
    startingPrice: 34000,
    status: 'live',
    stockNumber: '00100060',
    transmission: 'Variable',
    trim: 'Touring 2WD',
    uuid: 'ab119e0e0a0a00f944d6f3031cd34854',
    vin: '1HGCV1F42JA141468',
    year: 2020
}

Vehicle pricing is a particularly flexible piece of our system. With flexibility often comes complexity, so we provide several fields on the vehicle object to simplify and standardize the pricing information for the API consumer.

startingPrice is the first pricing value displayed on the vehicle. It is often the highest price displayed to the user. In some cases a dealer fee can increase the price later in the pricing stack.

finalPrice is the lowest price displayed on the vehicle not including conditional incentives. This is intended to be the price that the dealership is willing to sell the vehicle at, less any special offers that are only applicable to some customers.

highestPrice is the greater of all of the available pricing field values. It is typically the highest price shown to the user on the page, but in some cases can be higher than what is displayed to the user. This field is deprecated and may be removed at a later date with sufficient notice.

It is recommended that you use startingPrice for the most expensive price displayed to the user and finalPrice for the least expensive price displayed to the user.

You may notice one or more of these fields available in the vehicle object on some sites:

askingPrice

internetPrice

msrp

retailValue

salePrice

All of the above fields are deprecated, meaning they may be removed from the API at a later date. New integrations should not use any of the above fields and should instead rely on startingPrice or finalPrice for the correct vehicle pricing information.

Please note that all of our prices are based on the available marketing context for the current vehicle. The same vehicle can be marketed in multiple contexts and display unique prices in those contexts. The API will always return the contextually appropriate price for the current vehicle.

Field Key Example Value Field Format Status
accountId futuredemodealer String
address {"accountName": "ROI Motors", "city": "Burlington", "state": "VT", "postalCode": "05401", "country": "US"} Object
askingPrice 32000 Integer Deprecated
autodataCaId CAD00CHT27CG0 String
bodyStyle Sedan String
certified false Boolean
chromeId 407123 String
cityFuelEconomy 30 Integer
classification primary String
dealerCodes {"dealertrack-post": "futuredemodealer", "dtid": "12345", "dt-dr-profile": "futuredemodealer"} Object
deliveryDateRange 10/30/2028 - 11/15/2028 String
doors 4-door String
driveLine FWD String
engine I-4 cyl String
engineSize 1.5L String
exteriorColor Platinum White Pearl String
finalPrice 32000 Integer
fuelType Regular Unleaded String
highestPrice 34500 Integer Deprecated
highwayFuelEconomy 38 Integer
images ["https://pictures.dealer.com/1.jpg"] Array
interiorColor Black String
internetPrice 33000 Integer Deprecated
inventoryType new String
link https://www.roimotors.com/new/Honda/2020-Honda-Accord-burlington-ab119e0e0a0a00f944d6f3031cd34854.htm String
make Honda String
model Accord String
modelCode RW1H9LKNW String
msrp 34000 Integer Deprecated
odometer 5 Integer
oemSourcedMerchandisingStatus Reserved String
optionCodes ["ABC", "123", "321"] Array
retailValue 32000 Integer Deprecated
salePrice 32000 Integer Deprecated
startingPrice 34000 Integer
status live String
stockNumber 00180772 String
transmission Variable String
trim EX String
uuid ab119e0e0a0a00f944d6f3031cd34854 String
vin 1HGCV1F42JA141468 String
year 2020 Integer

Event Subscriptions

To receive data for events, you must opt-in to event subscriptions. Each event is named and versioned to ensure it continues to operate in a consistent manner over time. As newer events supercede these, older events may be deprecated but will not be removed if still in use.

Page Load V1

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        API.log(ev);
    });
})(window.DDC.APILoader);

Parameter Name Example Value Parameter Type
event-id page-load-v1 String
callback ev => { API.log(ev); } function

This event passes the standard Page Event object to your callback function.

The page load event is useful to determine the context of the current page. By mapping our siteId or domain field to a customer ID in your system, you can determine the site your code is executing on as well as relevant information about the current page type and user's device type. This approach eliminates the need for configuration in our system beyond simply enabling or disabling your integration for each site.

Dealership Info V1

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('dealership-info-v1', ev => {
        API.log(ev);
    });
})(window.DDC.APILoader);

Parameter Name Example Value Parameter Type
event-id dealership-info-v1 String
callback ev => { API.log(ev); } function

This event passes the standard Dealership Info Event object to your callback function.

The dealership info event is useful if you need to know the name and address of the dealership.

Vehicle Shown V1

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-shown-v1', ev => {
        API.log(ev);
    });
})(window.DDC.APILoader);
Parameter Name Example Value Parameter Type
event-id vehicle-shown-v1 String
callback ev => { API.log(ev); } function

This event passes the standard Vehicle Event object to your callback function.

This event is sent for each vehicle present on the current page. For search results pages, a dozen or more such events may be fired. In the future, dynamic in-page updates to vehicle results will cause potentially hundreds of events to be fired on a single page view as new vehicles are displayed.

On a vehicle deals page, a single event is fired because you are viewing a single vehicle. This is useful for capturing details about the vehicles that a user has viewed, or to take a particular action for a certain type or class of vehicles.

Vehicle Data Updated V1

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-data-updated-v1', data => {
        API.log(data.payload.pageData); // Outputs the Page Data object to the console.
        API.log(data.payload.vehicleData); // Outputs the updated Vehicle Data object to the console.
    });
})(window.DDC.APILoader);
Parameter Name Example Value Parameter Type
event-id vehicle-data-updated-v1 String
callback ev => { API.log(ev); } function

This event passes the standard Page Event object to your callback function as well as the full array of Vehicle Event data objects.

This is useful for coding your application to work with our modern Search Results Page which is a dynamic single page application. When the list of vehicles is refreshed/updated, this event is fired with the Page and Vehicle payload of data for you to use as needed. Any subsequent methods/subscriptions/insertions can occur within this event, to ensure that when vehicles are updated your code immediately executes to decorate those vehicle cards with your content.

API Methods

API.subscribe(name, callback(event))

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('event-name-and-version', ev => {
        API.log(ev);
    });
})(window.DDC.APILoader);

Please see the specific event documentation for more detail on the available events and the data payload sent to your callback function.

API.insertCallToAction(type, intent, setupFunction(meta))

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.insertCallToAction('button', 'value-a-trade', meta => {
        return {
            type: 'default',
            href: 'https://www.yourdomain.com/value-a-trade/?vin=' + meta.vin,
            target: '_blank',
            text: {
                en_US: 'Value My Trade',
                fr_CA: 'Valeur mon commerce'
            }
        }
    });
})(window.DDC.APILoader);

Designed for use within the Page Load subscription, the insertCallToAction method is used to create a call to action (CTA) button for placement on web site inventory items. Rather than generating markup and inserting it into a predefined location, when using this method you specify the CTA type (button is currently the only supported type), an intent (more on this below), and a data object describing the CTA's attributes. The data object follows the same pattern as the object passed into the API.create method which you can use as a reference to see all available config options.

Parameter Name Purpose Field Format
type The type of CTA that should be inserted. String
intent The intention of the CTA you are inserting. String
setupFunction(meta) The payload object for the current vehicle. You return a data object from this method. Object

setupFunction is called for each inventory item presented on the page. The meta field provided is the Vehicle Event payload. You can use the vehicle data to construct an object describing your CTA's attributes, then return it back to the API. With the data returned from setupFunction, the API creates the markup for your button and places it into the CTA area on each vehicle card on the search results page and/or the CTA area on the vehicle details page. The placement depends on how you set up your code and the integration's configuration options for the given site.

This method acts as an event subscription, so as the application displays new vehicles dynamically (a single page application), new events are fired and setupFunction is automatically called for each of those new items. This works well for a basic use case where you want to place content on every item having the target location, or every item matching specific criteria available to you in the setupFunction payload. If you need to execute intermediary code before determining if you need to insert content, such as calling an external service, you should use the insertCallToActionOnce method instead. You should also always use insertCallToActionOnce instead of insertCallToAction when running the code from within a Vehicle Data Updated subscription.

The default location for a CTA is the bottom of the existing CTA list on vehicle search results and details pages.

SRP CTA Location

However, by using this method it enables Dealer.com to reorder the location of your CTA in the list and even map it to "take over" an existing CTA. For example, if a dealer has a custom styled E-Price button and wants your CTA to replace the standard functionality for that feature on their site, we can map your CTA to replace the default functionality. If your code fails to load for some reason, the default behavior of that button still takes effect and ensures that site users can still submit leads, etc.

CTA Type

The current supported type is button, though other types may be added in the future. We currently only support text-based buttons.

CTA Intent

Button Intent is a concept used in the API as a way to categorize the type of functionality your CTA provides. The current supported intent types are:

chat

check-availability

delivery

digital-retailing

eprice

payment-calculator

pre-approval

request-a-quote

reserve-it-now

send-to-phone

social

test-drive

text-us

value-a-trade

window-sticker

If you have a CTA that does not align with one of these types, please let us know and we will consider adding it to the API.

Callback Format

The callback function for this method is used to create a data object describing the CTA you want to place on the page. The format of this object is described below.

Field Name Purpose Example Value(s) Field Format
type The style of CTA that should be inserted. default, primary String
classes A space delimited list of class names to add to the CTA. custom-class1 custom-class2 String
href The URL to access when the CTA is clicked. Must begin with https://. https://www.yourdomain.com/ String
target The link target. _blank, _self String
onclick A function to attach as a click handler to the CTA. window.MyIntegration.clickFunction Function
text An object supplying text for the CTA in one or more languages. en_US is required at minimum. fr_CA is highly recommended. See usage example Object
attributes An object of data- attributes to add to the CTA. String

After creating the callback object, you must then return it for the API to create your CTA. If you do not return anything or return null, no CTA will be inserted for that vehicle item.

API.insertCallToActionOnce(type, intent, setupFunction(meta))

Functional example

(async APILoader => {

    // Initialize an instance of the API
    const API = await APILoader.create();

    // Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
    API.subscribe('vehicle-data-updated-v1', ev => {

        // Collect the VIN for each vehicle on the page in an array.
        API.utils.getAttributeForVehicles('vin').then(vins => {
            API.log("Calling service with these VINs: " + vins.join(','));

            // Fetch data from your endpoint by supplying the list of VINs.
            fetch('https://www.yourdomain.com/api/endpoint-that-returns-json?vins=' + vins.join(','))
            .then(response => {
                return response.json();
            })
            .then(serviceData => {
                // Now that you have your service data, you can determine whether or not to place a CTA for this vehicle on the page.
                API.insertCallToActionOnce('button', 'value-a-trade', meta => {
                    if (serviceData.hasOwnProperty(meta.vin)) {
                        return {
                            type: 'default',
                            classes: 'custom-class1 custom-class2',
                            href: 'https://www.yourdomain.com/value-a-trade/?vin=' + meta.vin,
                            target: '_blank',
                            text: {
                                en_US: 'Value My Trade',
                                fr_CA: 'Valeur mon commerce'
                            }
                        }
                    } else {
                        API.log("Skipping vehicle " + meta.vin + " because it does not have service data.");
                    }
                });
            });
        });
    });
})(window.DDC.APILoader);

You may prefer to only insert a CTA when you are ready, after performing other functions. For example, if you need to make a service call to your system with a list of vehicles to determine which ones have data on your side, and only then add CTAs to supported vehicles. With insertCallToActionOnce, the method behaves as a functional insert which can be chained with other functions, and does not behave as a subscription. With API.insertCallToActionOnce, you will need to invoke it inside of a vehicle-data-updated-v1 subscription so that your code is triggered each time the list of vehicles is loaded on a page rather than only the first time. You should instead use insertCallToAction instead of insertCallToActionOnce when running the code from within a Page Load subscription.

Field Name Purpose Field Format
type The type of CTA that should be inserted. String
intent The intention of the CTA you are inserting. String
setupFunction(meta) The payload object for the current vehicle. Object

API.insertGalleryContent(target, arrayOfObjects)

Usage

(async APILoader => {
    const API = await APILoader.create();

    // Callback function to be called when your HTML slide is rendered.
    const myCallback = (data) => {
        // The `el` property is the top level HTML element where your content will be placed. You may modify this element or insert additional elements into it.
        const { el } = data;

        // The height and width variables give you size information about the content placement location.
        const { width, height } = data;

        el.style.height = height;
        el.style.width = width;

        // The full Vehicle Object is available in the callback data.
        const { vehicle } = data;

        // This destructures a few variables out of that vehicle object for later use.
        // The height and width variables give you size information about the content placement location.
        const { year, make, model, vin } = vehicle;

        el.innerHTML = `<iframe style="height: ${height}px; width: 100%; border: 0;" frameborder="0" src="https://www.myintegration.com/framed-in-content/index.html?year=${vehicle.year}&make=${vehicle.make}&model=${vehicle.model}&vin=${vehicle.vin}" />`;
    }

    // This informs your script when the displayed list of vehicles has changed, or when a Vehicle Details Page has loaded.
    API.subscribe('vehicle-data-updated-v1', async ev => {
        // This obtains a list of VINs for the displayed vehicles.
        const vins = await API.utils.getAttributeForVehicles('vin');

        // With the list of VINs, you could query your service
        // to obtain the dataset of imagery for those vehicles.

        // Code to query your service goes here.
        const imagesData = await queryYourService(vins);

        // With the returned `imagesData`, you could then construct an array of objects in the following format to use when calling `API.insertGalleryContent`.
        const imagesToInsert = [
            {
                vin: '1HGCV1F46LA144134',
                insertMethod: 'insert',
                images: [
                    {
                        type: 'html',
                        position: 'primary',
                        callback: myCallback,
                        src: 'https://via.placeholder.com/530x360.png?text=Primary HTML Content Placeholder'
                    },
                    {
                        type: 'image',
                        position: 'secondary',
                        src: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Secondary Image',
                        thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Secondary Image',
                    },
                    {
                        type: 'image',
                        position: 'last',
                        src: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Last Image',
                        thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Last Image',
                    }
                ]
            },
            {
                vin: '1HGCV1F48LA139453',
                insertMethod: 'replace',
                images: [
                    {
                        type: 'image',
                        position: 'primary',
                        src: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Primary Image',
                        thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Primary Image',
                    },
                    {
                        type: 'html',
                        position: 'secondary',
                        callback: myCallback,
                        src: 'https://via.placeholder.com/530x360.png?text=Secondary HTML Content Placeholder'
                    },
                    {
                        type: 'image',
                        position: 'last',
                        src: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Last Image',
                        thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Last Image',
                    }
                ]
            }
        ];

        // And finally, call insertGalleryContent with the new imagery data.
        API.insertGalleryContent('vehicle-media', imagesToInsert);
    });
})(window.DDC.APILoader);

The insertGalleryContent method allows you to add media to galleries across various pages of Dealer.com sites. The only currently supported target is vehicle-media, which will insert media into the media carousels on Search Results Pages (Images only) and Vehicle Details Pages (Images and HTML slides).

arrayOfObjects is an array of objects describing the media to be inserted. Each object requires a vin field for the target vehicle, an images array with fields describing the images and other HTML content to insert for that vehicle as well as an optional insertMethod. The type field specifies the type of media to be inserted. The available options are image and html. Utilizing vehicle-data-updated-v1 to know when the list of vehicles changes combined with API.utils.getAttributeForVehicles, you can easily obtain a list of the VINs for vehicles shown on the page. With this data, you could query your service to get the dataset of imagery and other content for those vehicles, then construct the array of objects for API.insertGalleryContent. See the example code for more details on this approach.

The insertMethod accepts the following string values:

Value Description
insert This is the default value for inserting media. This method inserts the content into the pre-existing media.
replace This method removes all pre-existing media before inserting the new content.

The images array objects support the following additional attributes:

Name Description
type Expected values are image or html. The html and callback attributes are only used when type is set to html.
src HTTPS url to the image to be inserted.
position Where to insert the image. primary is used for content that should be displayed to the user as soon as it loads -- as long as the user has not explicitly performed an action to look at pre-existing media. secondary is used for content that should be displayed soon, but not replace the pre-existing main image. last is used to append content to the end of an existing gallery.
src HTTPS url to the image to be inserted.
thumbnail HTTPS url to a thumbnail of the image to be inserted. For performance, it is ideal to provide a low resolution thumbnail that can be used for a preview of the inserted image, however, src will be used if thumbnail is not provided.
html HTML markup to render when the user clicks on the placeholder image. This field is optional, as you can also use the callback function to create your markup.
callback A callback function to call when the user clicks on the placeholder image. This allows you to construct the HTML to display dynamically, with relevant data in context.

The callback function you specify is called with a single parameter, which is an object with the following data structure:

Name Description
el {HTMLElement} Initial HTML Element where your markup can be inserted. You can modify this element or insert other content inside of it.
locationId {string} A unique ID for the location where your content will be inserted (either 'carousel' or 'photoswipe'). There are two insert locations on the Vehicle Details page. If your code requires a unique location on the page by ID (some video players may require this), you can add the locationId value to your target ID to make them unique.
width {number} The available width for your target location. This can be useful when constructing iframes to insert, etc.
height {number} The available height for your target location. This can be useful when constructing iframes to insert, etc.
vehicle {object} The Vehicle Object for the current vehicle, so you can use vehicle data when constructing your markup.

API.insert(name, callback(elem, meta))

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.insert('location-name', (elem, meta) => {
        API.log(elem); // The DOM element where markup may be inserted.
        API.log(meta); // The payload object for the current insertion point.
    });
})(window.DDC.APILoader);

Designed for use within the Page Load subscription, the insert method allows you to append markup to specific locations on some pages of Dealer sites. These locations are commonly targeted areas where you may want to place content.

When activated, API.insert will call the callback function you define with the elem and meta parameters. It will call this for each relevant location on the page. For example, if you specify vehicle-media as the location and you are viewing a search results page with 30 vehicles, the callback function you define on API.insert will be called 30 times, once per vehicle, with the relevant location and vehicle data in the payload.

This acts as an event subscription, so as the application displays new vehicles dynamically (a single page application), new events are fired and your callback is immediately called for each of those new items. This works well for a basic use case where you want to place content on every item having the target location, or every item matching specific criteria available to you in the callback payload.

If you need to execute additional code before determining if you wish to insert content, such as calling an external service, you should use the insertOnce method instead in combination with the vehicle-data-updated-v1 event as shown in the example here.

API.insertOnce(name, callback(elem, meta))

Usage

(async APILoader => {
    const API = await APILoader.create();
    // Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
    API.subscribe('vehicle-data-updated-v1', ev => {
        // Insert content into each vehicle location now present on the page.
        API.insertOnce('location-name', (elem, meta) => {
            API.log(elem); // The DOM element where markup may be inserted.
            API.log(meta); // The payload object for the current insertion point.
        });
    });
})(window.DDC.APILoader);

Functional example

(async APILoader => {

    // Initialize an instance of the API
    const API = await APILoader.create();

    // Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
    API.subscribe('vehicle-data-updated-v1', ev => {

        // Collect the VIN for each vehicle on the page in an array.
        API.utils.getAttributeForVehicles('vin').then(vins => {
            API.log("Calling service with these VINs: " + vins.join(','));

            // Fetch data from your endpoint by supplying the list of VINs.
            fetch('https://www.yourdomain.com/api/endpoint-that-returns-json?vins=' + vins.join(','))
            .then(response => {
                return response.json();
            })
            .then(serviceData => {
                // Now that you have your service data, you can determine whether or not to place content for this location on to the page.
                API.insertOnce('vehicle-badge', (elem, meta) => {
                    // Verify my service has data for this vehicle
                    if (serviceData.hasOwnProperty(meta.vin)) {

                        // Create your markup here
                        const div = document.createElement('div');
                        div.innerText = "Hello World!";

                        // Insert your markup into the parent element.
                        API.append(elem, div);
                    } else {
                        API.log("Skipping vehicle " + meta.vin + " because it does not have service data.");
                    }
                });
            });
        });
    });
})(window.DDC.APILoader);

You may prefer to only insert content when you are ready, after performing other functions. For example, if you need to make a service call to your system with a list of vehicles to determine which ones have data on your side, and only then decorate specific vehicles with appropriate content. With insertOnce, the method behaves as a functional insert which can be chained with other functions, and does not behave as a subscription. With API.insertOnce, you will need to invoke it inside of a vehicle-data-updated-v1 subscription so that your code is triggered each time the list of vehicles is loaded on a page rather than only the first time. You should use insert instead of insertOnce when running the code from within a Page Load subscription.

Field Name Purpose Field Format
location The DOM element where the markup should be inserted. Element
callback(elem, meta) The DOM element where the markup should be inserted, and the payload object for the current insertion point. Element, Object

The updateLink method is used to override links on the page where the integration is enabled.

Field Name Purpose Field Format
intent The functionality of the overriding links. String
setupFunction(meta) The payload object for the current page. Object

The current supported intent types are:

payment-search

pre-approval

quote-build

schedule-service

value-a-trade

And, we only support limited attributes of the link to be modified in order to preserve the look and feel of the link. The attributes that can be modified are href, target, onclick, popover and attributes (data-*).

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.updateLink('x-time', meta => {
        return {
            href: 'https://www.yourdomain.com/?account=' + meta.accountId,
            target: '_blank',
        }
    });
})(window.DDC.APILoader);

API.create(type, options)

Create a Button

(async APILoader => {
    const API = await APILoader.create();
    const button = API.create('button', {
        href: 'https://www.google.com/',
        text: {
            'en_US': 'Visit Google', // English
            'fr_CA': 'Visitez Google', // French
            'es_MX': 'Visite Google' // Spanish
        },
        classes: 'btn btn-primary',
        style: 'border: 2px solid #c00',
        attributes: {
            'data-custom-attribute': 'attribute-value',
            'target': '_blank'
        },
        imgSrc: 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
        imgAlt: {
            'en_US': 'Visit Google', // English
            'fr_CA': 'Visitez Google', // French
            'es_MX': 'Visite Google' // Spanish
        },
        imgClasses: 'custom-image-class another-class',
        imgAttributes: {
            'data-image-attribute': 'image-attribute-value'
        },
        onclick: () => {
            window.MyIntegration.activateModalOverlay();
        }
    });
    return button;
})(window.DDC.APILoader);

The above example generates this markup:

<a href="https://www.google.com/" class="btn btn-primary mb-3" data-custom-attribute="attribute-value" target="_blank" style="border: 2px solid rgb(204, 0, 0);">
    <img src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" alt="Visit Google" class="custom-image-class another-class" data-image-attribute="image-attribute-value">
</a>

The create method can be used to generate markup which adheres to our standard practices. This allows you to simply "create a button", for example, without having to know the inner workings of how buttons should be created on different types of sites.

Currently only the "button" type is supported, however other types may be added in the future. Please let us know if there are particular types of elements you want to create so we can work to incorporate your feedback into this API.

The available options for the button type are as follows:

Field Key Example Value Field Format Purpose
href https://www.google.com/ String The link URL. Must begin with https://.
text See functional example. Object The localized button text object.
classes btn btn-primary String Classes to place on the link element.
style border: 2px solid #c00 String Inline styles to place on the link element.
attributes See functional example. Object An object of key/value pairs to add to the link element as attributes.
imgSrc https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png String The URL to the button image.
imgAlt See functional example. Object The localized image alt text object.
imgClasses custom-image-class another-class String Classes to place on the image.
imgAttributes See functional example. Object An object of key/value pairs to add to the image element as attributes.
onclick See functional example. Function The onclick handler to attach to the button.

API.append(targetEl, appendEl)

Usage

(async APILoader => {
    const API = await APILoader.create();
    API.append(targetEl, appendEl);
})(window.DDC.APILoader);

For example, used in conjunction with the insert and the create methods your code might look similar to this:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('target-location-name', (elem, meta) => {
        let lowPrice = Math.round(meta.finalPrice - 1000);
        let highPrice = Math.round(meta.finalPrice + 1000);
        const button = API.create('button', {
            text: 'Search This Price Range',
            href: '/new-inventory/index.htm?internetPrice=' + lowPrice.toString() + '-' + highPrice.toString(),
            classes: 'btn btn-primary',
            style: 'margin-top: 12px;',
            attributes: {
                'target': '_blank'
            }
        })
        API.append(elem, button);
    });
})(window.DDC.APILoader);

When calling the insert method, the goal is to insert some markup into a location on the page. Once you have constructed the element(s) you wish to insert, you may call the append method to complete the process.

API.loadCSS(resourceUrl)

The loadCSS method is a simple way to include an additional CSS stylesheet file required for your integration. The method returns a JavaScript Promise that resolves when the loading is complete. You may then insert markup that depends on that styling to avoid displaying a flash of unstyled content.

Usage

(async APILoader => {
    const API = await APILoader.create();
    // Loads a CSS stylesheet
    API.loadCSS('https://www.company.com/integration.css')
        .then(() => {
            // Code to execute after your stylesheet has loaded.
        });
})(window.DDC.APILoader);

API.loadJS(resourceUrl, attributeMap)

The loadJS method is a simple way to include additional JavaScript files required for your integration. The method returns a JavaScript Promise which you can use to know when file loading is complete, to then run any necessary initialization functions, etc.

Usage

(async APILoader => {
    const API = await APILoader.create();
    // Loads a JavaScript file
    API.loadJS('https://www.company.com/script.js')
        .then(() => {
            // Code to execute after your JavaScript file has loaded.
        });
})(window.DDC.APILoader);

Usage with attributes

In some cases your code may look for data attributes on the inserted script tag. In this case, you may use the following example as a guide to add the necessary attributes:

(async APILoader => {
    const API = await APILoader.create();
    const attributeMap = new Map([
        ['data-license-key', 'abc123'],
        ['id', 'myCompanyScript']
    ]);
    API.loadJS('https://www.company.com/script.js', attributeMap).then(() => {
        // Code to execute after your JavaScript file has loaded.
    });
})(window.DDC.APILoader);

Note that the attributeMap parameter is optional and may be ommitted in most cases.

Insert Locations

Insert locations are areas of pages where you can safely insert markup. These exist in commonly used areas, such as below vehicle pricing on search and detail pages. This section details the current available locations. Please let us know if there are other locations you want to target beyond those listed here.

See the insert documentation for more details on the insert method.

Vehicle Media

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('vehicle-media', (elem, meta) => {
        // This element is positioned below the vehicle image area on vehicle search pages.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.searchPage) {
            API.insert('vehicle-media', (elem, meta) => {
                const button = API.create('button', {
                    text: 'Watch Video',
                    href: 'https://www.providerdomain.com/path/video-player.html?vin=' + meta.vin,
                    classes: 'btn btn-primary dialog',
                    style: 'margin-top: 12px;',
                    attributes: {
                        'target': '_blank'
                    }
                });
                API.append(elem, button);
            });
        }
    });
})(window.DDC.APILoader);

This element is positioned below the vehicle image area on vehicle search pages.

Vehicle Badge

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('vehicle-badge', (elem, meta) => {
        // This element is positioned below the vehicle tech specs area on vehicle search and detail pages.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.searchPage || ev.payload.detailPage) {
            API.insert('vehicle-badge', (elem, meta) => {
                if (meta.inventoryType !== 'used') {
                    return;
                }

                const img = document.createElement('img'),
                    a = document.createElement('a');

                img.src = 'https://static.dealer.com/v8/global/images/franchise/white/logo-certified-carfax-free-lrg.png';
                img.alt = 'Carfax Free Report';

                a.href = 'https://www.carfax.com/VehicleHistory/p/Report.cfx?partner=DLR_3&vin=' + meta.vin;
                a.target = '_blank';
                a.innerHTML = img.outerHTML;

                API.append(elem, a);
            });
        }
    });
})(window.DDC.APILoader);

This element is positioned below the vehicle tech specs area on vehicle search and detail pages.

Vehicle Payments

Usage:

(async APILoader => {
    const API = await APILoader.create();

    const callback = (elem, meta) => {
        // Insert your content here.
    }

    API.insert('vehicle-payments-primary', callback);
    API.insert('vehicle-payments', callback);
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {

        const { searchPage, detailPage } = ev.payload;

        const callback = (elem, meta) => {
            const button = API.create('button', {
                text: 'Vehicle Payments',
                href: '#',
                classes: 'btn btn-primary'
            })
            API.append(elem, button);
        };

        // Only execute the code on search results and vehicle details pages.
        if (searchPage || detailPage) {

            // This element exists only on the "Grid View" layout of the vehicle search page.
            // The content appears below the primary price of the vehicle and above the "Info", "Specials" and "Pricing" tabs.
            // Use this in addition to the `vehicle-payments` location if you want your content to be shown before the user clicks on the pricing tab.
            if (searchPage) {
                API.insert('vehicle-payments-primary', callback);
            }

            // This element is positioned directly below the vehicle pricing area on vehicle search and detail pages.
            // On the "Grid View" layout of the vehicle search page, this content is placed inside the "Pricing" tab, below the vehicle price.
            API.insert('vehicle-payments', callback);
        }
    });
})(window.DDC.APILoader);

This element is positioned directly below the vehicle pricing area on vehicle search and detail pages.

Vehicle Pricing

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('vehicle-pricing', (elem, meta) => {
        // This element is positioned after the vehicle-payments insert location, and is placed below the pricing/incentives area on vehicle search and detail pages.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        // Only execute the code on search results and vehicle details pages.
        if (ev.payload.searchPage || ev.payload.detailPage) {
            API.insert('vehicle-pricing', (elem, meta) => {
                let lowPrice = Math.round(meta.finalPrice - 1000);
                let highPrice = Math.round(meta.finalPrice + 1000);
                const button = API.create('button', {
                    text: 'Search This Price Range',
                    href: '/' + meta.inventoryType + '-inventory/index.htm?internetPrice=' + lowPrice.toString() + '-' + highPrice.toString(),
                    classes: 'btn btn-primary',
                    style: 'margin-top: 12px;'
                })
                API.append(elem, button);
            });
        }
    });
})(window.DDC.APILoader);

This element is positioned after the vehicle-payments insert location, and is placed below the pricing/incentives area on vehicle search and detail pages.

Vehicle Media Container

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('vehicle-media-container', (elem, meta) => {
        // This element is the media gallery container on vehicle details pages.
        // Injecting into this location will replace the media gallery with the elements you insert.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.detailPage) {
            API.insert('vehicle-media-container', (elem, meta) => {
                const containerEl = document.createElement('div');
                containerEl.style = 'background-color: #ff0000; font-size: 30px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
                containerEl.innerHTML = 'Your media container goes here.';
                API.append(elem, containerEl);
            });
        }
    });
})(window.DDC.APILoader);

This element is the media gallery container on vehicle details pages. Injecting into this location will replace the media gallery with the elements you insert.

Primary Banner

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('primary-banner', (elem, meta) => {
        // This element is typically positioned in a prominent location above the vehicle listings on the Search Results Page.
        // On the Details page, it is near the top of the vehicle information, below the media gallery.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.searchPage || ev.payload.detailPage) {
            API.insert('primary-banner', (elem, meta) => {
                const img = document.createElement('img'),
                    a = document.createElement('a');

                img.src = 'https://pictures.dealer.com/d/ddcdemohonda/0217/15bd9bd8ecf0b2a292a91cecb08c595bx.jpg';
                img.alt = 'New 2015 Honda Pilot';
                img.title = 'New 2015 Honda Pilot';

                a.href = '/specials/new.htm';
                a.innerHTML = img.outerHTML;

                API.append(elem, a);
            });
        }
    });
})(window.DDC.APILoader);

This element is positioned in a prominent location above the vehicle listings on the Search Results Page.

On the Details page, it is positioned at the top of the vehicle information, below the media gallery.

You can target either the listings or details page by first subscribing to the page-load-v1 event, then using the event values of payload.searchPage and payload.detailPage to check the page type.

Secondary Content

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('secondary-content', (elem, meta) => {
        // This element is the a secondary content container on vehicle details pages roughly 2/3 of the way down.
        // It may also be added custom to one or more standalone pages on the website.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', (ev) => {
        if (!ev.payload.detailPage) {
            return;
        }

        API.insert('secondary-content', (elem, meta) => {
            const containerEl = document.createElement('div');
            containerEl.style = 'background-color: #ff0000; font-size: 30px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
            containerEl.innerHTML = 'Your secondary content container goes here.';
            API.append(elem, containerEl);
        });
    });
})(window.DDC.APILoader);

By default, this element is roughly 2/3 of the way down on vehicle details pages.

Because this may also be present on one or two standalone pages as custom additions, it is likely you will want to exclusively target Vehicle Details pages by first subscribing to the page-load-v1 event, then using the event value of payload.detailPage to check the page type.

Content

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.insert('content', (elem, meta) => {
        // This element is will only insert on pages created by us for your purposes.
        // It may also be present on pages created for another integration.
    });
})(window.DDC.APILoader);

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.pageName === 'YOUR_LANDING_PAGE') { // Note: Replace 'pageName' with the one we provide at page creation.
            API.insert('content', (elem, meta) => {
                const containerEl = document.createElement('div');
                containerEl.classList = 'bg-neutral-950 text-light';
                containerEl.style = 'font-size: 35px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
                containerEl.innerHTML = 'Your content container goes here.';
                API.append(elem, containerEl);
            });
        }
    });
})(window.DDC.APILoader);

This element will represent the entirety of the empty space between the header and footer on a custom landing page.

The example implementation can be tested here: https://www.roimotors.com/tools/your-landing-page.htm

You will need to subscribe to the page-load-v1 event, then use the event value of payload.pageName to ensure you only target your dedicated landing page.

Once ready for production, you will need to work with us to ensure we provide a landing page with this target.

Utility Methods

In addition to the event based system for working with sites, some utility methods are available to directly access data needed to make decisions in your code. Rather than waiting for a particular event to fire, you can query the information directly at any point in your code to ensure it is in context at the point where you need it. All utility methods return a JavaScript Promise so you can ensure the data is available before proceeding with the rest of your code.

API.utils.getAttributeForVehicles(attribute)

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-data-updated-v1', data => {

        API.log(data.payload.pageData); // Logs the Page Data object
        API.log(data.payload.vehicleData); // Logs the updated Vehicle Data object

        API.utils.getAttributeForVehicles('vin').then(vins => {
            // With the updated list of VINs, you could query your service
            // to determine which VINs are supported by your service before
            // placing relevant content such as buttons or iframes.

            // Code to query your service goes here.

            // Output an array of VINs for vehicles currently displayed on the page.
            API.log(vins);
        });
    })
})(window.DDC.APILoader);

This can be used to obtain an array of attributes for the currently displayed vehicles on a page. For example, passing the attribute 'finalPrice' would return an array of all of the final prices on the page. Passing 'vin' would return an array of vehicle vins. You can pass any of the attributes that are present on the Vehicle Event object and have this return an array.

API.utils.getConfig(testConfig)

Usage:

(async APILoader => {
    const API = await APILoader.create();
    const testConfig = {
        dealerId: "12345",
        showOnSRP: true,
        showOnVDP: false,
        apiKey: "abcd12349876zyxw"
    }
    API.utils.getConfig(testConfig).then(config => {
        // Output the configuration object for your integration (if defined).
        API.log(config);
    });
})(window.DDC.APILoader);

This fetches a JavaScript object of your integration's configuration for the current website and page. Not all integrations have configuration options in our system (aside from enabled/disabled), but if your integration does, you can use this to obtain the configuration data.

testConfig is an optional parameter you can send into the getConfig method. The format should be a JavaScript object. Using the testConfig, you can specify any key/value pairs you need for configuration data for your integration to test various scenarios. The testConfig is only applied when ?_integrationMode=debug is in the URL of the site you are testing, and that site does not already have your integration configured. If the site does have your integration configured, you can override the config specified on our side by also including &_integrationConfig=override in the URL.

API.utils.getDealerData()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.utils.getDealerData().then(dealerData => {
        // Logs the Dealership Info Event object for the current website.
        API.log(dealerData);
    });
})(window.DDC.APILoader);

This fetches the Dealership Info Event object for the current website.

API.utils.getJwtForSite()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.utils.getJwtForSite().then(jwtObject => {
        API.log(jwtObject);
        // Returns a data structure like this:
        // {
        //   jwt: "eyJraWQiOiIya0k0XzIyZoLUUi...KpSUf6vJ8b9Z1NDRcIgv0GrZoiqPhTunw" // String
        // }
    });
})(window.DDC.APILoader);

This fetches an object containing a Java Web Token which can be used to secure/verify the request from our site to your service. This tool offers the capability for you to validate that the request for content from your service originated from our platform and enables you to determine whether or not the content should be served. For more details regarding how to use this with your service, please refer to the JWT usage documentation.

API.utils.getJwtForVehicles()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.utils.getJwtForVehicles().then(jwtObject => {
        API.log(jwtObject);
        // Returns a data structure like this:
        // {
        //   vins: ["1HGCV1F51LA013850", "1HGCV1F16LA029720", "1HGCV1F32LA011829"], // Array
        //   jwt: "eyJraWQiOiIya0k0XzIyZoLUUi...KpSUf6vJ8b9Z1NDRcIgv0GrZoiqPhTunw" // String
        // }
    });
})(window.DDC.APILoader);

This fetches an object containing the array of VINs on the current page and a corresponding Java Web Token which can be used to secure/verify the request from our site to your service. This tool offers the capability for you to validate that the request for content from your service originated from our platform and enables you to determine whether or not the content should be served. For more details regarding how to use this with your service, please refer to the JWT usage documentation.

API.utils.getPageData()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.utils.getPageData().then(pageData => {
        // Outputs the Page Data Object for the current page.
        API.log(pageData);
    });
})(window.DDC.APILoader);

API.utils.getPixallVisitorId()

The getPixallVisitorId method returns the Pixall ID for the current website visitor. Pixall provides a unique identifier for each visitor to aggregate analytics events initiated by that shopper across marketplaces.

Usage

(async APILoader => {
    const API = await APILoader.create();
    const visitorId = await API.utils.getPixallVisitorId();
    // `visitorId` now equals a string value similar to this example: 'e9qAVkIRhnC64Pd4EfdwhB3Z'
})(window.DDC.APILoader);

This fetches the Page Event object for the current website and page.

API.utils.getUnlockedVehicles()

The utility method getUnlockedVehicles returns an array of vehicle UUIDs where the vehicle's pricing has already been unlocked.

This can be useful when paired with the unlockPricing method. When vehicle-data-updated-v1 triggers, a list of currently displayed vehicles is provided. You could gather the list of vehicles, skip the ones which are already unlocked, and then call your service for a smaller subset of vehicles which may need to be unlocked.

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-data-updated-v1', data => {
        const unlockedUuids = await API.utils.getUnlockedVehicles();
    });
})(window.DDC.APILoader);

Another example:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-data-updated-v1', data => {
        // Get the list of unlocked vehicles
        const unlockedUuids = await API.utils.getUnlockedVehicles();

        // Get the list of all vehicles
        const uuids = API.utils.getAttributeForVehicles('uuid');

        // Filter the unlocked UUIDs from the UUIDs.
        const finalUuids = uuids.filter((el) => !unlockedUuids.includes(el));

        // Call your service with the list of finalUuids here, to reduce network overhead.
        // Note: `callToYourService` is just an example here and not an implemented function in the API.
        const uuidsToUnlock = await callToYourService(finalUuids);

        // Unlock vehicles which are not already unlocked, and your service indicates should be unlocked.
        API.utils.unlockPricing(uuidsToUnlock);

    });
})(window.DDC.APILoader);

API.utils.getUrlParams()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    const urlParams = API.utils.getUrlParams(); // Returns the current URL parameters as object attributes, so you can easily access the values.
    API.log(urlParams); // Log the entire object.
    API.log(urlParams.query); // Access just the `query` parameter, for example.
})(window.DDC.APILoader);

NOTE: This method returns synchronously, not as a Promise, because the URL Params are already available and do not require asynchronous behavior for performance.

Running this function at a URL such as this:

https://www.roimotors.com/?query=This%20is%20the%20query&hello=world&foo=bar

Will return the following object:

{ query: "This is the query", hello: "world", foo: "bar" }

API.utils.getVehicleData()

Usage:

(async APILoader => {
    const API = await APILoader.create();
    const config = API.utils.getVehicleData().then(vehicleData => {
        // Outputs the current set of vehicle data.
        API.log(vehicleData);
    });
})(window.DDC.APILoader);

API.utils.unlockPricing(uuids)

The utility method unlockPricing is used to unlock special pricing for specific vehicles on a page.

You can provide the vehicles to unlock by passing an array of vehicle UUIDs to the method. If you would like to unlock all vehicles on the page, omit the uuids parameter from the function call.

The method must be called on each page where you want the vehicles to be unlocked. If a vehicle has been previously unlocked, it will be initially displayed unlocked on a subsequent view of the vehicle. However, when faceting and searching vehicles it is common for a mix of locked and unlocked vehicles to be rendered. Therefore, it is necessary to call the function each time the vehicle-data-updated-v1 event is triggered.

Usage:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('vehicle-data-updated-v1', data => {
        // You could call a service here to determine the list of vehicles to unlock based on the set
        // of vehicles presented on the current view, then construct the array of uuids accordingly.
        const uuids = [
            'f4b436e10a0e09a844d99ec8c92cf29c',
            '1db75afc0a0e09713efa52d69381e2f1',
            '808bb6a00a0e09716fa39a4a8b079353'
        ];
        API.utils.unlockPricing(uuids);
    });
})(window.DDC.APILoader);

JWT Usage Documentation

This API provides a method for loosely coupled content delivery authentication, enabling you to only provide content for valid requests from Dealer.com sites.

We have a sample application to demonstrate how to consume Dealer.com's Integrated Partner Program JWKs, JWTs, and JWSs. How to pronounce JWSs is left as an exercise for the reader.

JWTs

JWTs are used to claim that, at the time the token is issued, Dealer.com rendered a webpage containing specific vehicles, on a specific domain, for a specific account.

An off-platform vendor can validate this token and use that result to decide whether or not to provide paid content.

This claim is then signed with a rotating RSA private key, and the public keys are published on a *.dealer.com domain with HTTPS.

Successful validation of a JWT with one of our public keys ensures that the claims were not tampered with.

The consumer of a JWT is responsible for validating the signature, and can make their own decision about whether or not to honor the token. Dealer.com provides an iat (Issued At) claim, but does not provide an exp (Expiration Time) claim. This means the consumer is free to (and should) define their own limits on token freshness. An implicit expiry is reached when Dealer.com is no longer publishing the public key of the keypair that a particular JWT is signed with.

JWKs

JWKs are published on a Dealer.com domain with HTTPS, which prevents a malicious actor from distributing public keys that would validate JWT signatures not generated by Dealer.com.

JWK public keys are guaranteed to be published well before the corresponding private key is used for generating JWTs.

This means consumers polling for public keys will always be able to validate the signature on a new, valid JWT.

JWKs are hosted on Dealer.com's Content Delivery Network, and therefore are highly available to consumers.

Consumers SHOULD poll and cache JWKs for a short period of time. Consumers SHOULD NOT cache or store keys for an extended period of time to ensure invalidated/expired keys are removed in a timely manner.

JWKs are published as a JWK Set, and each JWT signature contains the kid (Key Id) of the JWK used to verify the signature.

Details

The details in this document are not necessarily final and are subject to change with notice.

At the time of writing:

JWK URL: https://api.web.dealer.com/ipp/keys/jwks.json

JWK Algorith: 2048-bit RSA

JWK Usage:

JWK Consumer Cache Duration: 0-15 minutes, recommend 1 minute.

JWA: RS256

JWT Claims:

Example

The provided example is a spring-boot application that polls the JWK url to fetch public keys as they're published. It has a single endpoint (/jwt/validate/{jwt}) that accepts a JWT as a path parameter and validates the signature and audience.

Retrieving a JWT

A test JWT can be generated for any dealer.com site by calling <domain>/api/ipp/jwt/vehicles?vins= and providing a list of VINs that are valid for that site.

A full example for roimotors.com would be:

curl -Lv https://roimotors.com/api/ipp/jwt/vehicles?vins=1HGCV1F46LA013527,1HGCV1F51LA013850,1HGCV1F52LA011170,1HGCV1F57LA003078

Decode the token at https://jwt.io/ to check that all of the requested VINs were returned. If any VINs are missing, that means that Dealer.com believes the given account doesn't have access to display that inventory.

The Web Integration API provides a convenience method for obtaining the valid VINs and JWT for the current page where your code is executing. This accomplishes the step above and provides you with the JWT which can be passed to your system through a URL parameter on a request which then can be validated on your service with the following validation step.

Validating the JWT

Once you have a valid JWT, you can validate it with the sample application. The JWT is guaranteed to be url-safe.

curl -v localhost:8080/jwt/validate/<jwt>

Debugging / Troubleshooting

Example Implementation:

(async APILoader => {
    const API = await APILoader.create();
    API.subscribe('page-load-v1', ev => {
        if (ev.payload.searchPage) {
            API.log('My integration has loaded and this is a search results page.');
        }
    });
})(window.DDC.APILoader);

When developing an integration, it's helpful to know if the API is doing what you expect and if your code is running successfully. The API intentionally suppresses most errors in regular use cases, however you can opt to view the error messages and API events by adding the following URL parameter to any page of any Dealer.com web site:

?_integrationMode=debug

This will allow the API to output some log messages to your browser's console regarding the actions it is taking.

For more verbose logging, use this combination of URL parameters:

?_integrationMode=debug&_integrationLogLevel=trace

This will output more information about the actions being taken in your script to the browser console so you can more easily see what actions are happening. You can easily filter the browser console to show only these messages by typing Web Integration API into the filter field in your browser.

Additionally, you can instrument your own integration with log messages to validate that the expected code is being executed and desired code paths are being followed.

To log messages from within your integration code, simply use the API.log method the same way you would use console.log. Those messages will be output to your console when the above URL parameter is present and will be suppressed when it is not.

Sample Code

The sample code here is provided as a starting point for how to accomplish tasks that are related to integrations but that may not fit squarely in the responsibility of the Integration API. Code may need to be modified to fit your integration's use case.

Resizing an iframe Based on Content Changes

iframe Code:

document.body.style.overflow = 'hidden';

let sendResizeMessage = () => {
    window.parent.postMessage({
        type: 'IFRAME_HEIGHT_RESIZE',
        target: 'test-integration-iframe', // Note: Replace 'test-integration-frame' with your actual iframe identifier.
        frameHeight: document.body.offsetHeight + 10 /* a little extra for good measure */
    }, '*');
}

if (window.ResizeObserver) {
    const heightObserver = new ResizeObserver(() => {
        sendResizeMessage();
    });
    heightObserver.observe(document.body);
}

Integration Code:

(async APILoader => {
    const API = await APILoader.create();

    API.insert('content', (elem, meta) => {
        const iframeElem = document.createElement('iframe');
        iframeElem.src = 'https://www.yourdomain.com/path-to-iframe.htm';
        iframeElem.classList.add('test-integration-iframe'); // Note: Replace 'test-integration-frame' with your actual iframe identifier.
        API.append(elem, iframeElem);
    });

    let setIframeHeight = e => {
        if (e.origin !== 'https://www.yourdomain.com') {
            // You should ALWAYS verify the origin matches the third party domain
            // the iframe is loaded from. For more information, see:
            // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns
            return;
        }

        if (e.data.type === 'IFRAME_HEIGHT_RESIZE' && e.data.frameHeight && e.data.target) {
            const iframes = document.getElementsByClassName(e.data.target);
            if (iframes.length === 1) {
                iframes[0].style.height = e.data.frameHeight + 'px';
            }
        }
    }

    window.addEventListener('message', setIframeHeight, false);
})(window.DDC.APILoader);

An integration may want to insert an iframe that resizes as its contents change. One possible way to accomplish this is for the iframe and the integration to work together as shown in the sample code from the pane on the right side of this page. You can see the sample code running here.

IFrame Responsibilities:

Integration Responsibilities(on the outer page):

Considerations