#Reviews
GET /reviews
List the organization's reviews, newest first by default. Includes unpublished reviews waiting on moderation. Supports search, filtering and cursor pagination.
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.
sortrating | -rating | createdAt | -createdAt | moderatedAt | -moderatedAtThe sort order. Prefix a field with - to sort descending. Defaults to -createdAt.
searchstringA free-text search term, matched case-insensitively against the record's most identifying fields. Combined with filter using AND.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, rating, published, flagged, moderatedAt, surveyId, ipAddress, browserFingerprint, metadata, createdAt, product.id, product.slug, product.title, product.metadata, product.createdAt, customer.id, customer.email, customer.firstName, customer.lastName, customer.metadata, customer.emailOptOut, customer.emailOptOutSource, customer.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: importJob, customer, product, response, files, answers, collisions.
curl \
"https://api.cascade.dev/reviews" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getReviews } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviews()require "net/http"
uri = URI("https://api.cascade.dev/reviews")
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/reviews",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"productId": "550e8400-e29b-41d4-a716-446655440000",
"productVariantId": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "550e8400-e29b-41d4-a716-446655440000",
"surveyId": "550e8400-e29b-41d4-a716-446655440000",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"flagged": true,
"flags": ["..."],
"moderatedAt": "2025-01-15T09:30:00Z",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"importJob": {
"id": "...",
"type": "...",
"action": "...",
"row": "...",
"createdAt": "..."
},
"customer": {
"id": "...",
"email": "...",
"phone": "...",
"firstName": "...",
"lastName": "...",
"country": "...",
"birthday": "...",
"metadata": "...",
"notes": "...",
"emailOptOut": "...",
"emailOptOutAt": "...",
"emailOptOutSource": "...",
"createdAt": "...",
"updatedAt": "..."
},
"product": {
"id": "...",
"slug": "...",
"title": "...",
"description": "...",
"imageUrl": "...",
"url": "...",
"metadata": "...",
"notes": "...",
"createdAt": "...",
"updatedAt": "..."
},
"response": {
"id": "...",
"reviewId": "...",
"body": "...",
"responderName": "...",
"published": "...",
"metadata": "...",
"createdAt": "...",
"updatedAt": "..."
},
"files": ["..."],
"answers": ["..."],
"collisions": {
"browserFingerprint": "...",
"ipAddress": "..."
}
}
],
"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 review."
},
"siteId": {
"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": "The site the review was left on."
},
"productId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The survey the review came in through, or null if it was collected another way."
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's headline."
},
"body": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's body text."
},
"originalTitle": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The headline as the customer wrote it, captured the first time the title was redacted. Null if the title has never been redacted."
},
"originalBody": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The body as the customer wrote it, captured the first time the body was redacted. Null if the body has never been redacted."
},
"redactedAt": {
"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 review's text was last redacted, or null if it never has been."
},
"rating": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The star rating the customer gave, from 1 to 5."
},
"published": {
"type": "boolean",
"description": "Whether the review is visible on the storefront. Reviews start unpublished and wait for moderation."
},
"verifiedBuyer": {
"type": "boolean",
"description": "Whether the reviewer verifiably bought the product. Set when the review comes in through a review request, and shown as the Verified buyer badge in the reviews widget."
},
"flagged": {
"type": "boolean",
"description": "Whether content screening flagged the review on arrival. A flagged review is never auto-published; it waits for a person, and `flags` says why."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links` or `profanity`."
},
"detail": {
"type": "string",
"description": "Human-readable explanation shown in the moderation pane."
},
"score": {
"description": "The raw score behind the flag, from 0 to 1, for checks that produce one. The `aiGenerated` flag carries the AI-writing detector's confidence; the other checks carry no score.",
"type": "number"
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening."
},
"moderatedAt": {
"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 review was published or rejected, or null if nobody has decided on it yet. An unpublished review with no `moderatedAt` is still waiting in the moderation queue; one with a stamp was rejected."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from. Cleared 90 days after the moderation decision, so older reviews carry null."
},
"browserFingerprint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser fingerprint recorded when the review was submitted, used to spot one browser reviewing as many customers. Cleared 90 days after the moderation decision."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address."
},
"userAgent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser user agent the review was submitted with, shown for moderator context. Cleared 90 days after the moderation decision."
},
"submitSeconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many seconds after the review page loaded the form came back. Very fast submissions are flagged as automation."
},
"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 review was submitted."
},
"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 review was last changed."
},
"importJob": {
"anyOf": [
{
"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": "The import job that wrote the record."
},
"type": {
"type": "string",
"enum": [
"customers",
"products",
"orders",
"wishlists",
"reviews",
"loyalty-members"
],
"description": "What that import's file contained."
},
"action": {
"type": "string",
"enum": ["created", "updated"],
"description": "Whether the import created this record or updated one that was already there."
},
"row": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The 1-based row in the import's CSV that became this record, where it is known."
},
"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 that import was started."
}
},
"required": ["id", "type", "action", "row", "createdAt"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The import that last wrote this record, or null when no import did."
},
"customer": {
"anyOf": [
{
"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 customer."
},
"email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"description": "The customer's email address, unique within the organization."
},
"phone": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's phone number in E.164 format."
},
"firstName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's first name."
},
"lastName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's last name."
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's country, as an ISO 3166-1 code."
},
"birthday": {
"anyOf": [
{
"type": "string",
"pattern": "^(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-9]))$",
"description": "The customer's birthday as `MM-DD`. Month and day only: no year is collected or stored. February 29 is allowed, and in a year that has no February 29 it is treated as February 28."
},
{
"type": "null"
}
],
"description": "The customer's birthday as `MM-DD`, or null when they have not given one. Month and day only: no year is collected or stored."
},
"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`."
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Internal notes about the customer. Never shown to them."
},
"emailOptOut": {
"type": "boolean",
"description": "Whether the customer has opted out of marketing email. They still receive mail they asked for, such as a wishlist they shared themselves."
},
"emailOptOutAt": {
"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 opt-out was recorded. Null while the customer is still subscribed."
},
"emailOptOutSource": {
"anyOf": [
{
"type": "string",
"enum": ["customer", "merchant", "import", "shopify"],
"description": "Where the opt-out came from."
},
{
"type": "null"
}
],
"description": "Where the opt-out came from: `customer` (they opted out themselves, through an unsubscribe link or your integration), `merchant`, `import` or `shopify`. Null while the customer is still subscribed."
},
"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 customer was first created in Cascade."
},
"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 customer was last changed."
}
},
"required": [
"id",
"email",
"phone",
"firstName",
"lastName",
"country",
"birthday",
"metadata",
"notes",
"emailOptOut",
"emailOptOutAt",
"emailOptOutSource",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have been deleted."
},
"product": {
"anyOf": [
{
"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 product."
},
"slug": {
"type": "string",
"description": "The product's URL handle, unique within the organization. For Shopify-connected stores this is the Shopify handle, so renaming it there creates a new product here."
},
"title": {
"type": "string",
"description": "The product's display name."
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The product's long-form description."
},
"imageUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "URL of the product's main image."
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "URL of the product's page on the storefront, or null if it is not known."
},
"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`."
},
"notes": {
"type": "string",
"description": "Internal notes about the product. Never shown to customers."
},
"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 product was first created in Cascade."
},
"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 product was last changed."
}
},
"required": [
"id",
"slug",
"title",
"description",
"imageUrl",
"url",
"metadata",
"notes",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if it has been deleted."
},
"response": {
"anyOf": [
{
"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 reply."
},
"reviewId": {
"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": "The review this reply answers."
},
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "The name shown above the reply on the storefront, such as the person or the store."
},
"published": {
"type": "boolean",
"description": "Whether the reply is visible on the storefront. This is independent of the review's own published flag, so a reply can be drafted now and approved later."
},
"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 reply was written."
},
"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 reply was last changed."
}
},
"required": [
"id",
"reviewId",
"body",
"responderName",
"published",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The merchant's reply to the review, published or not, or null if nobody has replied."
},
"files": {
"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 file."
},
"fileName": {
"type": "string",
"description": "The name the file was uploaded under."
},
"contentType": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The file's MIME type, such as `image/jpeg` or `video/mp4`, or null for files uploaded before content types were recorded."
},
"sizeBytes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Size of the file in bytes, or null if the upload never completed."
},
"url": {
"type": "string",
"description": "Public URL the file is served from."
}
},
"required": ["id", "fileName", "contentType", "sizeBytes", "url"],
"additionalProperties": false
},
"description": "The photos and videos the reviewer attached, oldest first."
},
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"questionId": {
"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": "The review question that was answered."
},
"label": {
"type": "string",
"description": "The question as the reviewer 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."
},
"step": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, the step the reviewer picked, counting from 1."
},
"steps": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, how many steps it has, so the answer reads as `3 of 5`."
},
"lowLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its first step means."
},
"highLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its last step means."
},
"middleLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its middle step means, if it has one."
},
"optionLabels": {
"type": "array",
"items": {
"type": "string"
},
"description": "For a multi-choice question, the options the reviewer picked, by label."
}
},
"required": [
"questionId",
"label",
"type",
"step",
"steps",
"lowLabel",
"highLabel",
"middleLabel",
"optionLabels"
],
"additionalProperties": false
},
"description": "What the reviewer answered to the review questions that were asked, in the order the form asked them. Archived questions are included: the answers already given still count."
},
"collisions": {
"type": "object",
"properties": {
"browserFingerprint": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many other reviews in the organization share this review's browser fingerprint. Zero when the fingerprint is missing or has been cleared."
},
"ipAddress": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many other reviews in the organization were submitted from this review's IP address. Zero when the IP address is missing or has been cleared."
}
},
"required": ["browserFingerprint", "ipAddress"],
"additionalProperties": false,
"description": "How many other reviews share this review's submission identifiers. The same fingerprint or IP address appearing across different customers is the strongest sign of coordinated reviews."
}
},
"required": [
"id",
"siteId",
"productId",
"productVariantId",
"customerId",
"surveyId",
"title",
"body",
"originalTitle",
"originalBody",
"redactedAt",
"rating",
"published",
"verifiedBuyer",
"flagged",
"flags",
"moderatedAt",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"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
}PATCH /reviews/bulk-update
Publish or reject every review matching a filter, in one request. Sending no filter moderates every review in the organization. Each review is stamped as moderated either way, and every product involved has its review stats recalculated once.
Requires the reviews:write permission.
Costs 5 rate limit tokens instead of the usual one. See Rate limits.
Query Parameters
filterstringA JSON-encoded filter document selecting the reviews to moderate. Omit it and every review in the organization is moderated. See Filtering for the syntax.
searchstringA free-text search term, matched case-insensitively against the record's most identifying fields. Combined with filter using AND.
Body Parameters
publishedbooleanrequiredWhether the selected reviews are visible on the storefront. This is the moderation switch: true publishes them, false takes them down.
curl \
-X PATCH \
"https://api.cascade.dev/reviews/bulk-update" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"published": true
}'import { client } from "@cascade-commerce/api/admin/client"
import { patchReviewsBulkUpdate } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsBulkUpdate({
body: {
published: true,
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/bulk-update")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"published": true
}
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 = """{
"published": true
}"""
req = Request(
"https://api.cascade.dev/reviews/bulk-update",
data=body.encode(),
method="PATCH",
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"count": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"count": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many reviews the filter matched and moderated."
}
},
"required": ["count"],
"additionalProperties": false
}POST /reviews/import
Bring reviews in from another platform in bulk, matching the customer by email and the product by slug. A customer Cascade has never seen is created from the email. Rows whose other references don't resolve are skipped and returned in skipped rather than failing the whole batch, so a catalog that didn't migrate cleanly still gets everything it can; set strict to fail on the first such row instead. Imported reviews count as already moderated and go live unless published says otherwise. To import a CSV instead, upload it and queue an import job with POST /import-jobs.
Requires the reviews:write permission.
Costs 10 rate limit tokens instead of the usual one. See Rate limits.
Body Parameters
dataobject[]requiredThe reviews to create or update.
strictbooleanWhen true, a row that references something missing fails the whole import with an error. Off by default: rows whose references don't resolve are skipped and reported in skipped.
curl \
-X POST \
"https://api.cascade.dev/reviews/import" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": [
{
"externalId": "string",
"site": "string",
"customer": "[email protected]",
"product": "string",
"productExternalId": "string",
"productUrl": "string",
"productVariant": "string",
"title": "string",
"body": "string",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"createdAt": "2025-01-15T09:30:00Z",
"imageUrls": [
"..."
],
"response": "string",
"responderName": "string",
"responseCreatedAt": "2025-01-15T09:30:00Z",
"answers": [
"..."
],
"metadata": "..."
}
],
"strict": true
}'import { client } from "@cascade-commerce/api/admin/client"
import { postReviewsImport } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postReviewsImport({
body: {
data: [
{
externalId: "string",
site: "string",
customer: "[email protected]",
product: "string",
productExternalId: "string",
productUrl: "string",
productVariant: "string",
title: "string",
body: "string",
rating: 0,
published: true,
verifiedBuyer: true,
createdAt: "2025-01-15T09:30:00Z",
imageUrls: ["..."],
response: "string",
responderName: "string",
responseCreatedAt: "2025-01-15T09:30:00Z",
answers: ["..."],
metadata: "...",
},
],
strict: true,
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/import")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"data": [
{
"externalId": "string",
"site": "string",
"customer": "[email protected]",
"product": "string",
"productExternalId": "string",
"productUrl": "string",
"productVariant": "string",
"title": "string",
"body": "string",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"createdAt": "2025-01-15T09:30:00Z",
"imageUrls": [
"..."
],
"response": "string",
"responderName": "string",
"responseCreatedAt": "2025-01-15T09:30:00Z",
"answers": [
"..."
],
"metadata": "..."
}
],
"strict": true
}
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": [
{
"externalId": "string",
"site": "string",
"customer": "[email protected]",
"product": "string",
"productExternalId": "string",
"productUrl": "string",
"productVariant": "string",
"title": "string",
"body": "string",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"createdAt": "2025-01-15T09:30:00Z",
"imageUrls": [
"..."
],
"response": "string",
"responderName": "string",
"responseCreatedAt": "2025-01-15T09:30:00Z",
"answers": [
"..."
],
"metadata": "..."
}
],
"strict": true
}"""
req = Request(
"https://api.cascade.dev/reviews/import",
data=body.encode(),
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"imported": [
{
"index": 0,
"id": "550e8400-e29b-41d4-a716-446655440000",
"imageUrls": ["..."]
}
],
"skipped": [
{
"index": 0,
"message": "string"
}
]
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"imported": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Position of the row in the batch you sent."
},
"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": "The review that was created or updated."
},
"imageUrls": {
"type": "array",
"items": {
"type": "string"
},
"description": "The attachment URLs from the row. This endpoint records the review only. Fetching the files is the CSV importer's job."
}
},
"required": ["index", "id", "imageUrls"],
"additionalProperties": false
},
"description": "The rows that landed."
},
"skipped": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Position of the row in the batch you sent."
},
"message": {
"type": "string",
"description": "Why the row could not be imported."
}
},
"required": ["index", "message"],
"additionalProperties": false
},
"description": "The rows that could not be placed, and why."
}
},
"required": ["imported", "skipped"],
"additionalProperties": false
}GET /reviews/{id}
Get a single review by ID.
Requires the reviews:read permission.
Path Parameters
idstring (uuid)requiredThe ID of the review.
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: importJob, customer, product, response, files, answers, collisions.
curl \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getReviewsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviewsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/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/reviews/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",
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"productId": "550e8400-e29b-41d4-a716-446655440000",
"productVariantId": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "550e8400-e29b-41d4-a716-446655440000",
"surveyId": "550e8400-e29b-41d4-a716-446655440000",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"importJob": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "customers",
"action": "created",
"row": 0,
"createdAt": "2025-01-15T09:30:00Z"
},
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]",
"phone": "string",
"firstName": "string",
"lastName": "string",
"country": "string",
"birthday": "string",
"metadata": "...",
"notes": "string",
"emailOptOut": true,
"emailOptOutAt": "2025-01-15T09:30:00Z",
"emailOptOutSource": "customer",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
},
"product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "string",
"title": "string",
"description": "string",
"imageUrl": "string",
"url": "string",
"metadata": "...",
"notes": "string",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
},
"response": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"reviewId": "550e8400-e29b-41d4-a716-446655440000",
"body": "string",
"responderName": "string",
"published": true,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
},
"files": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "string",
"contentType": "string",
"sizeBytes": 0,
"url": "string"
}
],
"answers": [
{
"questionId": "550e8400-e29b-41d4-a716-446655440000",
"label": "string",
"type": "scale",
"step": 0,
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string",
"optionLabels": ["..."]
}
],
"collisions": {
"browserFingerprint": 0,
"ipAddress": 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 review."
},
"siteId": {
"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": "The site the review was left on."
},
"productId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The survey the review came in through, or null if it was collected another way."
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's headline."
},
"body": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's body text."
},
"originalTitle": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The headline as the customer wrote it, captured the first time the title was redacted. Null if the title has never been redacted."
},
"originalBody": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The body as the customer wrote it, captured the first time the body was redacted. Null if the body has never been redacted."
},
"redactedAt": {
"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 review's text was last redacted, or null if it never has been."
},
"rating": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The star rating the customer gave, from 1 to 5."
},
"published": {
"type": "boolean",
"description": "Whether the review is visible on the storefront. Reviews start unpublished and wait for moderation."
},
"verifiedBuyer": {
"type": "boolean",
"description": "Whether the reviewer verifiably bought the product. Set when the review comes in through a review request, and shown as the Verified buyer badge in the reviews widget."
},
"flagged": {
"type": "boolean",
"description": "Whether content screening flagged the review on arrival. A flagged review is never auto-published; it waits for a person, and `flags` says why."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links` or `profanity`."
},
"detail": {
"type": "string",
"description": "Human-readable explanation shown in the moderation pane."
},
"score": {
"description": "The raw score behind the flag, from 0 to 1, for checks that produce one. The `aiGenerated` flag carries the AI-writing detector's confidence; the other checks carry no score.",
"type": "number"
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening."
},
"moderatedAt": {
"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 review was published or rejected, or null if nobody has decided on it yet. An unpublished review with no `moderatedAt` is still waiting in the moderation queue; one with a stamp was rejected."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from. Cleared 90 days after the moderation decision, so older reviews carry null."
},
"browserFingerprint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser fingerprint recorded when the review was submitted, used to spot one browser reviewing as many customers. Cleared 90 days after the moderation decision."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address."
},
"userAgent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser user agent the review was submitted with, shown for moderator context. Cleared 90 days after the moderation decision."
},
"submitSeconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many seconds after the review page loaded the form came back. Very fast submissions are flagged as automation."
},
"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 review was submitted."
},
"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 review was last changed."
},
"importJob": {
"anyOf": [
{
"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": "The import job that wrote the record."
},
"type": {
"type": "string",
"enum": [
"customers",
"products",
"orders",
"wishlists",
"reviews",
"loyalty-members"
],
"description": "What that import's file contained."
},
"action": {
"type": "string",
"enum": ["created", "updated"],
"description": "Whether the import created this record or updated one that was already there."
},
"row": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The 1-based row in the import's CSV that became this record, where it is known."
},
"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 that import was started."
}
},
"required": ["id", "type", "action", "row", "createdAt"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The import that last wrote this record, or null when no import did."
},
"customer": {
"anyOf": [
{
"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 customer."
},
"email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"description": "The customer's email address, unique within the organization."
},
"phone": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's phone number in E.164 format."
},
"firstName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's first name."
},
"lastName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's last name."
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The customer's country, as an ISO 3166-1 code."
},
"birthday": {
"anyOf": [
{
"type": "string",
"pattern": "^(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-9]))$",
"description": "The customer's birthday as `MM-DD`. Month and day only: no year is collected or stored. February 29 is allowed, and in a year that has no February 29 it is treated as February 28."
},
{
"type": "null"
}
],
"description": "The customer's birthday as `MM-DD`, or null when they have not given one. Month and day only: no year is collected or stored."
},
"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`."
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Internal notes about the customer. Never shown to them."
},
"emailOptOut": {
"type": "boolean",
"description": "Whether the customer has opted out of marketing email. They still receive mail they asked for, such as a wishlist they shared themselves."
},
"emailOptOutAt": {
"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 opt-out was recorded. Null while the customer is still subscribed."
},
"emailOptOutSource": {
"anyOf": [
{
"type": "string",
"enum": ["customer", "merchant", "import", "shopify"],
"description": "Where the opt-out came from."
},
{
"type": "null"
}
],
"description": "Where the opt-out came from: `customer` (they opted out themselves, through an unsubscribe link or your integration), `merchant`, `import` or `shopify`. Null while the customer is still subscribed."
},
"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 customer was first created in Cascade."
},
"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 customer was last changed."
}
},
"required": [
"id",
"email",
"phone",
"firstName",
"lastName",
"country",
"birthday",
"metadata",
"notes",
"emailOptOut",
"emailOptOutAt",
"emailOptOutSource",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have been deleted."
},
"product": {
"anyOf": [
{
"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 product."
},
"slug": {
"type": "string",
"description": "The product's URL handle, unique within the organization. For Shopify-connected stores this is the Shopify handle, so renaming it there creates a new product here."
},
"title": {
"type": "string",
"description": "The product's display name."
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The product's long-form description."
},
"imageUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "URL of the product's main image."
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "URL of the product's page on the storefront, or null if it is not known."
},
"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`."
},
"notes": {
"type": "string",
"description": "Internal notes about the product. Never shown to customers."
},
"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 product was first created in Cascade."
},
"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 product was last changed."
}
},
"required": [
"id",
"slug",
"title",
"description",
"imageUrl",
"url",
"metadata",
"notes",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if it has been deleted."
},
"response": {
"anyOf": [
{
"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 reply."
},
"reviewId": {
"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": "The review this reply answers."
},
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "The name shown above the reply on the storefront, such as the person or the store."
},
"published": {
"type": "boolean",
"description": "Whether the reply is visible on the storefront. This is independent of the review's own published flag, so a reply can be drafted now and approved later."
},
"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 reply was written."
},
"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 reply was last changed."
}
},
"required": [
"id",
"reviewId",
"body",
"responderName",
"published",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The merchant's reply to the review, published or not, or null if nobody has replied."
},
"files": {
"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 file."
},
"fileName": {
"type": "string",
"description": "The name the file was uploaded under."
},
"contentType": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The file's MIME type, such as `image/jpeg` or `video/mp4`, or null for files uploaded before content types were recorded."
},
"sizeBytes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Size of the file in bytes, or null if the upload never completed."
},
"url": {
"type": "string",
"description": "Public URL the file is served from."
}
},
"required": ["id", "fileName", "contentType", "sizeBytes", "url"],
"additionalProperties": false
},
"description": "The photos and videos the reviewer attached, oldest first."
},
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"questionId": {
"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": "The review question that was answered."
},
"label": {
"type": "string",
"description": "The question as the reviewer 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."
},
"step": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, the step the reviewer picked, counting from 1."
},
"steps": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, how many steps it has, so the answer reads as `3 of 5`."
},
"lowLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its first step means."
},
"highLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its last step means."
},
"middleLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For a scale question, what its middle step means, if it has one."
},
"optionLabels": {
"type": "array",
"items": {
"type": "string"
},
"description": "For a multi-choice question, the options the reviewer picked, by label."
}
},
"required": [
"questionId",
"label",
"type",
"step",
"steps",
"lowLabel",
"highLabel",
"middleLabel",
"optionLabels"
],
"additionalProperties": false
},
"description": "What the reviewer answered to the review questions that were asked, in the order the form asked them. Archived questions are included: the answers already given still count."
},
"collisions": {
"type": "object",
"properties": {
"browserFingerprint": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many other reviews in the organization share this review's browser fingerprint. Zero when the fingerprint is missing or has been cleared."
},
"ipAddress": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many other reviews in the organization were submitted from this review's IP address. Zero when the IP address is missing or has been cleared."
}
},
"required": ["browserFingerprint", "ipAddress"],
"additionalProperties": false,
"description": "How many other reviews share this review's submission identifiers. The same fingerprint or IP address appearing across different customers is the strongest sign of coordinated reviews."
}
},
"required": [
"id",
"siteId",
"productId",
"productVariantId",
"customerId",
"surveyId",
"title",
"body",
"originalTitle",
"originalBody",
"redactedAt",
"rating",
"published",
"verifiedBuyer",
"flagged",
"flags",
"moderatedAt",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The requested review."
}PATCH /reviews/{id}
Moderate or redact a review. Set published to put it live on the storefront or take it down. Edits to title and body are limited to redaction: send the current text with the spans to hide replaced by [redacted], and anything else is rejected. The first redaction stores the customer's original text on the review, visible only through this API. The rating is the customer's and cannot be changed. Triggers the review.updated webhook.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the review to update.
Body Parameters
titlestring | nullThe review's headline. Edits are limited to redaction: the new text must be the current text with one or more spans replaced by [redacted].
bodystring | nullThe review's body text. Edits are limited to redaction: the new text must be the current text with one or more spans replaced by [redacted].
publishedbooleanWhether the review is visible on the storefront. This is the moderation switch.
metadataobjectReplaces the review's metadata wholesale. Omit to leave it alone.
curl \
-X PATCH \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "string",
"body": "string",
"published": true,
"metadata": "..."
}'import { client } from "@cascade-commerce/api/admin/client"
import { patchReviewsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
title: "string",
body: "string",
published: true,
metadata: "...",
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"title": "string",
"body": "string",
"published": 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 = """{
"title": "string",
"body": "string",
"published": true,
"metadata": "..."
}"""
req = Request(
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="PATCH",
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",
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"productId": "550e8400-e29b-41d4-a716-446655440000",
"productVariantId": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "550e8400-e29b-41d4-a716-446655440000",
"surveyId": "550e8400-e29b-41d4-a716-446655440000",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$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 review."
},
"siteId": {
"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": "The site the review was left on."
},
"productId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The survey the review came in through, or null if it was collected another way."
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's headline."
},
"body": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's body text."
},
"originalTitle": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The headline as the customer wrote it, captured the first time the title was redacted. Null if the title has never been redacted."
},
"originalBody": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The body as the customer wrote it, captured the first time the body was redacted. Null if the body has never been redacted."
},
"redactedAt": {
"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 review's text was last redacted, or null if it never has been."
},
"rating": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The star rating the customer gave, from 1 to 5."
},
"published": {
"type": "boolean",
"description": "Whether the review is visible on the storefront. Reviews start unpublished and wait for moderation."
},
"verifiedBuyer": {
"type": "boolean",
"description": "Whether the reviewer verifiably bought the product. Set when the review comes in through a review request, and shown as the Verified buyer badge in the reviews widget."
},
"flagged": {
"type": "boolean",
"description": "Whether content screening flagged the review on arrival. A flagged review is never auto-published; it waits for a person, and `flags` says why."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links` or `profanity`."
},
"detail": {
"type": "string",
"description": "Human-readable explanation shown in the moderation pane."
},
"score": {
"description": "The raw score behind the flag, from 0 to 1, for checks that produce one. The `aiGenerated` flag carries the AI-writing detector's confidence; the other checks carry no score.",
"type": "number"
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening."
},
"moderatedAt": {
"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 review was published or rejected, or null if nobody has decided on it yet. An unpublished review with no `moderatedAt` is still waiting in the moderation queue; one with a stamp was rejected."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from. Cleared 90 days after the moderation decision, so older reviews carry null."
},
"browserFingerprint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser fingerprint recorded when the review was submitted, used to spot one browser reviewing as many customers. Cleared 90 days after the moderation decision."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address."
},
"userAgent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser user agent the review was submitted with, shown for moderator context. Cleared 90 days after the moderation decision."
},
"submitSeconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many seconds after the review page loaded the form came back. Very fast submissions are flagged as automation."
},
"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 review was submitted."
},
"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 review was last changed."
}
},
"required": [
"id",
"siteId",
"productId",
"productVariantId",
"customerId",
"surveyId",
"title",
"body",
"originalTitle",
"originalBody",
"redactedAt",
"rating",
"published",
"verifiedBuyer",
"flagged",
"flags",
"moderatedAt",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The updated review."
}DELETE /reviews/{id}
Permanently delete a review. This is for spam, abusive content, mistakes and customer data requests: to take a review off the storefront, unpublish it instead. The reason is required and is kept in a deletion record along with who deleted it. Removes the review's attached photos and videos, recalculates the product's review stats, and triggers the review.deleted webhook.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the review to delete.
Body Parameters
reasoncustomer_request | spam | abusive_content | not_about_product | duplicate | test_data | otherrequiredWhy the review is being deleted. Recorded permanently. To hide a review from your widgets, unpublish it instead of deleting it.
notestringMore detail on the deletion, recorded with the reason. Required for "other".
curl \
-X DELETE \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "customer_request",
"note": "string"
}'import { client } from "@cascade-commerce/api/admin/client"
import { deleteReviewsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteReviewsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
reason: "customer_request",
note: "string",
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"reason": "customer_request",
"note": "string"
}
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 = """{
"reason": "customer_request",
"note": "string"
}"""
req = Request(
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="DELETE",
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",
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"productId": "550e8400-e29b-41d4-a716-446655440000",
"productVariantId": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "550e8400-e29b-41d4-a716-446655440000",
"surveyId": "550e8400-e29b-41d4-a716-446655440000",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$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 review."
},
"siteId": {
"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": "The site the review was left on."
},
"productId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
],
"description": "The survey the review came in through, or null if it was collected another way."
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's headline."
},
"body": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The review's body text."
},
"originalTitle": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The headline as the customer wrote it, captured the first time the title was redacted. Null if the title has never been redacted."
},
"originalBody": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The body as the customer wrote it, captured the first time the body was redacted. Null if the body has never been redacted."
},
"redactedAt": {
"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 review's text was last redacted, or null if it never has been."
},
"rating": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The star rating the customer gave, from 1 to 5."
},
"published": {
"type": "boolean",
"description": "Whether the review is visible on the storefront. Reviews start unpublished and wait for moderation."
},
"verifiedBuyer": {
"type": "boolean",
"description": "Whether the reviewer verifiably bought the product. Set when the review comes in through a review request, and shown as the Verified buyer badge in the reviews widget."
},
"flagged": {
"type": "boolean",
"description": "Whether content screening flagged the review on arrival. A flagged review is never auto-published; it waits for a person, and `flags` says why."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links` or `profanity`."
},
"detail": {
"type": "string",
"description": "Human-readable explanation shown in the moderation pane."
},
"score": {
"description": "The raw score behind the flag, from 0 to 1, for checks that produce one. The `aiGenerated` flag carries the AI-writing detector's confidence; the other checks carry no score.",
"type": "number"
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening."
},
"moderatedAt": {
"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 review was published or rejected, or null if nobody has decided on it yet. An unpublished review with no `moderatedAt` is still waiting in the moderation queue; one with a stamp was rejected."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from. Cleared 90 days after the moderation decision, so older reviews carry null."
},
"browserFingerprint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser fingerprint recorded when the review was submitted, used to spot one browser reviewing as many customers. Cleared 90 days after the moderation decision."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address."
},
"userAgent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Browser user agent the review was submitted with, shown for moderator context. Cleared 90 days after the moderation decision."
},
"submitSeconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many seconds after the review page loaded the form came back. Very fast submissions are flagged as automation."
},
"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 review was submitted."
},
"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 review was last changed."
}
},
"required": [
"id",
"siteId",
"productId",
"productVariantId",
"customerId",
"surveyId",
"title",
"body",
"originalTitle",
"originalBody",
"redactedAt",
"rating",
"published",
"verifiedBuyer",
"flagged",
"flags",
"moderatedAt",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The deleted review."
}POST /reviews/{id}/response
Reply to a review as the merchant. A review takes one reply, so replying twice is a conflict: edit the existing reply instead. The reply is a draft until published is true, which is a separate decision from whether the review itself is published.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the review to reply to.
Body Parameters
bodystringrequiredWhat the merchant wants to say back to the reviewer.
publishedbooleanWhether the reply goes live right away. Leave it out to save a draft.
metadataobjectFree-form string key/value pairs for your own data.
responderNamestringThe name shown above the reply on the storefront. Defaults to the name on your account.
curl \
-X POST \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "string",
"published": true,
"metadata": "...",
"responderName": "string"
}'import { client } from "@cascade-commerce/api/admin/client"
import { postReviewsByIdResponse } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postReviewsByIdResponse({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
body: "string",
published: true,
metadata: "...",
responderName: "string",
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"body": "string",
"published": true,
"metadata": "...",
"responderName": "string"
}
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 = """{
"body": "string",
"published": true,
"metadata": "...",
"responderName": "string"
}"""
req = Request(
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response",
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",
"reviewId": "550e8400-e29b-41d4-a716-446655440000",
"body": "string",
"responderName": "string",
"published": true,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$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 reply."
},
"reviewId": {
"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": "The review this reply answers."
},
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "The name shown above the reply on the storefront, such as the person or the store."
},
"published": {
"type": "boolean",
"description": "Whether the reply is visible on the storefront. This is independent of the review's own published flag, so a reply can be drafted now and approved later."
},
"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 reply was written."
},
"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 reply was last changed."
}
},
"required": [
"id",
"reviewId",
"body",
"responderName",
"published",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The new reply."
}PATCH /reviews/{id}/response
Edit a review's reply, or publish and unpublish it. Only the fields you send are changed.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the replied-to review.
Body Parameters
bodystringWhat the merchant wants to say back to the reviewer.
responderNamestringThe name shown above the reply on the storefront. Defaults to the name on your account.
publishedbooleanWhether the reply is visible on the storefront. Omit to leave it as it is.
metadataobjectReplaces the reply's metadata wholesale. Omit to leave it alone.
curl \
-X PATCH \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "string",
"responderName": "string",
"published": true,
"metadata": "..."
}'import { client } from "@cascade-commerce/api/admin/client"
import { patchReviewsByIdResponse } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsByIdResponse({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
body: "string",
responderName: "string",
published: true,
metadata: "...",
},
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"body": "string",
"responderName": "string",
"published": 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 = """{
"body": "string",
"responderName": "string",
"published": true,
"metadata": "..."
}"""
req = Request(
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response",
data=body.encode(),
method="PATCH",
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",
"reviewId": "550e8400-e29b-41d4-a716-446655440000",
"body": "string",
"responderName": "string",
"published": true,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$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 reply."
},
"reviewId": {
"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": "The review this reply answers."
},
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "The name shown above the reply on the storefront, such as the person or the store."
},
"published": {
"type": "boolean",
"description": "Whether the reply is visible on the storefront. This is independent of the review's own published flag, so a reply can be drafted now and approved later."
},
"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 reply was written."
},
"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 reply was last changed."
}
},
"required": [
"id",
"reviewId",
"body",
"responderName",
"published",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The updated reply."
}DELETE /reviews/{id}/response
Delete a review's reply, taking it off the storefront. The review itself is untouched.
Requires the reviews:write permission.
Path Parameters
idstring (uuid)requiredThe ID of the replied-to review.
curl \
-X DELETE \
"https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { deleteReviewsByIdResponse } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteReviewsByIdResponse({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/reviews/550e8400-e29b-41d4-a716-446655440000/response")
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/reviews/550e8400-e29b-41d4-a716-446655440000/response",
method="DELETE",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"reviewId": "550e8400-e29b-41d4-a716-446655440000",
"body": "string",
"responderName": "string",
"published": true,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$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 reply."
},
"reviewId": {
"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": "The review this reply answers."
},
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "The name shown above the reply on the storefront, such as the person or the store."
},
"published": {
"type": "boolean",
"description": "Whether the reply is visible on the storefront. This is independent of the review's own published flag, so a reply can be drafted now and approved later."
},
"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 reply was written."
},
"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 reply was last changed."
}
},
"required": [
"id",
"reviewId",
"body",
"responderName",
"published",
"metadata",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The deleted reply."
}