#Review questions
GET /review-questions
List the extra questions reviewers are asked beside the star rating, in the order they appear on the form. Archived questions are included.
Requires the reviews:read permission.
Query Parameters
limitnumberThe number of records to return.
cursorstringA pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.
sortsortOrder | -sortOrder | label | -label | createdAt | -createdAtThe sort order. Prefix a field with - to sort descending. Defaults to sortOrder.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, label, type, required, showOnWidget, archivedAt, createdAt. See Filtering for the syntax.
includestringA comma-separated list of extra fields to compute and return. Each one costs an additional query, so ask only for what you need. Available: answersCount.
curl \
"https://api.cascade.dev/review-questions" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getReviewQuestions } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviewQuestions()require "net/http"
uri = URI("https://api.cascade.dev/review-questions")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/review-questions",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"config": {
"steps": "...",
"lowLabel": "...",
"highLabel": "...",
"middleLabel": "..."
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archivedAt": "2025-01-15T09:30:00Z",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"answersCount": 0
}
],
"nextCursor": "string"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the question."
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"steps": {
"type": "integer",
"minimum": 3,
"maximum": 7,
"description": "How many steps the scale has, from 3 to 7. Answers are the step number, counting from 1."
},
"lowLabel": {
"type": "string",
"minLength": 1,
"description": "What step 1 means, such as `Runs small`."
},
"highLabel": {
"type": "string",
"minLength": 1,
"description": "What the last step means, such as `Runs large`."
},
"middleLabel": {
"description": "What the middle step means, such as `True to size`. Only a scale with an odd number of steps has one, and the steps in between are never labelled.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": ["steps", "lowLabel", "highLabel"],
"additionalProperties": false,
"description": "The shape of a `scale` question."
},
{
"type": "object",
"properties": {
"multiple": {
"type": "boolean",
"description": "Whether the reviewer can pick more than one option. False means exactly one."
},
"options": {
"minItems": 2,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Stable identifier for the option, unique within the question. Answers are recorded against it, so it never changes."
},
"label": {
"type": "string",
"minLength": 1,
"description": "What the option says on the form."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows this option's share of the answers. Turn it off to surface only the interesting half of a yes/no question."
},
"archivedAt": {
"description": "When the option stopped being offered, or null while it is still on the form. Archived options keep the answers already recorded against them.",
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": ["id", "label", "showOnWidget"],
"additionalProperties": false,
"description": "One option of a `multi_choice` question."
},
"description": "The options, in the order they are shown."
}
},
"required": ["multiple", "options"],
"additionalProperties": false,
"description": "The shape of a `multi_choice` question."
}
],
"description": "The question's settings, whose shape follows its `type`: a `scale` question carries `steps` and its end labels, a `multi_choice` question carries `multiple` and its `options`."
},
"required": {
"type": "boolean",
"description": "Whether a reviewer has to answer before the form can be submitted."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows what reviewers answered. A hidden question is still asked."
},
"sortOrder": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Where the question sits on the form. Lower numbers are asked first."
},
"productFilter": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
],
"description": "A JSON filter document limiting the question to matching products, using the same fields as the products list endpoint. Null or empty asks it about every product."
},
"archivedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the question stopped being asked, or null while it is still live. Answers already given keep showing."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
},
"description": "Free-form string key/value pairs for your own data. Individual keys are filterable with a dot path, for example `metadata.category`."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was last changed."
},
"answersCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews have answered the question. Once this is above zero the question's type, its number of steps and its existing option IDs are locked, and it can no longer be deleted."
}
},
"required": [
"id",
"label",
"type",
"config",
"required",
"showOnWidget",
"sortOrder",
"productFilter",
"archivedAt",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
"description": "The page of records, in the requested sort order."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Pass this back as `cursor` to fetch the next page. Null once the last page has been returned."
}
},
"required": ["data", "nextCursor"],
"additionalProperties": false
}POST /review-questions
Add a question to the review form. A scale question asks for one of a run of steps between two labelled ends; a multi_choice question offers a list of options, one or several of which can be picked. Give it a productFilter to ask it only about part of the catalog.
Requires the reviews:write permission.
Body Parameters
dataobjectrequiredThe question to add.
curl \
-X POST \
"https://api.cascade.dev/review-questions" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"metadata": "..."
}
}'import { client } from "@cascade-commerce/api/admin/client"
import { postReviewQuestions } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postReviewQuestions({
body: {
data: {
label: "string",
type: "scale",
config: {
steps: 0,
lowLabel: "string",
highLabel: "string",
middleLabel: "string",
},
required: true,
showOnWidget: true,
sortOrder: 0,
productFilter: "...",
metadata: "...",
},
},
})require "net/http"
uri = URI("https://api.cascade.dev/review-questions")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"metadata": "..."
}
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"metadata": "..."
}
}"""
req = Request(
"https://api.cascade.dev/review-questions",
data=body.encode(),
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archivedAt": "2025-01-15T09:30:00Z",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"answersCount": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the question."
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"steps": {
"type": "integer",
"minimum": 3,
"maximum": 7,
"description": "How many steps the scale has, from 3 to 7. Answers are the step number, counting from 1."
},
"lowLabel": {
"type": "string",
"minLength": 1,
"description": "What step 1 means, such as `Runs small`."
},
"highLabel": {
"type": "string",
"minLength": 1,
"description": "What the last step means, such as `Runs large`."
},
"middleLabel": {
"description": "What the middle step means, such as `True to size`. Only a scale with an odd number of steps has one, and the steps in between are never labelled.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": ["steps", "lowLabel", "highLabel"],
"additionalProperties": false,
"description": "The shape of a `scale` question."
},
{
"type": "object",
"properties": {
"multiple": {
"type": "boolean",
"description": "Whether the reviewer can pick more than one option. False means exactly one."
},
"options": {
"minItems": 2,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Stable identifier for the option, unique within the question. Answers are recorded against it, so it never changes."
},
"label": {
"type": "string",
"minLength": 1,
"description": "What the option says on the form."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows this option's share of the answers. Turn it off to surface only the interesting half of a yes/no question."
},
"archivedAt": {
"description": "When the option stopped being offered, or null while it is still on the form. Archived options keep the answers already recorded against them.",
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": ["id", "label", "showOnWidget"],
"additionalProperties": false,
"description": "One option of a `multi_choice` question."
},
"description": "The options, in the order they are shown."
}
},
"required": ["multiple", "options"],
"additionalProperties": false,
"description": "The shape of a `multi_choice` question."
}
],
"description": "The question's settings, whose shape follows its `type`: a `scale` question carries `steps` and its end labels, a `multi_choice` question carries `multiple` and its `options`."
},
"required": {
"type": "boolean",
"description": "Whether a reviewer has to answer before the form can be submitted."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows what reviewers answered. A hidden question is still asked."
},
"sortOrder": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Where the question sits on the form. Lower numbers are asked first."
},
"productFilter": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
],
"description": "A JSON filter document limiting the question to matching products, using the same fields as the products list endpoint. Null or empty asks it about every product."
},
"archivedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the question stopped being asked, or null while it is still live. Answers already given keep showing."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
},
"description": "Free-form string key/value pairs for your own data. Individual keys are filterable with a dot path, for example `metadata.category`."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was last changed."
},
"answersCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews have answered the question. Once this is above zero the question's type, its number of steps and its existing option IDs are locked, and it can no longer be deleted."
}
},
"required": [
"id",
"label",
"type",
"config",
"required",
"showOnWidget",
"sortOrder",
"productFilter",
"archivedAt",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The new question."
}GET /review-questions/{id}
Get a single review question by ID.
Requires the reviews:read permission.
Path Parameters
idstring (uuid)requiredThe ID of the review question.
Query Parameters
includestringA comma-separated list of extra fields to compute and return. Each one costs an additional query, so ask only for what you need. Available: answersCount.
curl \
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getReviewQuestionsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviewQuestionsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archivedAt": "2025-01-15T09:30:00Z",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"answersCount": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the question."
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"steps": {
"type": "integer",
"minimum": 3,
"maximum": 7,
"description": "How many steps the scale has, from 3 to 7. Answers are the step number, counting from 1."
},
"lowLabel": {
"type": "string",
"minLength": 1,
"description": "What step 1 means, such as `Runs small`."
},
"highLabel": {
"type": "string",
"minLength": 1,
"description": "What the last step means, such as `Runs large`."
},
"middleLabel": {
"description": "What the middle step means, such as `True to size`. Only a scale with an odd number of steps has one, and the steps in between are never labelled.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": ["steps", "lowLabel", "highLabel"],
"additionalProperties": false,
"description": "The shape of a `scale` question."
},
{
"type": "object",
"properties": {
"multiple": {
"type": "boolean",
"description": "Whether the reviewer can pick more than one option. False means exactly one."
},
"options": {
"minItems": 2,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Stable identifier for the option, unique within the question. Answers are recorded against it, so it never changes."
},
"label": {
"type": "string",
"minLength": 1,
"description": "What the option says on the form."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows this option's share of the answers. Turn it off to surface only the interesting half of a yes/no question."
},
"archivedAt": {
"description": "When the option stopped being offered, or null while it is still on the form. Archived options keep the answers already recorded against them.",
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": ["id", "label", "showOnWidget"],
"additionalProperties": false,
"description": "One option of a `multi_choice` question."
},
"description": "The options, in the order they are shown."
}
},
"required": ["multiple", "options"],
"additionalProperties": false,
"description": "The shape of a `multi_choice` question."
}
],
"description": "The question's settings, whose shape follows its `type`: a `scale` question carries `steps` and its end labels, a `multi_choice` question carries `multiple` and its `options`."
},
"required": {
"type": "boolean",
"description": "Whether a reviewer has to answer before the form can be submitted."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows what reviewers answered. A hidden question is still asked."
},
"sortOrder": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Where the question sits on the form. Lower numbers are asked first."
},
"productFilter": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
],
"description": "A JSON filter document limiting the question to matching products, using the same fields as the products list endpoint. Null or empty asks it about every product."
},
"archivedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the question stopped being asked, or null while it is still live. Answers already given keep showing."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
},
"description": "Free-form string key/value pairs for your own data. Individual keys are filterable with a dot path, for example `metadata.category`."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was last changed."
},
"answersCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews have answered the question. Once this is above zero the question's type, its number of steps and its existing option IDs are locked, and it can no longer be deleted."
}
},
"required": [
"id",
"label",
"type",
"config",
"required",
"showOnWidget",
"sortOrder",
"productFilter",
"archivedAt",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The requested question."
}PUT /review-questions/{id}
Edit a review question, or archive it so it stops being asked. Once reviewers have answered it, its type and its number of steps are fixed, and its options can be added to, relabeled or archived but never removed: the answers already recorded are read against them.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the question to update.
Body Parameters
dataobjectrequiredThe fields to update.
curl \
-X PUT \
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archived": true,
"metadata": "..."
}
}'import { client } from "@cascade-commerce/api/admin/client"
import { putReviewQuestionsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await putReviewQuestionsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
data: {
label: "string",
type: "scale",
config: {
steps: 0,
lowLabel: "string",
highLabel: "string",
middleLabel: "string",
},
required: true,
showOnWidget: true,
sortOrder: 0,
productFilter: "...",
archived: true,
metadata: "...",
},
},
})require "net/http"
uri = URI("https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archived": true,
"metadata": "..."
}
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"data": {
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archived": true,
"metadata": "..."
}
}"""
req = Request(
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="PUT",
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archivedAt": "2025-01-15T09:30:00Z",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"answersCount": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the question."
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"steps": {
"type": "integer",
"minimum": 3,
"maximum": 7,
"description": "How many steps the scale has, from 3 to 7. Answers are the step number, counting from 1."
},
"lowLabel": {
"type": "string",
"minLength": 1,
"description": "What step 1 means, such as `Runs small`."
},
"highLabel": {
"type": "string",
"minLength": 1,
"description": "What the last step means, such as `Runs large`."
},
"middleLabel": {
"description": "What the middle step means, such as `True to size`. Only a scale with an odd number of steps has one, and the steps in between are never labelled.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": ["steps", "lowLabel", "highLabel"],
"additionalProperties": false,
"description": "The shape of a `scale` question."
},
{
"type": "object",
"properties": {
"multiple": {
"type": "boolean",
"description": "Whether the reviewer can pick more than one option. False means exactly one."
},
"options": {
"minItems": 2,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Stable identifier for the option, unique within the question. Answers are recorded against it, so it never changes."
},
"label": {
"type": "string",
"minLength": 1,
"description": "What the option says on the form."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows this option's share of the answers. Turn it off to surface only the interesting half of a yes/no question."
},
"archivedAt": {
"description": "When the option stopped being offered, or null while it is still on the form. Archived options keep the answers already recorded against them.",
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": ["id", "label", "showOnWidget"],
"additionalProperties": false,
"description": "One option of a `multi_choice` question."
},
"description": "The options, in the order they are shown."
}
},
"required": ["multiple", "options"],
"additionalProperties": false,
"description": "The shape of a `multi_choice` question."
}
],
"description": "The question's settings, whose shape follows its `type`: a `scale` question carries `steps` and its end labels, a `multi_choice` question carries `multiple` and its `options`."
},
"required": {
"type": "boolean",
"description": "Whether a reviewer has to answer before the form can be submitted."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows what reviewers answered. A hidden question is still asked."
},
"sortOrder": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Where the question sits on the form. Lower numbers are asked first."
},
"productFilter": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
],
"description": "A JSON filter document limiting the question to matching products, using the same fields as the products list endpoint. Null or empty asks it about every product."
},
"archivedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the question stopped being asked, or null while it is still live. Answers already given keep showing."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
},
"description": "Free-form string key/value pairs for your own data. Individual keys are filterable with a dot path, for example `metadata.category`."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was last changed."
},
"answersCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews have answered the question. Once this is above zero the question's type, its number of steps and its existing option IDs are locked, and it can no longer be deleted."
}
},
"required": [
"id",
"label",
"type",
"config",
"required",
"showOnWidget",
"sortOrder",
"productFilter",
"archivedAt",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The updated question."
}DELETE /review-questions/{id}
Delete a review question. This is refused once any review has answered it, because deleting it would take those answers with it. Archive it instead to stop asking it while its answers keep showing.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the question to delete.
curl \
-X DELETE \
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { deleteReviewQuestionsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteReviewQuestionsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/review-questions/550e8400-e29b-41d4-a716-446655440000",
method="DELETE",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"config": {
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string"
},
"required": true,
"showOnWidget": true,
"sortOrder": 0,
"productFilter": "...",
"archivedAt": "2025-01-15T09:30:00Z",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"answersCount": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the question."
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"steps": {
"type": "integer",
"minimum": 3,
"maximum": 7,
"description": "How many steps the scale has, from 3 to 7. Answers are the step number, counting from 1."
},
"lowLabel": {
"type": "string",
"minLength": 1,
"description": "What step 1 means, such as `Runs small`."
},
"highLabel": {
"type": "string",
"minLength": 1,
"description": "What the last step means, such as `Runs large`."
},
"middleLabel": {
"description": "What the middle step means, such as `True to size`. Only a scale with an odd number of steps has one, and the steps in between are never labelled.",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
}
},
"required": ["steps", "lowLabel", "highLabel"],
"additionalProperties": false,
"description": "The shape of a `scale` question."
},
{
"type": "object",
"properties": {
"multiple": {
"type": "boolean",
"description": "Whether the reviewer can pick more than one option. False means exactly one."
},
"options": {
"minItems": 2,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Stable identifier for the option, unique within the question. Answers are recorded against it, so it never changes."
},
"label": {
"type": "string",
"minLength": 1,
"description": "What the option says on the form."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows this option's share of the answers. Turn it off to surface only the interesting half of a yes/no question."
},
"archivedAt": {
"description": "When the option stopped being offered, or null while it is still on the form. Archived options keep the answers already recorded against them.",
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": ["id", "label", "showOnWidget"],
"additionalProperties": false,
"description": "One option of a `multi_choice` question."
},
"description": "The options, in the order they are shown."
}
},
"required": ["multiple", "options"],
"additionalProperties": false,
"description": "The shape of a `multi_choice` question."
}
],
"description": "The question's settings, whose shape follows its `type`: a `scale` question carries `steps` and its end labels, a `multi_choice` question carries `multiple` and its `options`."
},
"required": {
"type": "boolean",
"description": "Whether a reviewer has to answer before the form can be submitted."
},
"showOnWidget": {
"type": "boolean",
"description": "Whether the storefront shows what reviewers answered. A hidden question is still asked."
},
"sortOrder": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Where the question sits on the form. Lower numbers are asked first."
},
"productFilter": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
],
"description": "A JSON filter document limiting the question to matching products, using the same fields as the products list endpoint. Null or empty asks it about every product."
},
"archivedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the question stopped being asked, or null while it is still live. Answers already given keep showing."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
},
"description": "Free-form string key/value pairs for your own data. Individual keys are filterable with a dot path, for example `metadata.category`."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the question was last changed."
},
"answersCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews have answered the question. Once this is above zero the question's type, its number of steps and its existing option IDs are locked, and it can no longer be deleted."
}
},
"required": [
"id",
"label",
"type",
"config",
"required",
"showOnWidget",
"sortOrder",
"productFilter",
"archivedAt",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The deleted question."
}