> ## Documentation Index
> Fetch the complete documentation index at: https://docs.repdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Integration (JS Library)

> Collect additional device datasets with the Research Defender JavaScript library and include them in your respondent search.

## Overview

The basic integration sends only your survey number and the respondent's session ID. The advanced integration also loads the Research Defender JavaScript library in the respondent's browser. The library collects additional datasets, and the search API processes them to produce a more complete result.

Because the library needs a moment to collect its data, your integration waits for a short interval before calling the search endpoint.

## How it works

<Steps>
  <Step title="Load the library">
    Import `rd_sys_util.min.js` from your Research Defender environment.
  </Step>

  <Step title="Initialize it">
    Call `init()` with your publishable key and environment so the library starts collecting data.
  </Step>

  <Step title="Wait for the datasets">
    Pause for a short interval (1 second by default) so the datasets are ready.
  </Step>

  <Step title="Get the pass-through value">
    Call `pass_params()`. It returns a string that you send with the search request.
  </Step>

  <Step title="Call the search endpoint">
    Send a `POST` or `GET` request that includes your survey number, the session ID, and the `pass_params()` value.
  </Step>
</Steps>

## Prerequisites

* A Research Defender **publishable key**
* Your **survey number** (`sy_nr`)
* The respondent's **session ID** (`sn_ud`)
* A page served over **HTTP(S)**. Opening the file directly from disk (`file://`) will not work.

<Warning>
  Only use your **publishable** key in client-side code. Never expose a secret key in a browser.
</Warning>

## Configuration

| Setting | Description | Default |
| - | - | - |
| `ENVIRONMENT` | The Research Defender environment, for example `staging` or `prod`. | `prod` |
| `PUBLISHABLE_KEY` | Your Research Defender publishable key. | n/a |
| `LIBRARY_VERSION` | The version of `rd_sys_util.min.js` to load. | `2.0.1` |
| `DATASET_READY_DELAY_MS` | Time in milliseconds to wait after `init()` before calling `pass_params()`. | `1000` |

## Endpoints

<ParamField path="Library" type="string">
  `https://{environment}.rtymgt.com/static/js/rd_sys_util.min.js?v={version}`
</ParamField>

<ParamField path="Search" type="string">
  `https://{environment}.rtymgt.com/api/v4/respondents/search/{publishable_key}`
</ParamField>

### Request parameters

<ParamField body="sy_nr" type="string" required>
  The survey number.
</ParamField>

<ParamField body="sn_ud" type="string" required>
  The respondent's session ID.
</ParamField>

<ParamField body="df_details" type="string">
  Set to `"1"` to enable.
</ParamField>

<ParamField body="mfr" type="string">
  Set to `"1"` to enable.
</ParamField>

<ParamField body="{pass_params() value}" type="string" required>
  The string returned by `pass_params()`. Send it as the **name** of a parameter with an empty string as its value.
</ParamField>

<Note>
  Requests must be sent with credentials (`credentials: "include"`) so Research Defender cookies are included.
</Note>

## Examples

