---
title: REST API enricher
url: https://documentation.cluedin.net/govern/enrichment/rest-api
content_type: reference
section: Govern & improve > Enrichment
---

# REST API enricher

> reference: Facts, options, APIs.

This article explains how to add the REST API enricher. The purpose of this enricher is to retrieve data from a wide variety of endpoints. It provides a flexible way to integrate diverse data sources into your golden records.

> **Important:** This enricher is intended for users who are comfortable **writing JavaScript code**, as scripting is required to customize request and response handling.

## Add REST API enricher

The REST API enricher requires the URL of an external endpoint to retrieve data. You can use JavaScript to customize request construction and response processing for precise control over data extraction. You can find some examples of JavaScript code in the [Sample scripts V1](#sample-v1) and [Sample scripts V2](#sample-v2) section of this page.

**To add REST API enricher**

1. On the navigation pane, go to **Preparation** > **Enrich**. Then, select **Add Enricher**.

1. On the **Choose Enricher** tab, select **REST API**, and then select **Next**.

   ![Add Enricher dialog on the Choose enricher tab, with REST API selected above Vat Layer and Web](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api-enricher-1.png)

1. On the **Configure** tab, provide the following details:

   1. **Accepted Business Domain** – enter the business domain to define which golden records will be enriched.

   1. **Version** – select the version of REST API enricher.

   1. **Method** – available only in V1. Select the HTTP method (GET or POST) that will be used to retrieve data.

   1. **URL** – available only in V1. Enter the URL of the external endpoint. This can be:

      - An external API endpoint (for example, DuckDuckGo) that returns data directly to the enricher, where it is processed using the **Process Response Script**.

      - A service endpoint (for example, Power Automate, Jupyter Notebook, or a Cloud Function) that acts as a wrapper for external APIs. These endpoints can call external APIs, process the responses internally, and return the final result to the enricher.

      You can use placeholders in **URL** to dynamically insert values at runtime:

      - `{APIKey}` – this placeholder will be replaced by the actual API key value provided in **API Key**.

      - `{Vocabulary:vocabularykey}` – this placeholder will be replaced with the value of the specified vocabulary key from the golden record. For example, if the URL contains `{Vocabulary:trainingcontact.country}` and the golden record contains `"trainingcontact.country": "Norway"`, the placeholder will be replaced with `Norway`.

      ![REST API enricher Configure tab with Accepted Business Domain Organization, Version V1 (legacy), Method GET, and a URL with a vocabulary key placeholder](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api-enricher-2.png)

   1. **API Key** – enter the API key required to authenticate with the endpoint, if applicable. Provide this only if the API requires a key for access. Use `{APIKey}` in **URL (V1)**, **Process Request Script (V1)** and **Process Script (V2)** and the place holder will be replaced by actual API Key value provided.

   1. **Headers** – available only in V1. Enter any HTTP headers needed to call the endpoint. Enter one header per line in the format `Header-Name=value`.

   1. **Vocabulary and Properties** – enter the vocabulary keys and properties to include in the request payload, with one entry per line. These will be passed to the endpoint to retrieve the relevant data.

   1. **Process Request Script** – available only in V1. Provide the JavaScript code used to construct or modify the request before it is sent to the external endpoint. You can find an example of the process request script in the [Sample scripts](#sample-process-request-script) section of this page.

   1. **Process Response Script** – available only in V1. Provide the JavaScript code used to process the response returned from the external endpoint. This script is required to transform the API response into a format that the enricher can understand and use. You can find an example of the process response script in the [Sample scripts](#sample-process-response-script) section of this page.

   1. **Process Script** – available only in V2. Provide the JavaScript code used to request and process the response returned from the external endpoint. This script is required to transform the API response into a format that the enricher can understand and use. You can find an example of the process response script in the [Sample scripts](#sample-process-script) section of this page.

   1. **Include Confidence Score** – decide whether the results will include a confidence score, which can be used during data processing.

1. Select **Test Connection** to make sure the enricher is properly configured, and then select **Add**.

   The REST API enricher is added and has an active status. This means that it will enrich relevant golden records during processing or when you trigger external enrichment.

After the REST API enricher is added, you can modify its details:

- **Settings** – add a user-friendly display name, select the description for data coming from the enricher, and define the source quality for determining the winning values.

- **Authentication** – modify the details you provided while configuring the enricher.

## View properties added by REST API enricher

You can find the properties added to golden records from the REST API enricher on the **Properties** page. The vocabulary keys added to golden records by the REST API enricher are grouped under **No Source** source type.

![Properties page of the Google golden record filtered to No Source, showing the country.officialName property with the value Kingdom of Norway](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api-enricher-3.png)

For a more detailed information about the changes made to a golden record by the REST API enricher, check the corresponding data part on the **History** page.

![History page of the Google golden record with the REST API data part highlighted above the File Data Source data part](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api-enricher-4.png)

## Sample (V1)

This section provides some sample configuration and scripts that you can use to configure the V1 REST API enricher.

### Sample process request script

```javascript
// sample request that can be accessed in the script
//  let request = {
//     ApiKey: "testApiKey",
//     Url: "testUrl",
//     Headers: [
//        { Key: "TestHeader", Value: "123"}
//     ],
//     Body: {
//        Properties: [{Key: "organization.name", Value: "CluedIn"}]
//     }
//   };

// Add Header
request.AddHeader("TestHeader2", "456");

let detailsArray = [];
request.Body.Properties.forEach((x) => {
  detailsArray.push(x.Value);
});

// Update Body that will be sent to external api
request.Body = {
  names: detailsArray,
};

//modified request
//  let request = {
//      ApiKey: "testArray",
//      Url: "testUrl",
//      Headers: [
//          { Key: "TestHeader", Value: "123"},
//          { Key: "TestHeader2", Value: "456"}
//      ],
//      Body: {
//          name: ["CluedIn"]
//      }
//  };
```

### Sample process response script

```javascript
//  Sample response that returned from external api to enricher
//    const response = {
//        HttpStatus: "OK".
//        Content: "{\"fullName\": \"CluedIn ApS\"}",
//        Headers: [
//            { Key: "Connection", Value: "keep-alive"},
//            { Key: "Content-Type, Value "application/x-javascript"}",
//        ],
//    };

let parsedContent = JSON.parse(response.Content);
let newContent = {
  "organization.fullName": parsedContent.fullName,
};

// Data must be returned in the following format to be processed by enricher
// Score can be assigned with external api response
response.Content = JSON.stringify([{ Data: newContent, Score: 0 }]);

// Sample response after executing the script
//    const response = {
//        HttpStatus: "OK",
//        Content: "[{\"Data\":{\"organization.fullName\":\"CluedIn Aps\"}, \"Score\":0}]",
//        ContentType: "application/x-javascript",
//        Headers: [
//            { Key: "Connection", Value: "keep-alive"},
//            { Key: "Content-Type", Value: "application/x-javscript",
//        ],
```

### Accessing and modifying request and response objects in process request and response scripts

| Name              | Type         | Description                                                                                         |
| ----------------- | ------------ | --------------------------------------------------------------------------------------------------- |
| `request`         | `Request`    | The current HTTP request object that can be modified during script execution.                       |
| `originalRequest` | `Request`    | The original, unmodified HTTP request for reference purposes. Available in Process Response Script. |
| `response`        | `Response`   | The response from external endpoint that can be populated or modified by the script.                |
| `vocabularies`    | `Property[]` | The list of vocabularies and properties from enricher configuration.                                |

**Request**

| Property  | Type       | Description                                   |
| --------- | ---------- | --------------------------------------------- |
| `Method`  | `string`   | The HTTP request method (e.g. `GET`, `POST`). |
| `Url`     | `string`   | The target URL for the HTTP request.          |
| `Headers` | `Header[]` | List of HTTP headers included in the request. |
| `ApiKey`  | `string`   | API key used for authenticating the request.  |
| `Body`    | `object`   | The body content of the HTTP request.         |

| Method      | Signature                                     | Description                       |
| ----------- | --------------------------------------------- | --------------------------------- |
| `addHeader` | `addHeader(key: string, value: string): void` | Adds a new header to the request. |

**Response**

| Property      | Type       | Description                                                                                                                                 |
| ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `HttpStatus`  | `string`   | The HTTP status code of the response (e.g. `200 OK`).                                                                                       |
| `Content`     | `string`   | The raw response body content. Must be converted into a string value with the **Result** type when returned to the enricher for processing. |
| `ContentType` | `string`   | The response content type (e.g. `application/json`).                                                                                        |
| `Headers`     | `Header[]` | List of HTTP headers included in the response.                                                                                              |

**Header**

| Property | Type     | Description   |
| -------- | -------- | ------------- |
| `Key`    | `string` | Header name.  |
| `Value`  | `string` | Header value. |

**Property**

| Property | Type     | Description     |
| -------- | -------- | --------------- |
| `Key`    | `string` | Property name.  |
| `Value`  | `string` | Property value. |

**Result**

| Property | Type     | Description                                                                       |
| -------- | -------- | --------------------------------------------------------------------------------- |
| `Data`   | `object` | Key-value data extracted from the response. Each key represents a vocabulary key. |
| `Score`  | `number` | A numeric score indicating the relevance or confidence of the results.            |

### Azure OpenAI

This script takes an organization's name and sends a request to an Azure OpenAI deployment using GPT-5.1 or higher, asking it to translate the name into Japanese. Then, it ensures the model responds in a defined JSON format and parses and returns the result in a structure compatible with the enricher. Note that you can use the [Azure Open AI enricher](https://documentation.cluedin.net/govern/enrichment/artificial-intelligence.md) for the same task.

**Method:** POST

**URL**

```
https://<your-resource-name>.openai.azure.com/openai/deployments/<your-gpt-5.1-or-higher-deployment-name>/chat/completions?api-version=<api-version>
```

**Vocabulary key and properties**

```
organization.name
```

**Headers**

```
api-key={APIKey}
```

**Process request script**

```javascript
let prompt = String.raw`Please get {organization.japaneseNameRestApi} by translating {Vocabulary:organization.name} into Japanese.

                    Response in JSON using the following template
                    ###
                    {
                        "organization.japaneseNameRestApi": ""
                    }
                    ###
  `;

request.Body.Properties.forEach((x) => {
  prompt = prompt.replace("{Vocabulary:" + x.Key + "}", x.Value);
});

request.Body = {
  messages: [{ role: "user", content: prompt }],
  temperature: 0,
};
```

**Process response script**

```javascript
let parsedContent = JSON.parse(response.Content);
let content = parsedContent.choices[0].message.content.trimEnd();
response.Content = JSON.stringify([{ Data: JSON.parse(content), Score: 0 }]);
log(JSON.stringify(response));
```

### DuckDuckGo

This script handles the JSON response returned by a DuckDuckGo API call. The main purpose is to extract useful information from the API response and transform it into a format usable by the enricher.

**Method:** GET

**URL**

```
https://api.duckduckgo.com?q={Vocabulary:organization.name}&format=json
```

**Process response script**

```javascript
function extractStringValues(obj, dict = new Map()) {
  for (const [key, value] of Object.entries(obj)) {
    if (value && typeof value !== "object" && !Array.isArray(value)) {
      dict.set("duckDuckGo.organization." + key, value.toString());
    }
  }
  return dict;
}
function extractInfobox(content, dict = new Map()) {
  content?.forEach((x) => {
    dict.set("duckDuckGo.organization.infobox." + x.label, x.value);
  });
}
function extractRelatedTopics(relatedTopics, dict = new Map()) {
  for (let i = 0; i < relatedTopics.length; i++) {
    dict.set(
      "duckDuckGo.organization.relatedTopics" + i + ".firstUrl",
      relatedTopics[i]?.FirstURL
    );
    dict.set(
      "duckDuckGo.organization.relatedTopics" + i + ".text",
      relatedTopics[i]?.Text
    );
    dict.set(
      "duckDuckGo.organization.relatedTopics" + i + ".icon",
      relatedTopics[i]?.Icon?.Url
    );
  }
}
try {
  // Update the content here
  let parsedContent = JSON.parse(response.Content);
  let vocabularyKeysWithValue = extractStringValues(parsedContent);
  extractInfobox(parsedContent.Infobox?.content, vocabularyKeysWithValue);
  extractRelatedTopics(parsedContent.RelatedTopics, vocabularyKeysWithValue);
  response.Content = JSON.stringify([
    { Data: Object.fromEntries(vocabularyKeysWithValue), Score: 0 },
  ]);
  log(response);
} catch (error) {
  const errorDetails = {
    name: error?.name,
    message: error?.message,
    stack: error?.stack,
  };
  log("Error caught:" + JSON.stringify(errorDetails));
}
```

### REST Countries

This script extracts the official name of a country from the response and formats it into a new JSON structure.

**Method:** GET

**URL**

```
https://restcountries.com/v3.1/name/{Vocabulary:country.country}?fullText=true
```

**Process response script**

```javascript
let parsedContent = JSON.parse(response.Content);
let content = parsedContent[0];
let officialName = {
  "country.officialName": content.name.official,
};
response.Content = JSON.stringify([{ Data: officialName, Score: 0 }]);
log(JSON.stringify(response));
```

### Melissa

[Melissa](https://www.melissa.com/developer/global-address) is a widely used address validation and data quality service that helps organizations keep address data accurate, standardized, and up to date.

CluedIn can connect to Melissa through its REST API, sending address data for validation. The API responds with standardized and enriched results, which CluedIn processes and maps to the appropriate vocabulary keys for storage and further use.

In the following example, ingested company data contains missing or incorrect address details. We will use Melissa REST API calls to send address-related data stored in different vocabulary keys and enrich golden records with validated and parsed addresses.

![Ingested company address records with missing or inconsistent City, Zipcode, State, Country, and Type values](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api/melissa/data-before-enrichment.png)

**Configure Melissa REST API enricher**

To [add the Melissa REST API enricher](#add-rest-api-enricher), on the **Configure** tab, provide the following information:

**Accepted Business Domain:** In this example, **/Address**.

**Method:** GET.

**URL:**

In this example, the URL references several vocabulary keys that store address-related data.

```
https://address.melissadata.net/V3/WEB/GlobalAddress/doGlobalAddress?id={APIKey}&a1={Vocabulary:company.address.streetAddress}&loc={Vocabulary:company.address.city}&admarea={Vocabulary:company.address.state}&ctry={Vocabulary:company.address.country}&postal={Vocabulary:company.address.zipcode}
```

**Headers:**

```
Content-Type=application/json
```

**Process response script:**

This script transforms the raw response into a structured format that fits CluedIn’s schema.

```javascript
let parsedContent = JSON.parse(response.Content);
let content = parsedContent.Records[0];

let melissaCountryName = content.CountryName;
let melissaCountryISO = content.CountryISO3166_1_Alpha3;
let melissaZipcode = content.PostalCode;
let melissaFullAddress = content.FormattedAddress;
let melissaCity = content.Locality;
let melissaState = content.AdministrativeArea;
let melissaDeliveryIndicator = content.DeliveryIndicator;

let newContent = {
  "company.address.city": melissaCity,
  "company.address.zipcode": melissaZipcode,
  "company.address.state": melissaState,
  "company.address.country": melissaCountryISO,
  "company.address.vocabKey2": melissaCountryName,
  "company.address.vocabKey1": melissaFullAddress,
  "company.address.type": melissaDeliveryIndicator,
};

//apply conditions if required

if (melissaDeliveryIndicator === "B") {
  newContent["company.address.type"] = "Business";
} else if (melissaDeliveryIndicator === "R") {
  newContent["company.address.type"] = "Residential";
} else {
  newContent["company.address.type"] = "Unknown";
}

response.Content = JSON.stringify([{ Data: newContent, Score: 0 }]);
//log(JSON.stringify(response));
```

![REST API enricher Configure tab for Melissa: Accepted Business Domain Address, Method GET, the Melissa URL, API Key, Headers, and Process Response Script](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api/melissa/configure_tab.png)

**Example of golden record before and after enrichment**

An example of a golden record before enrichment:

![Properties of the Walmart Inc. golden record before enrichment, including Type Organization and Zipcode 72716](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api/melissa/golden_record_before_enrichment.png)

Same record after enrichment:

- The values for the **Type** and **Zipcode** properties were updated.

- Two new vocabulary keys (**Vocab Key 1** and **Vocab Key 2** were added).

![Properties of the Walmart Inc. golden record after enrichment, with Type Business, new Vocab Key 1 and Vocab Key 2 values, and Zipcode 72716-6209 highlighted](https://documentation.cluedin.net/assets/images/preparation/enricher/rest-api/melissa/golden_record_after_enrichment.png)

**Sample response**

Provided below is an example of the response you would receive for the same request in Postman. If you need an additional address line in your golden records, select the relevant fields from the response and add them to the [process response script](#configure-melissa-rest-api-enricher) provided earlier on this page. For example, you could include the **Latitude** and **Longitude** fields.

**URL:**

```
https://address.melissadata.net/V3/WEB/GlobalAddress/doGlobalAddress?id=key&org=Walmart Inc.&a1=702 S.W. 8th St.&loc=Bentonville&admarea=AR&ctry=USA&postal=72716&act=check,verify&format=JSON
```

**Response:**

```json
{
  "Version": "9.4.1.1228",
  "TransmissionReference": "",
  "TransmissionResults": "",
  "TotalRecords": "1",
  "Records": [
    {
      "RecordID": "1",
      "Results": "AC16,AV24,GS05",
      "FormattedAddress": "Walmart Inc.;702 SW 8th St;Bentonville AR  72716-6299",
      "Organization": "Walmart Inc.",
      "AddressLine1": "702 SW 8th St",
      "AddressLine2": "Bentonville AR  72716-6299",
      "AddressLine3": "",
      "AddressLine4": "",
      "AddressLine5": "",
      "AddressLine6": "",
      "AddressLine7": "",
      "AddressLine8": "",
      "SubPremises": "",
      "DoubleDependentLocality": "",
      "DependentLocality": "",
      "Locality": "Bentonville",
      "SubAdministrativeArea": "Benton",
      "AdministrativeArea": "AR",
      "PostalCode": "72716-6299",
      "PostalCodeType": "U",
      "AddressType": "F",
      "AddressKey": "72716629902",
      "SubNationalArea": "",
      "CountryName": "United States of America",
      "CountryISO3166_1_Alpha2": "US",
      "CountryISO3166_1_Alpha3": "USA",
      "CountryISO3166_1_Numeric": "840",
      "CountrySubdivisionCode": "US-AR",
      "Thoroughfare": "SW 8th St",
      "ThoroughfarePreDirection": "SW",
      "ThoroughfareLeadingType": "",
      "ThoroughfareName": "8th",
      "ThoroughfareTrailingType": "St",
      "ThoroughfarePostDirection": "",
      "DependentThoroughfare": "",
      "DependentThoroughfarePreDirection": "",
      "DependentThoroughfareLeadingType": "",
      "DependentThoroughfareName": "",
      "DependentThoroughfareTrailingType": "",
      "DependentThoroughfarePostDirection": "",
      "Building": "",
      "PremisesType": "",
      "PremisesNumber": "702",
      "SubPremisesType": "",
      "SubPremisesNumber": "",
      "PostBox": "",
      "Latitude": "36.363516",
      "Longitude": "-94.183177",
      "DeliveryIndicator": "U",
      "MelissaAddressKey": "2153275541",
      "MelissaAddressKeyBase": "",
      "PostOfficeLocation": "",
      "SubPremiseLevel": "",
      "SubPremiseLevelType": "",
      "SubPremiseLevelNumber": "",
      "SubBuilding": "",
      "SubBuildingType": "",
      "SubBuildingNumber": "",
      "UTC": "UTC-06:00",
      "DST": "Y",
      "DeliveryPointSuffix": "",
      "CensusKey": "050070205041002",
      "Extras": {}
    }
  ]
}
```

### AddressBase (OSData)

AddressBase is a set of diverse APIs one of which is address verification, like OS Places API (https://osdatahub.os.uk/).

METHOD: GET
URL: https://api.os.uk/search/places/v1/find?key={APIKey}&query={Vocabulary:address.fu[…]s}%20{Vocabulary:address.locality}&maxresults=1&matchprecision=1
API Key: create your own, premium has 60 day trial

Process Response Script:

```javascript
// Expects an input object named `response` like:
// {
//   HttpStatus: "OK",
//   Content: "{...OS Places JSON...}",
//   Headers: [{ Key: "Content-Type", Value: "application/json" }, ...]
// }
//
// Output will be:
// response.Content = "[{\"Data\":{...},\"Score\":0}]"

(function () {
  function asStr(v) {
    if (v === undefined || v === null) return null;
    v = String(v);
    return v.length ? v : null;
  }

  function asNum(v) {
    if (v === undefined || v === null || v === "") return null;
    var n = Number(v);
    return isFinite(n) ? n : null;
  }

  function setHeader(key, value) {
    if (!response.Headers) response.Headers = [];
    var i;
    for (i = 0; i < response.Headers.length; i++) {
      if (
        response.Headers[i] &&
        typeof response.Headers[i].Key === "string" &&
        response.Headers[i].Key.toLowerCase() === key.toLowerCase()
      ) {
        response.Headers[i].Value = value;
        return;
      }
    }
    response.Headers.push({ Key: key, Value: value });
  }

  // Parse incoming OS Places content
  var parsed;
  try {
    parsed = JSON.parse(response.Content || "{}");
  } catch (e) {
    // If Content isn't valid JSON, return a safe empty payload
    response.HttpStatus = response.HttpStatus || "OK";
    response.Content = JSON.stringify([
      {
        Data: { "osPlaces.error": "Invalid JSON in response.Content" },
        Score: 0,
      },
    ]);
    response.ContentType = "application/x-javascript";
    setHeader("Content-Type", "application/x-javascript");
    return;
  }

  var first =
    parsed && parsed.results && parsed.results.length
      ? parsed.results[0]
      : null;

  if (!first || typeof first !== "object") {
    response.HttpStatus = response.HttpStatus || "OK";
    response.Content = JSON.stringify([
      { Data: { "osPlaces.found": false }, Score: 0 },
    ]);
    response.ContentType = "application/x-javascript";
    setHeader("Content-Type", "application/x-javascript");
    return;
  }

  var source = first.DPA ? "DPA" : first.LPI ? "LPI" : null;
  var src = source ? first[source] : null;

  if (!src) {
    response.HttpStatus = response.HttpStatus || "OK";
    response.Content = JSON.stringify([
      { Data: { "osPlaces.found": false }, Score: 0 },
    ]);
    response.ContentType = "application/x-javascript";
    setHeader("Content-Type", "application/x-javascript");
    return;
  }

  // Build normalized dotted-key output (first result only)
  var newContent = {
    "osPlaces.found": true,
    "osPlaces.source": source,
    "osPlaces.uprn": null,
    "osPlaces.fullAddress": null,
    "osPlaces.postcode": null,
    "osPlaces.town": null,
    "osPlaces.street": null,
    "osPlaces.building": null,
    "osPlaces.subBuilding": null,
    "osPlaces.organisation": null,
    "osPlaces.department": null,
    "osPlaces.easting": null,
    "osPlaces.northing": null,
    "osPlaces.latitude": null,
    "osPlaces.longitude": null,
  };

  if (source === "DPA") {
    newContent["osPlaces.uprn"] = asStr(src.UPRN);
    newContent["osPlaces.fullAddress"] = asStr(src.ADDRESS);
    newContent["osPlaces.postcode"] = asStr(src.POSTCODE);
    newContent["osPlaces.town"] = asStr(src.POST_TOWN);

    newContent["osPlaces.street"] =
      asStr(src.THOROUGHFARE) || asStr(src.DEPENDENT_THOROUGHFARE);
    newContent["osPlaces.building"] =
      asStr(src.BUILDING_NAME) || asStr(src.BUILDING_NUMBER);
    newContent["osPlaces.subBuilding"] = asStr(src.SUB_BUILDING_NAME);

    newContent["osPlaces.organisation"] = asStr(src.ORGANISATION_NAME);
    newContent["osPlaces.department"] = asStr(src.DEPARTMENT_NAME);

    newContent["osPlaces.easting"] = asNum(src.X_COORDINATE);
    newContent["osPlaces.northing"] = asNum(src.Y_COORDINATE);
    newContent["osPlaces.latitude"] = asNum(src.LAT);
    newContent["osPlaces.longitude"] = asNum(src.LNG);
  } else {
    // LPI
    newContent["osPlaces.uprn"] = asStr(src.UPRN);
    newContent["osPlaces.fullAddress"] = asStr(src.ADDRESS); // may be missing in some LPI responses
    newContent["osPlaces.postcode"] =
      asStr(src.POSTCODE_LOCATOR) || asStr(src.POSTCODE);
    newContent["osPlaces.town"] = asStr(src.TOWN_NAME) || asStr(src.POST_TOWN);

    newContent["osPlaces.street"] = asStr(src.STREET_DESCRIPTION);
    newContent["osPlaces.building"] =
      asStr(src.PAO_TEXT) ||
      asStr(src.PAO_START_NUMBER) ||
      asStr(src.PAO_START_SUFFIX);
    newContent["osPlaces.subBuilding"] =
      asStr(src.SAO_TEXT) ||
      asStr(src.SAO_START_NUMBER) ||
      asStr(src.SAO_START_SUFFIX);

    newContent["osPlaces.organisation"] = asStr(src.ORGANISATION);
    newContent["osPlaces.department"] = asStr(src.DEPARTMENT);

    newContent["osPlaces.easting"] = asNum(src.X_COORDINATE);
    newContent["osPlaces.northing"] = asNum(src.Y_COORDINATE);
    newContent["osPlaces.latitude"] = asNum(src.LAT);
    newContent["osPlaces.longitude"] = asNum(src.LNG);
  }

  // Wrap into enricher output format
  response.HttpStatus = response.HttpStatus || "OK";
  response.Content = JSON.stringify([{ Data: newContent, Score: 0 }]);

  // Keep response format consistent with your sample
  response.ContentType = "application/x-javascript";
  setHeader("Content-Type", "application/x-javascript");
})();
```

## Sample (V2)

This section provides some sample configuration and scripts that you can use to configure the V2 REST API enricher.

### Sample process script

```javascript
// companies.country vocabulary key added to Vocabulary and Properties field can be accessed using .find
const country = vocabularies.find((x) => x.Key === "companies.country")?.Value;
const url = `https://restcountries.com/v3.1/name/${country}?fullText=true`;

// usee http.send() to make an API call
const countryResponse = http.send({
  url: url,
  method: "GET",
  // sample headers and body
  // headers: [
  //   {Key: 'Content-Type', Value: 'application/json'},
  //   {Key: 'Authorization', Value: `Bearer ${token}`}
  // ],
  // body: { name: 'testName' }
});

log(JSON.stringify(countryResponse));

let parsedContent = JSON.parse(countryResponse?.Content);
let results = {
  "country.officialName": parsedContent[0]?.name?.official,
};

response = JSON.stringify([{ Data: results, Score: 100 }]);
```

### Logo.dev

This example retrieves a company's logo URL from [Logo.dev](https://www.logo.dev/) by using the website stored on the golden record. It stores a stable Logo.dev image URL and the matched domain; it does not store the binary image response as a property value.

**API Key**
```
your-logo-dev-api-key
```

**Vocabulary and Properties**:

```
companies.website
```

**Process script**

```javascript
// REST API Enricher V2 - Logo.dev logo URL enrichment
// The {APIKey} placeholder is replaced with the API Key field value.

const website = vocabularies.find((x) => x.Key === "companies.website")?.Value;

if (!website) {
  response = JSON.stringify([]);
} else {
  // Accept a full URL, a bare domain, or a domain with a port.
  // Logo.dev expects only the hostname.
  const domain = String(website)
    .trim()
    .replace(/^https?:\/\//i, "")
    .replace(/^www\./i, "")
    .split(/[\/?#]/)[0]
    .split(":")[0]

  if (!domain) {
    response = JSON.stringify([]);
  } else {
    const logoUrl = `https://img.logo.dev/${encodeURIComponent(domain)}?token={APIKey}`;

    // Request the image so an unavailable logo or API error does not create
    // an enrichment result. The returned image bytes are intentionally ignored.
    const logoResponse = http.send({
      url: logoUrl,
      method: "GET",
    });

    const status = String(logoResponse?.HttpStatus || "");
    if (status === "OK") {
      response = JSON.stringify([
        {
          Data: {
            "companies.logoUrl": logoUrl,
            "companies.logoDomain": domain,
          },
          Score: 100,
        },
      ]);
    } else {
      log(`Logo.dev did not return a logo for ${domain}. Status: ${status}`);
      response = JSON.stringify([]);
    }
  }
}
```

### Dun & Bradstreet

**Vocabulary key and properties**

```
companies.dunsnumber
```

**Process script**

```javascript
// REST API Enricher V2 - DnB Search by DUNS - Process Response Script

function populateIndustryCodes(results, parsedContent) {
  const selectedTypes = ["19295", "37788"];

  const industryCodeValues =
    parsedContent?.organization?.industryCodes?.filter(
      (x) => x?.typeDnBCode > 0 && selectedTypes.includes(String(x.typeDnBCode))
    ) ?? [];

  if (industryCodeValues.length === 0) return;

  let industryCodesIndex = 0;
  for (const industryCode of industryCodeValues) {
    results[`Industry_${industryCodesIndex}_Description`] =
      industryCode.description;
    results[`Industry_${industryCodesIndex}_TypeDescription`] =
      industryCode.typeDescription;
    results[`Industry_${industryCodesIndex}_TypeDnBCode`] =
      industryCode.typeDnBCode;
    results[`Industry_${industryCodesIndex}_Priority`] = industryCode.priority;
    results[`Industry_${industryCodesIndex}_Code`] = industryCode.code;

    industryCodesIndex++;
  }
}

function populateOrganizationInfo(results, parsedContent) {
  const org = parsedContent?.organization;

  // DUNS Control Status
  if (org?.dunsControlStatus) {
    const status = org.dunsControlStatus;

    results.DunsControlStatusFullReportDate = status.fullReportDate;
    results.DunsControlStatusLastUpdateDate = status.lastUpdateDate;
    results.DunsControlStatusOperatingStatusDescription =
      status.operatingStatus?.description;
    results.DunsControlStatusOperatingStatusDnbCode =
      status.operatingStatus?.dnbCode;
    results.DunsControlStatusIsMarketable = status.isMarketable;
    results.DunsControlStatusIsMailUndeliverable = status.isMailUndeliverable;
    results.DunsControlStatusIsTelephoneDisconnected =
      status.isTelephoneDisconnected;
    results.DunsControlStatusIsDelisted = status.isDelisted;

    if (status?.operatingStatus) {
      results.OperatingStatusCode = status.operatingStatus?.dnbCode;
      results.OperatingStatusDescription = status.operatingStatus?.description;
    }
  }

  // DUNS Numbers
  results.Duns = org?.duns;
  results.DomesticUltimateDuns = org?.corporateLinkage?.domesticUltimate?.duns;
  results.GlobalUltimateDuns = org?.corporateLinkage?.globalUltimate?.duns;
  results.ParentDuns = org?.corporateLinkage?.parent?.duns;
  results.HeadQuarterDuns = org?.corporateLinkage?.headQuarter?.duns;

  // Business Information
  results.PrimaryBusinessName = org?.primaryName;
  results.BusinessEntityTypeDnbCode = org?.businessEntityType?.dnbCode;
  results.BusinessEntityTypeDescription = org?.businessEntityType?.description;

  // Trade Style Names
  const tradeStyleNames =
    org?.tradeStyleNames
      ?.map((t) => t?.name)
      ?.filter((n) => n && n.length > 0) ?? [];

  if (tradeStyleNames.length > 0) {
    results.TradeStyleNames = tradeStyleNames.join(" | ");
  }

  // Website
  const website = org?.websiteAddress?.[0];
  if (website) {
    results.WebsiteUrl = website.url;
  }

  // Telephone
  const telephone = org?.telephone?.[0];
  if (telephone?.isdCode && telephone?.telephoneNumber) {
    results.Telephone = `+${telephone.isdCode} ${telephone.telephoneNumber}`;
  }

  // Fax
  const fax = org?.fax?.[0];
  if (fax?.isdCode && fax?.faxNumber) {
    results.Fax = `+${fax.isdCode} ${fax.faxNumber}`;
  }

  // Stock Exchange
  const primaryStockExchange = org?.stockExchanges?.find(
    (x) => x?.isPrimary === true
  );

  if (primaryStockExchange) {
    results.StockExchangeTickerName = primaryStockExchange.tickerName;
    results.StockExchangeName = primaryStockExchange.exchangeName?.description;
    results.StockExchangeCountryCode =
      primaryStockExchange.exchangeCountry?.isoAlpha2Code;
  }

  // Registration Numbers
  const selectedRegistrationNumberTypes = ["2541"];

  if (selectedRegistrationNumberTypes.length > 0) {
    const registrationNumbers =
      org?.registrationNumbers?.filter(
        (x) =>
          x?.typeDnBCode > 0 &&
          selectedRegistrationNumberTypes.includes(String(x.typeDnBCode))
      ) ?? [];

    let index = 0;
    for (const reg of registrationNumbers) {
      results[`RegistrationNumbers_${index}_RegistrationNumber`] =
        reg.registrationNumber;
      results[`RegistrationNumbers_${index}_TypeDescription`] =
        reg.typeDescription;
      results[`RegistrationNumbers_${index}_TypeDnBCode`] = reg.typeDnBCode;
      results[
        `RegistrationNumbers_${index}_RegistrationNumberClassDescription`
      ] = reg.registrationNumberClass?.description;
      results[`RegistrationNumbers_${index}_RegistrationNumberClassDnbCode`] =
        reg.registrationNumberClass?.dnbCode;

      index++;
    }
  }

  // Corporate Linkage - Family Tree Roles Played
  let familyIndex = 0;
  for (const role of org?.corporateLinkage?.familytreeRolesPlayed ?? []) {
    results[
      `CorporateLinkageFamilyTreeRolesPlayedVocabulary_${familyIndex}_Description`
    ] = role.description;
    results[
      `CorporateLinkageFamilyTreeRolesPlayedVocabulary_${familyIndex}_DnbCode`
    ] = role.dnbCode;
    familyIndex++;
  }

  results.HierarchyLevel = org?.corporateLinkage?.hierarchyLevel;
  results.GlobalUltimateFamilyTreeMembersCount =
    org?.corporateLinkage?.globalUltimateFamilyTreeMembersCount;

  // Number of Employees
  results.NumberOfEmployees = org?.numberOfEmployees?.[0]?.value;
  results.GlobalUltimateNumberOfEmployees =
    org?.corporateLinkage?.globalUltimate?.numberOfEmployees?.[0]?.value;
  results.DomesticUltimateNumberOfEmployees =
    org?.corporateLinkage?.domesticUltimate?.numberOfEmployees?.[0]?.value;

  // Yearly Revenue
  const orgFinancial = org?.financials?.[0];
  const orgRevenue = orgFinancial?.yearlyRevenue?.[0];

  results.YearlyRevenue =
    orgRevenue && orgRevenue.currency
      ? `${orgRevenue.value} ${orgRevenue.currency}`
      : null;

  const globalUltimateFinancial = org?.globalUltimate?.financials?.[0];
  const globalUltimateRevenue = globalUltimateFinancial?.yearlyRevenue?.[0];

  results.GlobalUltimateYearlyRevenue =
    globalUltimateRevenue && globalUltimateRevenue.currency
      ? `${globalUltimateRevenue.value} ${globalUltimateRevenue.currency}`
      : null;

  const domesticUltimateFinancial = org?.domesticUltimate?.financials?.[0];
  const domesticUltimateRevenue = domesticUltimateFinancial?.yearlyRevenue?.[0];

  results.DomesticUltimateYearlyRevenue =
    domesticUltimateRevenue && domesticUltimateRevenue.currency
      ? `${domesticUltimateRevenue.value} ${domesticUltimateRevenue.currency}`
      : null;
}

function populatePrimaryAddresses(results, parsedContent) {
  const org = parsedContent?.organization;

  const domesticUltimateDuns = org?.corporateLinkage?.domesticUltimate?.duns;
  const globalUltimateDuns = org?.corporateLinkage?.globalUltimate?.duns;
  const parentDuns = org?.corporateLinkage?.parent?.duns;
  const headQuarterDuns = org?.corporateLinkage?.headQuarter?.duns;
  const duns = org?.duns;

  // Domestic Ultimate
  if (
    org?.corporateLinkage?.domesticUltimate?.primaryAddress &&
    domesticUltimateDuns &&
    domesticUltimateDuns !== duns
  ) {
    const addr = org.corporateLinkage.domesticUltimate.primaryAddress;

    results.DomesticUltimatePrimaryAddressCountry = addr.addressCountry?.name;
    results.DomesticUltimateISO2CountryCode =
      addr.addressCountry?.isoAlpha2Code;
    results.DomesticUltimatePrimaryAddressCountyName = addr.addressCounty?.name;
    results.DomesticUltimatePrimaryAddressLocality = addr.addressLocality?.name;
    results.DomesticUltimatePrimaryAddressPostalCode = addr.postalCode;
    results.DomesticUltimatePrimaryAddressRegionName = addr.addressRegion?.name;
    results.DomesticUltimatePrimaryAddressRegionAbbreviatedName =
      addr.addressRegion?.abbreviatedName;
    results.DomesticUltimatePrimaryAddressStreetLine1 =
      addr.streetAddress?.line1;
    results.DomesticUltimatePrimaryAddressStreetLine2 =
      addr.streetAddress?.line2;
  }

  // Global Ultimate
  if (
    org?.corporateLinkage?.globalUltimate?.primaryAddress &&
    globalUltimateDuns &&
    globalUltimateDuns !== duns
  ) {
    const addr = org.corporateLinkage.globalUltimate.primaryAddress;

    results.GlobalUltimatePrimaryAddressCountry = addr.addressCountry?.name;
    results.GlobalUltimateISO2CountryCode = addr.addressCountry?.isoAlpha2Code;
    results.GlobalUltimatePrimaryAddressCountyName = addr.addressCounty?.name;
    results.GlobalUltimatePrimaryAddressLocality = addr.addressLocality?.name;
    results.GlobalUltimatePrimaryAddressPostalCode = addr.postalCode;
    results.GlobalUltimatePrimaryAddressRegionName = addr.addressRegion?.name;
    results.GlobalUltimatePrimaryAddressRegionAbbreviatedName =
      addr.addressRegion?.abbreviatedName;
    results.GlobalUltimatePrimaryAddressStreetLine1 = addr.streetAddress?.line1;
    results.GlobalUltimatePrimaryAddressStreetLine2 = addr.streetAddress?.line2;
  }

  // Parent
  if (
    org?.corporateLinkage?.parent?.primaryAddress &&
    parentDuns &&
    parentDuns !== duns
  ) {
    const addr = org.corporateLinkage.parent.primaryAddress;

    results.ParentPrimaryAddressCountry = addr.addressCountry?.name;
    results.ParentISO2CountryCode = addr.addressCountry?.isoAlpha2Code;
    results.ParentPrimaryAddressCountyName = addr.addressCounty?.name;
    results.ParentPrimaryAddressLocality = addr.addressLocality?.name;
    results.ParentPrimaryAddressPostalCode = addr.postalCode;
    results.ParentPrimaryAddressRegionName = addr.addressRegion?.name;
    results.ParentPrimaryAddressRegionAbbreviatedName =
      addr.addressRegion?.abbreviatedName;
    results.ParentPrimaryAddressStreetLine1 = addr.streetAddress?.line1;
    results.ParentPrimaryAddressStreetLine2 = addr.streetAddress?.line2;
  }

  // HeadQuarter
  if (
    org?.corporateLinkage?.headQuarter?.primaryAddress &&
    headQuarterDuns &&
    headQuarterDuns !== duns
  ) {
    const addr = org.corporateLinkage.headQuarter.primaryAddress;

    results.HeadQuarterPrimaryAddressCountry = addr.addressCountry?.name;
    results.HeadQuarterISO2CountryCode = addr.addressCountry?.isoAlpha2Code;
    results.HeadQuarterPrimaryAddressCountyName = addr.addressCounty?.name;
    results.HeadQuarterPrimaryAddressLocality = addr.addressLocality?.name;
    results.HeadQuarterPrimaryAddressPostalCode = addr.postalCode;
    results.HeadQuarterPrimaryAddressRegionName = addr.addressRegion?.name;
    results.HeadQuarterPrimaryAddressRegionAbbreviatedName =
      addr.addressRegion?.abbreviatedName;
    results.HeadQuarterPrimaryAddressStreetLine1 = addr.streetAddress?.line1;
    results.HeadQuarterPrimaryAddressStreetLine2 = addr.streetAddress?.line2;
  }

  // Self
  if (!org?.primaryAddress) return;

  const primary = org.primaryAddress;

  results.PrimaryAddressCountry = primary.addressCountry?.name;
  results.ISO2CountryCode = primary.addressCountry?.isoAlpha2Code;
  results.PrimaryAddressCountyName = primary.addressCounty?.name;
  results.PrimaryAddressLocality = primary.addressLocality?.name;
  results.PrimaryAddressPostalCode = primary.postalCode;
  results.PrimaryAddressRegionName = primary.addressRegion?.name;
  results.PrimaryAddressRegionAbbreviatedName =
    primary.addressRegion?.abbreviatedName;
  results.PrimaryAddressStreetLine1 = primary.streetAddress?.line1;
  results.PrimaryAddressStreetLine2 = primary.streetAddress?.line2;
}

function searchByDuns(token) {
  // Get DUNS Number from vocabularies passed in Vocabulary and Properties control, please change the key as needed
  const dunsNumber = vocabularies.find(
    (x) => x.Key === "companies.dunsnumber"
  )?.Value;
  const dnbResponse = http.send({
    url: `https://plus.dnb.com/v1/data/duns/${dunsNumber}?blockIDs=companyfinancials_L1_v3,hierarchyconnections_L1_v1,companyinfo_L3_v1,companyinfo_identifiers_v1,esginsight_L3_v1`,
    method: "GET",
    headers: [{ Key: "Authorization", Value: `Bearer ${token}` }],
  });

  let parsedContent = JSON.parse(dnbResponse.Content);
  let results = {};

  const confidenceScore =
    parsedContent?.matchCandidates?.[0]?.matchQualityInformation
      ?.confidenceCode;
  populatePrimaryAddresses(results, parsedContent);
  populateOrganizationInfo(results, parsedContent);
  populateIndustryCodes(results, parsedContent);

  return JSON.stringify([
    { Data: results, Score: confidenceScore ? confidenceScore * 10 : 100 },
  ]);
}

try {
  const authCacheKey = "dnb_auth_token";
  const cachedAuthToken = cache.Get(authCacheKey);

  const key = "{key}";
  const secret = "{secret}";

  const credentials = `${key}:${secret}`;

  const base64 = stringEncoder.toBase64(credentials);

  const token = cache.GetOrSetFactory(
    "dnb_auth_token",
    () => {
      const tokenResponse = http.Send({
        url: `https://plus.dnb.com/v2/token`,
        method: "POST",
        headers: [
          { Key: "Content-Type", Value: "application/json" },
          { Key: "Authorization", Value: `Basic ${base64}` },
        ],
        body: { grant_type: "client_credentials" },
      });

      return JSON.parse(tokenResponse.Content).access_token;
    },
    3000000
  );

  log(JSON.stringify(token));

  response = searchByDuns(token);
} catch (error) {
  const errorDetails = {
    name: error?.name,
    message: error?.message,
    stack: error?.stack,
  };
  log("Authentication failed :" + JSON.stringify(errorDetails));
}
```

### REST Countries

This script extracts the official name of a country from the response and formats it into a new JSON structure.

**Vocabulary and Properties**

```
companies.country
```

**Process script**

```javascript
const country = vocabularies.find((x) => x.Key === "companies.country")?.Value;
const url = `https://restcountries.com/v3.1/name/${country}?fullText=true`;

const countryResponse = http.send({
  url: url,
  method: "GET",
});

log(JSON.stringify(countryResponse));

let parsedContent = JSON.parse(countryResponse?.Content);
let results = {
  "country.officialName": parsedContent[0]?.name?.official,
};

response = JSON.stringify([{ Data: results, Score: 100 }]);
```

## Helper functions

You can use the following helper functions in your scripts to facilitate common tasks.

**Logging**

| Function | Signature    | Description                                                                                     |
| -------- | ------------ | ----------------------------------------------------------------------------------------------- |
| `log`    | `log(value)` | Writes a message to the application log at **Information** level. Useful for debugging scripts. |

Example:

```javascript
log("Processing response from API");
```

**String Encoding**

| Function     | Signature                           | Description                                    |
| ------------ | ----------------------------------- | ---------------------------------------------- |
| `toBase64`   | `toBase64(value: string): string`   | Encodes a UTF-8 string into Base64 format.     |
| `fromBase64` | `fromBase64(value: string): string` | Decodes a Base64-encoded string back to UTF-8. |
| `urlEncode`  | `urlEncode(value: string): string`  | Encodes a string for safe use in a URL.        |
| `urlDecode`  | `urlDecode(value: string): string`  | Decodes a URL-encoded string.                  |

Example:

```javascript
// use stringEncoder object
var encoded = stringEncoder.toBase64("hello");
var decoded = stringEncoder.fromBase64(encoded);
```

**Cache**

| Function          | Signature                                                                     | Description                                                                                                                                                                                                                                 |
| ----------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get`             | `get(key: string): object`                                                    | Retrieves a cached value for the current organization. Returns `null` if not found.                                                                                                                                                         |
| `set`             | `set(key: string, value: object, expiration: number): void`                   | Stores a value in cache with an expiration time (in milliseconds). Does nothing if the key already exists. It is recommended to use `getOrSetValue` or `getOrSetFactory` instead, as they use distributed locks to prevent race conditions. |
| `getOrSetValue`   | `getOrSetValue(key: string, value: object, expiration: number): object`       | Returns the cached value if it exists; otherwise, stores the provided value with the given expiration time (in milliseconds) and returns it.                                                                                                |
| `getOrSetFactory` | `getOrSetFactory(key: string, factory: function, expiration: number): object` | Returns the cached value if it exists; otherwise, calls the factory function to produce the value, stores it with the given expiration time (in milliseconds), and returns it.                                                              |

Example:

```javascript
// use cache object (using )
var token = cache.get("authToken");

if (!token) {
  token = "new-token";
  cache.getOrSetValue("authToken", token, 60000);
}
```

**Http (V2)**

| Function | Signature                         | Description                                                                                                                                                                                                                     |
| -------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `send`   | `send(request: object): Response` | Sends an HTTP request based on the provided request object and return the response. Refer to request [Request and Response Type](#accessing-and-modifying-request-and-response-objects-in-process-request-and-response-scripts) |

Example:

```javascript
const tokenResponse = http.send({
  url: `https://plus.dnb.com/v2/token`,
  method: "POST",
  headers: [
    { Key: "Content-Type", Value: "application/json" },
    { Key: "Authorization", Value: `Basic ${base64}` },
  ],
  body: { grant_type: "client_credentials" },
});
```

Up: [Enrichment](https://documentation.cluedin.net/govern/enrichment.md)
