Overview
The Review API evaluates an open-ended text answer together with data about how it was entered. The Research Defender review library runs in the respondent’s browser and collects that data, including pasted text, timestamps, and behavioral data. When the respondent submits, your page sends the answer and the collected data to the Review API. You can send the request withPOST or GET. Both use the same fields.
Use
POST. Answers and behavioral data can be long and may contain line breaks, quotes, and personal content. With GET, all of it goes into the URL, which has length limits and is often written to server and proxy logs.How it works
1
Load the library
Add the review library script to the
<head> of your page.2
Add the page elements
Add the answer field, a submit button, and the fields the library writes its data into.
3
Create the library
Create
scReviewLib when the page has loaded, passing your environment URL, publishable key, the element IDs, and your listeners.4
Send the review on submit
In
submitEventListener, read the answer and the collected data from the page and call the Review API.Prerequisites
- A Research Defender publishable key
- A page served over HTTP(S). Opening the file directly from disk (
file://) will not work.
Step 1: Load the library
Add the library to the<head> of your page.
<script src="https://prod.rtymgt.com/static/js/review/sc_review_lib_secured_v1-3.min.js?v=1.0.2.1"></script>
Use the same environment in the library URL and in your code. For example, if the script loads from
prod.rtymgt.com, set ENVIRONMENT to "prod". A mix of environments is a common cause of failures.Step 2: Add the page elements
The library reads from and writes to elements on your page. Each one needs an ID, which you pass to the library.| Element | Purpose | Sample ID |
|---|---|---|
| Answer field | The respondent’s answer | text_element |
| Submit button | The button the respondent clicks | submit_entry |
| Pasted text field | The library writes pasted text here | encoded_pasted_text_element |
| Page load timestamp field | The library writes the page load time here | page_load_timestamp_text_element |
| Text entry start field | The library writes when the respondent started typing | answer_start_timestamp_text_element |
| Submit timestamp field | The library writes the submit time here | submit_timestamp_text_element |
| Behavioral data field | The library writes behavioral data here | behavioral_data_text_element |
The five fields the library writes to are meant to be hidden. The sample shows them so you can see the collected data. In production, use
type="hidden" for them.Step 3: Create the library and send the review
CreatescReviewLib after the page has loaded. The constructor takes these arguments, in order.
| Argument | Description |
|---|---|
domain | Your environment URL, for example https://prod.rtymgt.com |
publishableKey | Your Research Defender publishable key |
elementIds | An object with the element IDs from the table above |
pageLoadListener | Called when the page load is recorded |
copyPasteListener | Called when text is pasted |
textEventListener | Called when the respondent starts entering text |
submitEventListener | Called when the respondent submits. Send the review request here. |
Review API request
string
GET or POST https://{environment}.rtymgt.com/api/v4/respondents/review/{publishable_key}POST request, send the fields as a JSON body. For a GET request, send them as query parameters.
string
required
The question ID.
string
required
The question text shown to the respondent.
string
required
The respondent’s answer.
string
The pasted text collected by the library.
number
A number. The sample uses
5.string
The page load timestamp collected by the library.
string
The timestamp of when the respondent started entering the answer, collected by the library.
string
The submit timestamp collected by the library.
string
The behavioral data collected by the library.
Send the request with credentials (
credentials: "include") so Research Defender cookies are included.Examples
- POST
- GET
review-post.js
// ---------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------
const ENVIRONMENT = "prod"; // "staging" or "prod"
const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
const QUESTION_ID = "1"; // q_id
const S_TEXT_LENGTH = 5; // s_text_length
const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
const REVIEW_ENDPOINT = `${BASE_URL}/api/v4/respondents/review/${PUBLISHABLE_KEY}`;
// The IDs of the page elements the library reads from and writes to.
const ELEMENT_IDS = {
textElementId: "text_element",
pastedTextElementId: "encoded_pasted_text_element",
pageLoadTextTimeElementId: "page_load_timestamp_text_element",
entryStartTextTimeElementId: "answer_start_timestamp_text_element",
submitTextTimeElementId: "submit_timestamp_text_element",
submitButtonElementId: "submit_entry",
behavioralDataElementId: "behavioral_data_text_element",
};
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
const byId = (id) => document.getElementById(id);
/**
* Parses a 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));
}
}
/** Collects the review data from the page. The library fills in the hidden fields. */
function buildPayload() {
return {
q_id: QUESTION_ID,
question: byId("question_text").textContent.trim(),
text: byId(ELEMENT_IDS.textElementId).value,
c_text: byId(ELEMENT_IDS.pastedTextElementId).value,
s_text_length: S_TEXT_LENGTH,
t_page_load: byId(ELEMENT_IDS.pageLoadTextTimeElementId).value,
t_text_typed: byId(ELEMENT_IDS.entryStartTextTimeElementId).value,
t_submit: byId(ELEMENT_IDS.submitTextTimeElementId).value,
p_fields: byId(ELEMENT_IDS.behavioralDataElementId).value,
};
}
/** Reads a response, raising a helpful error for non-200 results. */
async function readResponse(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(`Review request failed with status ${response.status}: ${text.slice(0, 300)}`);
}
return parseResponse(text);
}
/** Sends the review as a POST request with a JSON body. */
async function sendReview(payload) {
const response = await fetch(REVIEW_ENDPOINT, {
method: "POST",
credentials: "include", // send Research Defender cookies
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload), // handles quotes, line breaks and special characters
});
return readResponse(response);
}
// ---------------------------------------------------------------
// Library listeners
// ---------------------------------------------------------------
// Optional hooks. The library also fills in the hidden fields on its own.
const onPageLoad = (time, error) => {};
const onCopyPaste = (pastedText, error) => {};
const onTextEvent = (answerTime, error) => {};
// Called by the library when the respondent submits. By now the library
// has filled in the submit timestamp and behavioral data fields.
async function onSubmit(submitTime, error) {
if (error) {
console.error("Review library error:", error);
}
const answer = byId(ELEMENT_IDS.textElementId).value;
const button = byId(ELEMENT_IDS.submitButtonElementId);
const output = byId("output");
if (!answer.trim()) {
alert("Please provide your answer.");
return;
}
button.disabled = true;
button.textContent = "Please wait...";
try {
const result = await sendReview(buildPayload());
console.log("Review result:", result);
output.textContent = JSON.stringify(result, null, 2);
} catch (err) {
console.error(err);
output.textContent = `Error: ${err.message}`;
} finally {
// Always re-enable the button, whether the request worked or not.
button.disabled = false;
button.textContent = "Submit";
}
}
// ---------------------------------------------------------------
// Start the library
// ---------------------------------------------------------------
window.addEventListener("load", () => {
try {
if (typeof scReviewLib === "undefined") {
throw new Error("The review library did not load. Check the script tag in the <head>.");
}
new scReviewLib(
BASE_URL,
PUBLISHABLE_KEY,
ELEMENT_IDS,
onPageLoad,
onCopyPaste,
onTextEvent,
onSubmit
);
} catch (error) {
console.error(error);
byId("output").textContent = `Error: ${error.message}`;
}
});
review-get.js
// ---------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------
const ENVIRONMENT = "prod"; // "staging" or "prod"
const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
const QUESTION_ID = "1"; // q_id
const S_TEXT_LENGTH = 5; // s_text_length
const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
const REVIEW_ENDPOINT = `${BASE_URL}/api/v4/respondents/review/${PUBLISHABLE_KEY}`;
// The IDs of the page elements the library reads from and writes to.
const ELEMENT_IDS = {
textElementId: "text_element",
pastedTextElementId: "encoded_pasted_text_element",
pageLoadTextTimeElementId: "page_load_timestamp_text_element",
entryStartTextTimeElementId: "answer_start_timestamp_text_element",
submitTextTimeElementId: "submit_timestamp_text_element",
submitButtonElementId: "submit_entry",
behavioralDataElementId: "behavioral_data_text_element",
};
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
const byId = (id) => document.getElementById(id);
/**
* Parses a 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));
}
}
/** Collects the review data from the page. The library fills in the hidden fields. */
function buildPayload() {
return {
q_id: QUESTION_ID,
question: byId("question_text").textContent.trim(),
text: byId(ELEMENT_IDS.textElementId).value,
c_text: byId(ELEMENT_IDS.pastedTextElementId).value,
s_text_length: S_TEXT_LENGTH,
t_page_load: byId(ELEMENT_IDS.pageLoadTextTimeElementId).value,
t_text_typed: byId(ELEMENT_IDS.entryStartTextTimeElementId).value,
t_submit: byId(ELEMENT_IDS.submitTextTimeElementId).value,
p_fields: byId(ELEMENT_IDS.behavioralDataElementId).value,
};
}
/** Reads a response, raising a helpful error for non-200 results. */
async function readResponse(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(`Review request failed with status ${response.status}: ${text.slice(0, 300)}`);
}
return parseResponse(text);
}
/** Sends the review as a GET request with URL parameters. */
async function sendReview(payload) {
const params = new URLSearchParams();
for (const [name, value] of Object.entries(payload)) {
params.set(name, String(value));
}
const response = await fetch(`${REVIEW_ENDPOINT}?${params}`, {
method: "GET",
credentials: "include", // send Research Defender cookies
});
return readResponse(response);
}
// ---------------------------------------------------------------
// Library listeners
// ---------------------------------------------------------------
// Optional hooks. The library also fills in the hidden fields on its own.
const onPageLoad = (time, error) => {};
const onCopyPaste = (pastedText, error) => {};
const onTextEvent = (answerTime, error) => {};
// Called by the library when the respondent submits. By now the library
// has filled in the submit timestamp and behavioral data fields.
async function onSubmit(submitTime, error) {
if (error) {
console.error("Review library error:", error);
}
const answer = byId(ELEMENT_IDS.textElementId).value;
const button = byId(ELEMENT_IDS.submitButtonElementId);
const output = byId("output");
if (!answer.trim()) {
alert("Please provide your answer.");
return;
}
button.disabled = true;
button.textContent = "Please wait...";
try {
const result = await sendReview(buildPayload());
console.log("Review result:", result);
output.textContent = JSON.stringify(result, null, 2);
} catch (err) {
console.error(err);
output.textContent = `Error: ${err.message}`;
} finally {
// Always re-enable the button, whether the request worked or not.
button.disabled = false;
button.textContent = "Submit";
}
}
// ---------------------------------------------------------------
// Start the library
// ---------------------------------------------------------------
window.addEventListener("load", () => {
try {
if (typeof scReviewLib === "undefined") {
throw new Error("The review library did not load. Check the script tag in the <head>.");
}
new scReviewLib(
BASE_URL,
PUBLISHABLE_KEY,
ELEMENT_IDS,
onPageLoad,
onCopyPaste,
onTextEvent,
onSubmit
);
} catch (error) {
console.error(error);
byId("output").textContent = `Error: ${error.message}`;
}
});
sendReview(). The POST version sends a JSON body built with JSON.stringify, which correctly handles quotes, line breaks, and special characters in the answer. The GET version encodes the same fields into the URL with URLSearchParams.
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(S).Review integration with POST (HTML)
Review integration with POST (HTML)
review-post-integration.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Research Defender – Review Integration (POST)</title>
<!--
Review library. Use the same environment host here and in ENVIRONMENT below
("staging" or "prod"). Load it in the <head>, as shown.
-->
<script src="https://prod.rtymgt.com/static/js/review/sc_review_lib_secured_v1-3.min.js?v=1.0.2.1"></script>
<style>
body { font-family: system-ui, sans-serif; background: #ebebeb; margin: 0; padding: 2rem; }
.card { max-width: 720px; margin: 0 auto; background: #fff; padding: 2rem; border-radius: 0.5rem; }
label { display: block; font-weight: 600; margin-bottom: 0.5rem; }
textarea { width: 100%; box-sizing: border-box; }
button { background: #25274d; color: #fff; font-weight: 600; border: 0; padding: 0.6rem 1.5rem; border-radius: 0.25rem; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: default; }
pre { background: #f6f6f6; padding: 1rem; overflow-x: auto; white-space: pre-wrap; }
details { margin-top: 1.5rem; }
details input { width: 100%; box-sizing: border-box; margin-bottom: 0.75rem; }
</style>
</head>
<body>
<div class="card">
<h1>Research Defender – Review Integration (POST)</h1>
<label id="question_text" for="text_element">What is your favorite holiday?</label>
<textarea id="text_element" rows="5"></textarea>
<p><button type="button" id="submit_entry">Submit</button></p>
<pre id="output"></pre>
<!-- The library writes the data it collects into these fields. -->
<details>
<summary>Data collected by the library (hidden fields)</summary>
<p>These are visible here for demonstration. In production, use <code>type="hidden"</code> for them.</p>
<label for="encoded_pasted_text_element">Pasted text</label>
<input type="text" id="encoded_pasted_text_element" readonly />
<label for="page_load_timestamp_text_element">Page load timestamp</label>
<input type="text" id="page_load_timestamp_text_element" readonly />
<label for="answer_start_timestamp_text_element">Start of text entry timestamp</label>
<input type="text" id="answer_start_timestamp_text_element" readonly />
<label for="submit_timestamp_text_element">Submit timestamp</label>
<input type="text" id="submit_timestamp_text_element" readonly />
<label for="behavioral_data_text_element">Behavioral data</label>
<input type="text" id="behavioral_data_text_element" readonly />
</details>
</div>
<script>
// ---------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------
const ENVIRONMENT = "prod"; // "staging" or "prod"
const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
const QUESTION_ID = "1"; // q_id
const S_TEXT_LENGTH = 5; // s_text_length
const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
const REVIEW_ENDPOINT = `${BASE_URL}/api/v4/respondents/review/${PUBLISHABLE_KEY}`;
// The IDs of the page elements the library reads from and writes to.
const ELEMENT_IDS = {
textElementId: "text_element",
pastedTextElementId: "encoded_pasted_text_element",
pageLoadTextTimeElementId: "page_load_timestamp_text_element",
entryStartTextTimeElementId: "answer_start_timestamp_text_element",
submitTextTimeElementId: "submit_timestamp_text_element",
submitButtonElementId: "submit_entry",
behavioralDataElementId: "behavioral_data_text_element",
};
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
const byId = (id) => document.getElementById(id);
/**
* Parses a 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));
}
}
/** Collects the review data from the page. The library fills in the hidden fields. */
function buildPayload() {
return {
q_id: QUESTION_ID,
question: byId("question_text").textContent.trim(),
text: byId(ELEMENT_IDS.textElementId).value,
c_text: byId(ELEMENT_IDS.pastedTextElementId).value,
s_text_length: S_TEXT_LENGTH,
t_page_load: byId(ELEMENT_IDS.pageLoadTextTimeElementId).value,
t_text_typed: byId(ELEMENT_IDS.entryStartTextTimeElementId).value,
t_submit: byId(ELEMENT_IDS.submitTextTimeElementId).value,
p_fields: byId(ELEMENT_IDS.behavioralDataElementId).value,
};
}
/** Reads a response, raising a helpful error for non-200 results. */
async function readResponse(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(`Review request failed with status ${response.status}: ${text.slice(0, 300)}`);
}
return parseResponse(text);
}
/** Sends the review as a POST request with a JSON body. */
async function sendReview(payload) {
const response = await fetch(REVIEW_ENDPOINT, {
method: "POST",
credentials: "include", // send Research Defender cookies
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload), // handles quotes, line breaks and special characters
});
return readResponse(response);
}
// ---------------------------------------------------------------
// Library listeners
// ---------------------------------------------------------------
// Optional hooks. The library also fills in the hidden fields on its own.
const onPageLoad = (time, error) => {};
const onCopyPaste = (pastedText, error) => {};
const onTextEvent = (answerTime, error) => {};
// Called by the library when the respondent submits. By now the library
// has filled in the submit timestamp and behavioral data fields.
async function onSubmit(submitTime, error) {
if (error) {
console.error("Review library error:", error);
}
const answer = byId(ELEMENT_IDS.textElementId).value;
const button = byId(ELEMENT_IDS.submitButtonElementId);
const output = byId("output");
if (!answer.trim()) {
alert("Please provide your answer.");
return;
}
button.disabled = true;
button.textContent = "Please wait...";
try {
const result = await sendReview(buildPayload());
console.log("Review result:", result);
output.textContent = JSON.stringify(result, null, 2);
} catch (err) {
console.error(err);
output.textContent = `Error: ${err.message}`;
} finally {
// Always re-enable the button, whether the request worked or not.
button.disabled = false;
button.textContent = "Submit";
}
}
// ---------------------------------------------------------------
// Start the library
// ---------------------------------------------------------------
window.addEventListener("load", () => {
try {
if (typeof scReviewLib === "undefined") {
throw new Error("The review library did not load. Check the script tag in the <head>.");
}
new scReviewLib(
BASE_URL,
PUBLISHABLE_KEY,
ELEMENT_IDS,
onPageLoad,
onCopyPaste,
onTextEvent,
onSubmit
);
} catch (error) {
console.error(error);
byId("output").textContent = `Error: ${error.message}`;
}
});
</script>
</body>
</html>
Review integration with GET (HTML)
Review integration with GET (HTML)
review-get-integration.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Research Defender – Review Integration (GET)</title>
<!--
Review library. Use the same environment host here and in ENVIRONMENT below
("staging" or "prod"). Load it in the <head>, as shown.
-->
<script src="https://prod.rtymgt.com/static/js/review/sc_review_lib_secured_v1-3.min.js?v=1.0.2.1"></script>
<style>
body { font-family: system-ui, sans-serif; background: #ebebeb; margin: 0; padding: 2rem; }
.card { max-width: 720px; margin: 0 auto; background: #fff; padding: 2rem; border-radius: 0.5rem; }
label { display: block; font-weight: 600; margin-bottom: 0.5rem; }
textarea { width: 100%; box-sizing: border-box; }
button { background: #25274d; color: #fff; font-weight: 600; border: 0; padding: 0.6rem 1.5rem; border-radius: 0.25rem; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: default; }
pre { background: #f6f6f6; padding: 1rem; overflow-x: auto; white-space: pre-wrap; }
details { margin-top: 1.5rem; }
details input { width: 100%; box-sizing: border-box; margin-bottom: 0.75rem; }
</style>
</head>
<body>
<div class="card">
<h1>Research Defender – Review Integration (GET)</h1>
<label id="question_text" for="text_element">What is your favorite holiday?</label>
<textarea id="text_element" rows="5"></textarea>
<p><button type="button" id="submit_entry">Submit</button></p>
<pre id="output"></pre>
<!-- The library writes the data it collects into these fields. -->
<details>
<summary>Data collected by the library (hidden fields)</summary>
<p>These are visible here for demonstration. In production, use <code>type="hidden"</code> for them.</p>
<label for="encoded_pasted_text_element">Pasted text</label>
<input type="text" id="encoded_pasted_text_element" readonly />
<label for="page_load_timestamp_text_element">Page load timestamp</label>
<input type="text" id="page_load_timestamp_text_element" readonly />
<label for="answer_start_timestamp_text_element">Start of text entry timestamp</label>
<input type="text" id="answer_start_timestamp_text_element" readonly />
<label for="submit_timestamp_text_element">Submit timestamp</label>
<input type="text" id="submit_timestamp_text_element" readonly />
<label for="behavioral_data_text_element">Behavioral data</label>
<input type="text" id="behavioral_data_text_element" readonly />
</details>
</div>
<script>
// ---------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------
const ENVIRONMENT = "prod"; // "staging" or "prod"
const PUBLISHABLE_KEY = "YOUR_PUBLISHABLE_KEY";
const QUESTION_ID = "1"; // q_id
const S_TEXT_LENGTH = 5; // s_text_length
const BASE_URL = `https://${ENVIRONMENT}.rtymgt.com`;
const REVIEW_ENDPOINT = `${BASE_URL}/api/v4/respondents/review/${PUBLISHABLE_KEY}`;
// The IDs of the page elements the library reads from and writes to.
const ELEMENT_IDS = {
textElementId: "text_element",
pastedTextElementId: "encoded_pasted_text_element",
pageLoadTextTimeElementId: "page_load_timestamp_text_element",
entryStartTextTimeElementId: "answer_start_timestamp_text_element",
submitTextTimeElementId: "submit_timestamp_text_element",
submitButtonElementId: "submit_entry",
behavioralDataElementId: "behavioral_data_text_element",
};
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
const byId = (id) => document.getElementById(id);
/**
* Parses a 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));
}
}
/** Collects the review data from the page. The library fills in the hidden fields. */
function buildPayload() {
return {
q_id: QUESTION_ID,
question: byId("question_text").textContent.trim(),
text: byId(ELEMENT_IDS.textElementId).value,
c_text: byId(ELEMENT_IDS.pastedTextElementId).value,
s_text_length: S_TEXT_LENGTH,
t_page_load: byId(ELEMENT_IDS.pageLoadTextTimeElementId).value,
t_text_typed: byId(ELEMENT_IDS.entryStartTextTimeElementId).value,
t_submit: byId(ELEMENT_IDS.submitTextTimeElementId).value,
p_fields: byId(ELEMENT_IDS.behavioralDataElementId).value,
};
}
/** Reads a response, raising a helpful error for non-200 results. */
async function readResponse(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(`Review request failed with status ${response.status}: ${text.slice(0, 300)}`);
}
return parseResponse(text);
}
/** Sends the review as a GET request with URL parameters. */
async function sendReview(payload) {
const params = new URLSearchParams();
for (const [name, value] of Object.entries(payload)) {
params.set(name, String(value));
}
const response = await fetch(`${REVIEW_ENDPOINT}?${params}`, {
method: "GET",
credentials: "include", // send Research Defender cookies
});
return readResponse(response);
}
// ---------------------------------------------------------------
// Library listeners
// ---------------------------------------------------------------
// Optional hooks. The library also fills in the hidden fields on its own.
const onPageLoad = (time, error) => {};
const onCopyPaste = (pastedText, error) => {};
const onTextEvent = (answerTime, error) => {};
// Called by the library when the respondent submits. By now the library
// has filled in the submit timestamp and behavioral data fields.
async function onSubmit(submitTime, error) {
if (error) {
console.error("Review library error:", error);
}
const answer = byId(ELEMENT_IDS.textElementId).value;
const button = byId(ELEMENT_IDS.submitButtonElementId);
const output = byId("output");
if (!answer.trim()) {
alert("Please provide your answer.");
return;
}
button.disabled = true;
button.textContent = "Please wait...";
try {
const result = await sendReview(buildPayload());
console.log("Review result:", result);
output.textContent = JSON.stringify(result, null, 2);
} catch (err) {
console.error(err);
output.textContent = `Error: ${err.message}`;
} finally {
// Always re-enable the button, whether the request worked or not.
button.disabled = false;
button.textContent = "Submit";
}
}
// ---------------------------------------------------------------
// Start the library
// ---------------------------------------------------------------
window.addEventListener("load", () => {
try {
if (typeof scReviewLib === "undefined") {
throw new Error("The review library did not load. Check the script tag in the <head>.");
}
new scReviewLib(
BASE_URL,
PUBLISHABLE_KEY,
ELEMENT_IDS,
onPageLoad,
onCopyPaste,
onTextEvent,
onSubmit
);
} catch (error) {
console.error(error);
byId("output").textContent = `Error: ${error.message}`;
}
});
</script>
</body>
</html>
Handling errors
The samples show the API’s error message on the page, write it to the browser console, and always re-enable the submit button so the respondent is never left waiting. Decide in advance what your survey should do when a review fails.Troubleshooting
| Symptom | Likely cause |
|---|---|
| The submit button stays on “Please wait” | The request failed and the page did not handle the error. Always re-enable the button in a finally block, as the samples do. |
400 response | The request is missing a required field or is not valid. Check the response message, and build a POST body with JSON.stringify rather than by joining strings. |
The question text is empty or undefined | A <label> has no value property. Read the question with textContent. |
| Hidden fields stay empty | The library did not start. Check the script tag, that scReviewLib is created after the page loads, and that every element ID matches the page. |
| The library fails to load or the request is rejected | The library URL and your ENVIRONMENT setting use different environments, or the publishable key belongs to another environment. |
| Requests fail only with long answers | You are using GET and the URL is too long. Use POST. |