<Tabs>
  <Tab title="POST">
    ```javascript advanced-post.js theme={null}
    // ---------------------------------------------------------------
    // Configuration
    // ---------------------------------------------------------------
    const ENVIRONMENT = "prod"; // "staging" or "prod"
    const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
    const LIBRARY_VERSION = "2.0.1";

    // Replace with the values for the respondent you are checking.
    const SURVEY_NUMBER = "YOUR_SURVEY_NUMBER"; // sy_nr
    const SESSION_ID = "YOUR_SESSION_ID";       // sn_ud

    // Time to let the library collect its datasets before calling search.
    const DATASET_READY_DELAY_MS = 1000;

    const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
    const LIBRARY_URL = `${BASE_URL}/static/js/rd_sys_util.min.js?v=${LIBRARY_VERSION}`;
    const SEARCH_ENDPOINT = `${BASE_URL}/api/v4/respondents/search/${PUBLISHABLE_KEY}`;

    // ---------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    /**
     * Parses the API response body. If the body has a stray trailing
     * character that breaks JSON.parse, it is trimmed and parsing is retried.
     */
    function parseResponse(text) {
      try {
        return JSON.parse(text);
      } catch {
        return JSON.parse(text.slice(0, -1));
      }
    }

    /** Loads the library, starts data collection, and returns its pass-through key. */
    async function collectLibraryData() {
      const { init, pass_params } = await import(LIBRARY_URL);

      if (typeof init !== "function" || typeof pass_params !== "function") {
        throw new Error("Research Defender library did not load correctly.");
      }

      await init({ key: PUBLISHABLE_KEY, env: ENVIRONMENT });

      // Give the library time to finish collecting its datasets.
      await sleep(DATASET_READY_DELAY_MS);

      const pass = pass_params();
      console.log("[rd] pass_params computed:", pass ? `${pass.length} chars` : "empty");
      return pass;
    }

    /** Sends the POST search request and returns the parsed JSON result. */
    async function searchRespondent({ surveyNumber, sessionId, pass }) {
      const body = {
        sy_nr: surveyNumber,
        sn_ud: sessionId,
        df_details: "1",
        mfr: "1",
      };

      // The value returned by pass_params() is sent as a key with an empty
      // string value. JSON.stringify takes care of quoting and escaping.
      if (pass) {
        body[pass] = "";
      }

      const response = await fetch(SEARCH_ENDPOINT, {
        method: "POST",
        credentials: "include", // send Research Defender cookies
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });

      if (!response.ok) {
        throw new Error(`Search request failed with status ${response.status}`);
      }

      return parseResponse(await response.text());
    }

    // ---------------------------------------------------------------
    // Usage
    // ---------------------------------------------------------------
    const pass = await collectLibraryData();

    const result = await searchRespondent({
      surveyNumber: SURVEY_NUMBER,
      sessionId: SESSION_ID,
      pass,
    });

    console.log("Threat Potential:", result.Respondent?.threat_potential);
    console.log("Duplicate Flag:", result.Surveys?.[0]?.flag);
    console.log("Survey Number:", result.Surveys?.[0]?.survey_number);
    ```
  </Tab>

  <Tab title="GET">
    ```javascript advanced-get.js theme={null}
    // ---------------------------------------------------------------
    // Configuration
    // ---------------------------------------------------------------
    const ENVIRONMENT = "prod"; // "staging" or "prod"
    const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
    const LIBRARY_VERSION = "2.0.1";

    // Replace with the values for the respondent you are checking.
    const SURVEY_NUMBER = "YOUR_SURVEY_NUMBER"; // sy_nr
    const SESSION_ID = "YOUR_SESSION_ID";       // sn_ud

    // Time to let the library collect its datasets before calling search.
    const DATASET_READY_DELAY_MS = 1000;

    const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
    const LIBRARY_URL = `${BASE_URL}/static/js/rd_sys_util.min.js?v=${LIBRARY_VERSION}`;
    const SEARCH_ENDPOINT = `${BASE_URL}/api/v4/respondents/search/${PUBLISHABLE_KEY}`;

    // ---------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    /**
     * Parses the API response body. If the body has a stray trailing
     * character that breaks JSON.parse, it is trimmed and parsing is retried.
     */
    function parseResponse(text) {
      try {
        return JSON.parse(text);
      } catch {
        return JSON.parse(text.slice(0, -1));
      }
    }

    /** Loads the library, starts data collection, and returns its pass-through key. */
    async function collectLibraryData() {
      const { init, pass_params } = await import(LIBRARY_URL);

      if (typeof init !== "function" || typeof pass_params !== "function") {
        throw new Error("Research Defender library did not load correctly.");
      }

      await init({ key: PUBLISHABLE_KEY, env: ENVIRONMENT });

      // Give the library time to finish collecting its datasets.
      await sleep(DATASET_READY_DELAY_MS);

      const pass = pass_params();
      console.log("[rd] pass_params computed:", pass ? `${pass.length} chars` : "empty");
      return pass;
    }

    /** Sends the GET search request and returns the parsed JSON result. */
    async function searchRespondent({ surveyNumber, sessionId, pass }) {
      const params = new URLSearchParams({
        sy_nr: surveyNumber,
        sn_ud: sessionId,
        df_details: "1",
        mfr: "1",
      });

      // The value returned by pass_params() is sent as a parameter name with
      // an empty value. URLSearchParams takes care of URL encoding.
      if (pass) {
        params.set(pass, "");
      }

      const response = await fetch(`${SEARCH_ENDPOINT}?${params}`, {
        method: "GET",
        credentials: "include", // send Research Defender cookies
      });

      if (!response.ok) {
        throw new Error(`Search request failed with status ${response.status}`);
      }

      return parseResponse(await response.text());
    }

    // ---------------------------------------------------------------
    // Usage
    // ---------------------------------------------------------------
    const pass = await collectLibraryData();

    const result = await searchRespondent({
      surveyNumber: SURVEY_NUMBER,
      sessionId: SESSION_ID,
      pass,
    });

    console.log("Threat Potential:", result.Respondent?.threat_potential);
    console.log("Duplicate Flag:", result.Surveys?.[0]?.flag);
    console.log("Survey Number:", result.Surveys?.[0]?.survey_number);
    ```
  </Tab>
</Tabs>

<Tip>
  Prefer `POST` for this integration. The `pass_params()` value is added to the request, and a `GET` request puts it in the URL, which can run into URL length limits.
</Tip>

## Full HTML pages

If you want a complete page you can open in a browser, use one of the samples below. Replace the placeholder values at the top of the script, then serve the file over HTTP, for example with `python3 -m http.server`.

<AccordionGroup>
  <Accordion title="Advanced POST integration (HTML)">
    ```html advanced-post-integration.html theme={null}
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Research Defender – Advanced POST Integration (JS Library)</title>
    </head>
    <body>
      <h1>Research Defender – Advanced POST Integration (JS Library)</h1>
      <pre id="output">Loading…</pre>

      <script type="module">
        // ---------------------------------------------------------------
        // Configuration
        // ---------------------------------------------------------------
        const ENVIRONMENT = "prod"; // "staging" or "prod"
        const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
        const LIBRARY_VERSION = "2.0.1";

        // Replace with the values for the respondent you are checking.
        const SURVEY_NUMBER = "YOUR_SURVEY_NUMBER"; // sy_nr
        const SESSION_ID = "YOUR_SESSION_ID";       // sn_ud

        // Time to let the library collect its datasets before calling search.
        const DATASET_READY_DELAY_MS = 1000;

        const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
        const LIBRARY_URL = `${BASE_URL}/static/js/rd_sys_util.min.js?v=${LIBRARY_VERSION}`;
        const SEARCH_ENDPOINT = `${BASE_URL}/api/v4/respondents/search/${PUBLISHABLE_KEY}`;

        // ---------------------------------------------------------------
        // Helpers
        // ---------------------------------------------------------------
        const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

        /**
         * Parses the API response body. If the body has a stray trailing
         * character that breaks JSON.parse, it is trimmed and parsing is retried.
         */
        function parseResponse(text) {
          try {
            return JSON.parse(text);
          } catch {
            return JSON.parse(text.slice(0, -1));
          }
        }

        /** Loads the library, starts data collection, and returns its pass-through key. */
        async function collectLibraryData() {
          const { init, pass_params } = await import(LIBRARY_URL);

          if (typeof init !== "function" || typeof pass_params !== "function") {
            throw new Error("Research Defender library did not load correctly.");
          }

          await init({ key: PUBLISHABLE_KEY, env: ENVIRONMENT });

          // Give the library time to finish collecting its datasets.
          await sleep(DATASET_READY_DELAY_MS);

          const pass = pass_params();
          console.log("[rd] pass_params computed:", pass ? `${pass.length} chars` : "empty");
          return pass;
        }

        /** Sends the POST search request and returns the parsed JSON result. */
        async function searchRespondent({ surveyNumber, sessionId, pass }) {
          const body = {
            sy_nr: surveyNumber,
            sn_ud: sessionId,
            df_details: "1",
            mfr: "1",
          };

          // The value returned by pass_params() is sent as a key with an empty
          // string value. JSON.stringify takes care of quoting and escaping.
          if (pass) {
            body[pass] = "";
          }

          const response = await fetch(SEARCH_ENDPOINT, {
            method: "POST",
            credentials: "include", // send Research Defender cookies
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(body),
          });

          if (!response.ok) {
            throw new Error(`Search request failed with status ${response.status}`);
          }

          return parseResponse(await response.text());
        }

        // ---------------------------------------------------------------
        // Run
        // ---------------------------------------------------------------
        const output = document.getElementById("output");

        try {
          const pass = await collectLibraryData();

          const result = await searchRespondent({
            surveyNumber: SURVEY_NUMBER,
            sessionId: SESSION_ID,
            pass,
          });

          console.log("Full results:", result);

          const survey = result.Surveys?.[0];
          output.textContent = [
            `Threat Potential: ${result.Respondent?.threat_potential}`,
            `Duplicate Flag:   ${survey?.flag}`,
            `Survey Number:    ${survey?.survey_number}`,
            "",
            "Full response:",
            JSON.stringify(result, null, 2),
          ].join("\n");
        } catch (error) {
          console.error(error);
          output.textContent = `Error: ${error.message}`;
        }
      </script>
    </body>
    </html>
    ```
  </Accordion>

  <Accordion title="Advanced GET integration (HTML)">
    ```html advanced-get-integration.html theme={null}
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Research Defender – Advanced GET Integration (JS Library)</title>
    </head>
    <body>
      <h1>Research Defender – Advanced GET Integration (JS Library)</h1>
      <pre id="output">Loading…</pre>

      <script type="module">
        // ---------------------------------------------------------------
        // Configuration
        // ---------------------------------------------------------------
        const ENVIRONMENT = "prod"; // "staging" or "prod"
        const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
        const LIBRARY_VERSION = "2.0.1";

        // Replace with the values for the respondent you are checking.
        const SURVEY_NUMBER = "YOUR_SURVEY_NUMBER"; // sy_nr
        const SESSION_ID = "YOUR_SESSION_ID";       // sn_ud

        // Time to let the library collect its datasets before calling search.
        const DATASET_READY_DELAY_MS = 1000;

        const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
        const LIBRARY_URL = `${BASE_URL}/static/js/rd_sys_util.min.js?v=${LIBRARY_VERSION}`;
        const SEARCH_ENDPOINT = `${BASE_URL}/api/v4/respondents/search/${PUBLISHABLE_KEY}`;

        // ---------------------------------------------------------------
        // Helpers
        // ---------------------------------------------------------------
        const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

        /**
         * Parses the API response body. If the body has a stray trailing
         * character that breaks JSON.parse, it is trimmed and parsing is retried.
         */
        function parseResponse(text) {
          try {
            return JSON.parse(text);
          } catch {
            return JSON.parse(text.slice(0, -1));
          }
        }

        /** Loads the library, starts data collection, and returns its pass-through key. */
        async function collectLibraryData() {
          const { init, pass_params } = await import(LIBRARY_URL);

          if (typeof init !== "function" || typeof pass_params !== "function") {
            throw new Error("Research Defender library did not load correctly.");
          }

          await init({ key: PUBLISHABLE_KEY, env: ENVIRONMENT });

          // Give the library time to finish collecting its datasets.
          await sleep(DATASET_READY_DELAY_MS);

          const pass = pass_params();
          console.log("[rd] pass_params computed:", pass ? `${pass.length} chars` : "empty");
          return pass;
        }

        /** Sends the GET search request and returns the parsed JSON result. */
        async function searchRespondent({ surveyNumber, sessionId, pass }) {
          const params = new URLSearchParams({
            sy_nr: surveyNumber,
            sn_ud: sessionId,
            df_details: "1",
            mfr: "1",
          });

          // The value returned by pass_params() is sent as a parameter name with
          // an empty value. URLSearchParams takes care of URL encoding.
          if (pass) {
            params.set(pass, "");
          }

          const response = await fetch(`${SEARCH_ENDPOINT}?${params}`, {
            method: "GET",
            credentials: "include", // send Research Defender cookies
          });

          if (!response.ok) {
            throw new Error(`Search request failed with status ${response.status}`);
          }

          return parseResponse(await response.text());
        }

        // ---------------------------------------------------------------
        // Run
        // ---------------------------------------------------------------
        const output = document.getElementById("output");

        try {
          const pass = await collectLibraryData();

          const result = await searchRespondent({
            surveyNumber: SURVEY_NUMBER,
            sessionId: SESSION_ID,
            pass,
          });

          console.log("Full results:", result);

          const survey = result.Surveys?.[0];
          output.textContent = [
            `Threat Potential: ${result.Respondent?.threat_potential}`,
            `Duplicate Flag:   ${survey?.flag}`,
            `Survey Number:    ${survey?.survey_number}`,
            "",
            "Full response:",
            JSON.stringify(result, null, 2),
          ].join("\n");
        } catch (error) {
          console.error(error);
          output.textContent = `Error: ${error.message}`;
        }
      </script>
    </body>
    </html>
    ```
  </Accordion>
</AccordionGroup>

## Tuning the wait interval

The default 1-second wait gives the library time to collect its datasets. If results look incomplete on slower devices, increase `DATASET_READY_DELAY_MS`. Avoid setting it higher than your survey flow can tolerate, since respondents wait for it before the search call is made.

## Handling errors

The samples throw an error if the library fails to load or if the search returns a non-`200` status. Decide in advance how your survey should respond, for example letting the respondent continue and logging the failure.

<Tip>
  Handle library load failures separately from search failures. An ad blocker or network issue can prevent the library from loading even when the search API is reachable.
</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.