#Reviews
These endpoints are called from a shopper's browser, so they take your publishable key rather than a secret one. Anything specific to one customer also takes a signed email and timestamp: see Identifying the customer for how to produce the signature.
GET /widgets/reviews/media
Get paginated photos and videos from published reviews, either for one product or, with no product slug, from across the whole store
Query Parameters
apiPublishableKeystringrequiredYour organization's publishable key, from Settings, API keys.
productSlugstringSlug of the product whose photos and videos to show. Leave it out for a wall of media from across the whole store.
limitnumberHow many media items to return. A review's own photos and videos are never split across pages, so a page can come back a little over this.
cursorstringThe nextCursor from the previous page.
minRatingintegerShow only media from reviews rated this many stars or higher.
curl \
"https://api.cascade.dev/widgets/reviews/media?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY"import { client } from "@cascade-commerce/api/widget/client"
import { getWidgetsReviewsMedia } from "@cascade-commerce/api/widget"
client.setConfig({
baseUrl: "https://api.cascade.dev",
})
const { data, error } = await getWidgetsReviewsMedia({
query: { apiPublishableKey: "pk_YOUR_PUBLISHABLE_KEY" },
})require "net/http"
uri = URI("https://api.cascade.dev/widgets/reviews/media?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY")
req = Net::HTTP::Get.new(uri)
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/widgets/reviews/media?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY",
)
with urlopen(req) as res:
print(json.load(res)){
"media": [
{
"url": "string",
"contentType": "string",
"review": {
"id": "...",
"rating": "...",
"title": "...",
"excerpt": "...",
"reviewerName": "...",
"verifiedBuyer": "...",
"createdAt": "..."
},
"product": {
"slug": "...",
"title": "...",
"url": "..."
}
}
],
"nextCursor": "string"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"media": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Public URL the photo or video is served from."
},
"contentType": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The file's MIME type, which is what decides whether it renders as a video."
},
"review": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"rating": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"excerpt": {
"type": "string",
"description": "The opening of the review body, trimmed for a caption."
},
"reviewerName": {
"type": "string",
"description": "First name and last initial, or Anonymous."
},
"verifiedBuyer": {
"type": "boolean"
},
"createdAt": {
"type": "string"
}
},
"required": [
"id",
"rating",
"title",
"excerpt",
"reviewerName",
"verifiedBuyer",
"createdAt"
],
"additionalProperties": false,
"description": "The review the media came from, which is what the grid's lightbox shows beside it."
},
"product": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"title": {
"type": "string"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Where the product lives on the storefront, if known."
}
},
"required": ["slug", "title", "url"],
"additionalProperties": false,
"description": "The product the review is about, for the lightbox's link back to it."
}
},
"required": ["url", "contentType", "review", "product"],
"additionalProperties": false
},
"description": "Photos and videos from published reviews, newest review first."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": ["media", "nextCursor"],
"additionalProperties": false
}POST /widgets/reviews/{id}/vote
Vote on whether a review was helpful
Path Parameters
idstring (uuid)requiredThe ID of the review being voted on.
Query Parameters
apiPublishableKeystringrequiredYour organization's publishable key, from Settings, API keys.
Body Parameters
helpfulbooleanrequiredTrue marks the review helpful, false marks it not helpful.
voterTokenstringrequiredRandom id identifying the visitor's browser. Voting again with the same token replaces the earlier vote.
curl \
-X POST \
"https://api.cascade.dev/widgets/reviews/550e8400-e29b-41d4-a716-446655440000/vote?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"helpful": true,
"voterToken": "string"
}'import { client } from "@cascade-commerce/api/widget/client"
import { postWidgetsReviewsByIdVote } from "@cascade-commerce/api/widget"
client.setConfig({
baseUrl: "https://api.cascade.dev",
})
const { data, error } = await postWidgetsReviewsByIdVote({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
query: { apiPublishableKey: "pk_YOUR_PUBLISHABLE_KEY" },
body: {
helpful: true,
voterToken: "string",
},
})require "net/http"
uri = URI("https://api.cascade.dev/widgets/reviews/550e8400-e29b-41d4-a716-446655440000/vote?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"helpful": true,
"voterToken": "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 = """{
"helpful": true,
"voterToken": "string"
}"""
req = Request(
"https://api.cascade.dev/widgets/reviews/550e8400-e29b-41d4-a716-446655440000/vote?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY",
data=body.encode(),
headers={"Content-Type": "application/json"},
)
with urlopen(req) as res:
print(json.load(res)){
"reviewId": "550e8400-e29b-41d4-a716-446655440000",
"helpful": true,
"helpfulCount": 0,
"notHelpfulCount": 0
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"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)$"
},
"helpful": {
"type": "boolean",
"description": "The vote now on record for this visitor."
},
"helpfulCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"notHelpfulCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": ["reviewId", "helpful", "helpfulCount", "notHelpfulCount"],
"additionalProperties": false
}GET /widgets/reviews
Get paginated published reviews, either for one product or, with no product slug, from across the whole store
Query Parameters
apiPublishableKeystringrequiredYour organization's publishable key, from Settings, API keys.
productSlugstringSlug of the product whose reviews to list. Leave it out to list published reviews from across the whole store, which is what a store-wide carousel shows.
limitnumberHow many reviews to return.
cursorstringThe nextCursor from the previous page.
sortnewest | highest | lowest | helpfulOrder of the review list: newest first, highest or lowest rating first, or most helpful votes first. Defaults to newest for one product, and to highest across the whole store.
ratingintegerShow only reviews with this star rating. The rating breakdown still covers them all.
minRatingintegerShow only reviews rated this many stars or higher. The rating breakdown covers them all.
voterTokenstringThe visitor's vote token, used to mark the reviews they have already voted on.
curl \
"https://api.cascade.dev/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY"import { client } from "@cascade-commerce/api/widget/client"
import { getWidgetsReviews } from "@cascade-commerce/api/widget"
client.setConfig({
baseUrl: "https://api.cascade.dev",
})
const { data, error } = await getWidgetsReviews({
query: { apiPublishableKey: "pk_YOUR_PUBLISHABLE_KEY" },
})require "net/http"
uri = URI("https://api.cascade.dev/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY")
req = Net::HTTP::Get.new(uri)
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/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY",
)
with urlopen(req) as res:
print(json.load(res)){
"product": {
"title": "string",
"imageUrl": "string",
"averageRating": 0,
"totalReviews": 0,
"ratingBreakdown": "..."
},
"summary": {
"text": "string",
"themes": [
{
"label": "...",
"sentiment": "...",
"reviewCount": "..."
}
],
"language": "string",
"reviewCount": 0,
"generatedAt": "string"
},
"reviewAnswers": {
"enabled": true,
"suggestedQuestions": ["string"]
},
"questions": [
{
"id": "string",
"label": "string",
"type": "scale",
"required": true,
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string",
"multiple": true,
"options": ["..."]
}
],
"questionSummary": [
{
"id": "string",
"label": "string",
"type": "scale",
"answered": 0,
"averageStep": 0,
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string",
"options": ["..."]
}
],
"reviews": [
{
"id": "string",
"rating": 0,
"title": "string",
"body": "string",
"createdAt": "string",
"verifiedBuyer": true,
"files": ["..."],
"helpfulCount": 0,
"notHelpfulCount": 0,
"viewerVote": true,
"response": {
"body": "...",
"responderName": "...",
"createdAt": "..."
},
"answers": ["..."],
"customer": {
"firstName": "...",
"lastInitial": "...",
"country": "..."
},
"product": {
"slug": "...",
"title": "...",
"url": "..."
}
}
],
"nextCursor": "string"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"product": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string"
},
"imageUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Product image, used in structured data."
},
"averageRating": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"totalReviews": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"ratingBreakdown": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "number"
}
}
},
"required": ["title", "imageUrl", "averageRating", "totalReviews", "ratingBreakdown"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The product the list belongs to. Null when no `productSlug` was sent, because a store-wide list has no one product to summarize."
},
"summary": {
"anyOf": [
{
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Two or three sentences on what reviewers say, generated from the published reviews rather than written by anyone."
},
"themes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Short name for the theme, such as `Fit runs small`, in a few words at most."
},
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "mixed"],
"description": "Whether reviewers raise this theme approvingly, as a complaint, or both."
},
"reviewCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Roughly how many of the published reviews mention this theme."
}
},
"required": ["label", "sentiment", "reviewCount"],
"additionalProperties": false
},
"description": "Recurring topics reviewers raise, most mentioned first."
},
"language": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The language the summary is written in, which is the one most reviews are in."
},
"reviewCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many published reviews it was generated from."
},
"generatedAt": {
"type": "string",
"description": "When it was generated."
}
},
"required": ["text", "themes", "language", "reviewCount", "generatedAt"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "A generated summary of the published reviews, precomputed and joined here. Null when the store has it switched off, the product has too few reviews, the summary is hidden, or none has been generated yet. It is deliberately absent from the page's structured data: it is not a review anyone wrote."
},
"reviewAnswers": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the ask box should be shown for this product. False when the store does not have review answers, the product has too few reviews, or the store's monthly allowance of generated answers is used up."
},
"suggestedQuestions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ready-made questions seeded from the review summary's themes, for one-tap asking."
}
},
"required": ["enabled", "suggestedQuestions"],
"additionalProperties": false,
"description": "Whether this product's ask box is available, and questions worth suggesting. Answers themselves come from POST /widgets/review-answers and are private to the shopper who asked."
},
"questions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"label": {
"type": "string",
"description": "The question as reviewers read it."
},
"type": {
"type": "string",
"enum": ["scale", "multi_choice"],
"description": "What the question asks for. `scale` is a run of steps between two labelled ends; `multi_choice` is a list of options."
},
"required": {
"type": "boolean",
"description": "Whether the form has to be answered before it can be sent."
},
"steps": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, how many steps it has."
},
"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."
},
"multiple": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "For a multi-choice question, whether more than one option can be picked."
},
"options": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"label": {
"type": "string"
}
},
"required": ["id", "label"],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "For a multi-choice question, the options to offer, in order."
}
},
"required": [
"id",
"label",
"type",
"required",
"steps",
"lowLabel",
"highLabel",
"middleLabel",
"multiple",
"options"
],
"additionalProperties": false
},
"description": "The extra questions this product's review form should ask, in order. Empty when no product slug was sent, because the questions are scoped per product. Send the answers back as `answers` on POST /widgets/reviews."
},
"questionSummary": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"label": {
"type": "string"
},
"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."
},
"answered": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many of this product's published reviews answered the question."
},
"averageStep": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "For a scale question, the mean step across those answers, which is where the marker sits."
},
"steps": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"lowLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"highLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"middleLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"options": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"label": {
"type": "string"
},
"count": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many answering reviews picked this option."
},
"percentage": {
"type": "number",
"description": "That count as a percentage of the answering reviews. A question that takes several options can add up to more than 100."
}
},
"required": ["id", "label", "count", "percentage"],
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "For a multi-choice question, the share each published option took."
}
},
"required": [
"id",
"label",
"type",
"answered",
"averageStep",
"steps",
"lowLabel",
"highLabel",
"middleLabel",
"options"
],
"additionalProperties": false
},
"description": "What published reviews answered to each question the merchant chose to show. Archived questions stay here while their answers do; hidden questions and hidden options never appear."
},
"reviews": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"rating": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"body": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"createdAt": {
"type": "string"
},
"verifiedBuyer": {
"type": "boolean"
},
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Public URL the attachment is served from."
},
"contentType": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The attachment's MIME type, which is what decides whether it renders as a video."
}
},
"required": ["url", "contentType"],
"additionalProperties": false
},
"description": "Photos and videos the reviewer attached."
},
"helpfulCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many visitors marked this review helpful."
},
"notHelpfulCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many visitors marked this review not helpful."
},
"viewerVote": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "This visitor's own vote, or null if they have not voted or sent no token."
},
"response": {
"anyOf": [
{
"type": "object",
"properties": {
"body": {
"type": "string",
"description": "What the merchant wrote back."
},
"responderName": {
"type": "string",
"description": "Who the reply is from."
},
"createdAt": {
"type": "string",
"description": "When the reply was written."
}
},
"required": ["body", "responderName", "createdAt"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The merchant's published reply, or null if there is none. Drafts never appear here."
},
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"questionId": {
"type": "string"
},
"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 they picked."
},
"steps": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "For a scale question, how many steps it has."
},
"lowLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"highLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"middleLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"optionLabels": {
"type": "array",
"items": {
"type": "string"
},
"description": "For a multi-choice question, the options they picked, by label."
}
},
"required": [
"questionId",
"label",
"type",
"step",
"steps",
"lowLabel",
"highLabel",
"middleLabel",
"optionLabels"
],
"additionalProperties": false
},
"description": "What this reviewer answered to the store's review questions. Only questions and options the merchant publishes appear here."
},
"customer": {
"type": "object",
"properties": {
"firstName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"lastInitial": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": ["firstName", "lastInitial", "country"],
"additionalProperties": false
},
"product": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"title": {
"type": "string"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Where the product lives on the storefront, if known."
}
},
"required": ["slug", "title", "url"],
"additionalProperties": false,
"description": "The product this review is about. It is what a store-wide carousel labels and links each card with."
}
},
"required": [
"id",
"rating",
"title",
"body",
"createdAt",
"verifiedBuyer",
"files",
"helpfulCount",
"notHelpfulCount",
"viewerVote",
"response",
"answers",
"customer",
"product"
],
"additionalProperties": false
}
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"product",
"summary",
"reviewAnswers",
"questions",
"questionSummary",
"reviews",
"nextCursor"
],
"additionalProperties": false
}POST /widgets/reviews
Submit a review of a product from the storefront. The review goes into moderation like any other: published in the answer says whether it is live already, which only happens when the store auto-publishes reviews at that rating. Reviews sent this way are never marked as verified buyers, because nothing proves the person bought the product. Send the customer trio for a signed-in shopper, and the reviewer's email for a guest. It is rate limited per IP address.
Query Parameters
apiPublishableKeystringrequiredYour organization's publishable key, from Settings, API keys.
customerEmailstring (email)The signed-in shopper's address. Leave it out for a shopper who is not signed in.
timestampstringThe Unix seconds the signature was made at. Required with customerEmail.
signaturestringThe HMAC of the email and timestamp. Required with customerEmail.
Body Parameters
productSlugstringrequiredSlug of the product being reviewed.
ratingintegerrequiredThe star rating, from 1 to 5.
titlestring | nullThe review's headline.
bodystring | nullThe review itself.
reviewerNamestring | nullThe reviewer's name. Only the first name and the last initial are ever shown, the same as every other review.
emailstring (email)The reviewer's email address. It is never shown on the storefront: it identifies the reviewer for moderation, and stops the same person reviewing one product twice. Ignored when the shopper is signed in, because the signed email is used instead.
fileKeysstring[]Keys from POST /widgets/reviews/upload-url for the photos and videos to attach, up to five.
renderedAtnumberUnix time in milliseconds when the form was opened, used to measure time to submit. A review that comes back instantly is flagged for a person to look at.
honeypotFilledbooleanTrue when the form's hidden trap field was filled in, which only a bot does.
answersobject[]Answers to the extra questions this product's form asked, from questions on GET /widgets/reviews. Every question marked required has to be answered.
curl \
-X POST \
"https://api.cascade.dev/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"productSlug": "string",
"rating": 0,
"title": "string",
"body": "string",
"reviewerName": "string",
"email": "[email protected]",
"fileKeys": [
"string"
],
"renderedAt": 0,
"honeypotFilled": true,
"answers": [
{
"questionId": "550e8400-e29b-41d4-a716-446655440000",
"value": {
"step": "..."
}
}
]
}'import { client } from "@cascade-commerce/api/widget/client"
import { postWidgetsReviews } from "@cascade-commerce/api/widget"
client.setConfig({
baseUrl: "https://api.cascade.dev",
})
const { data, error } = await postWidgetsReviews({
query: { apiPublishableKey: "pk_YOUR_PUBLISHABLE_KEY" },
body: {
productSlug: "string",
rating: 0,
title: "string",
body: "string",
reviewerName: "string",
email: "[email protected]",
fileKeys: ["string"],
renderedAt: 0,
honeypotFilled: true,
answers: [
{
questionId: "550e8400-e29b-41d4-a716-446655440000",
value: {
step: "...",
},
},
],
},
})require "net/http"
uri = URI("https://api.cascade.dev/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"productSlug": "string",
"rating": 0,
"title": "string",
"body": "string",
"reviewerName": "string",
"email": "[email protected]",
"fileKeys": [
"string"
],
"renderedAt": 0,
"honeypotFilled": true,
"answers": [
{
"questionId": "550e8400-e29b-41d4-a716-446655440000",
"value": {
"step": "..."
}
}
]
}
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 = """{
"productSlug": "string",
"rating": 0,
"title": "string",
"body": "string",
"reviewerName": "string",
"email": "[email protected]",
"fileKeys": [
"string"
],
"renderedAt": 0,
"honeypotFilled": true,
"answers": [
{
"questionId": "550e8400-e29b-41d4-a716-446655440000",
"value": {
"step": "..."
}
}
]
}"""
req = Request(
"https://api.cascade.dev/widgets/reviews?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY",
data=body.encode(),
headers={"Content-Type": "application/json"},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"published": true
}{
"$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": "The review that was created."
},
"published": {
"type": "boolean",
"description": "Whether the review is already live on the storefront. False means it is waiting for the store to approve it, which is the default."
}
},
"required": ["id", "published"],
"additionalProperties": false
}POST /widgets/reviews/upload-url
Get a short-lived link for uploading one photo or video to attach to a storefront review. PUT the file's bytes to uploadUrl, then send key in the review's fileKeys. It is rate limited per IP address.
Query Parameters
apiPublishableKeystringrequiredYour organization's publishable key, from Settings, API keys.
Body Parameters
fileNamestringrequiredThe file's name, kept for the moderation view.
contentTypeimage/jpeg | image/png | image/gif | image/webp | video/mp4 | video/quicktime | video/webmrequiredThe file's MIME type. Photos and videos only.
sizeBytesnumberrequiredThe file's size in bytes.
curl \
-X POST \
"https://api.cascade.dev/widgets/reviews/upload-url?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "string",
"contentType": "image/jpeg",
"sizeBytes": 0
}'import { client } from "@cascade-commerce/api/widget/client"
import { postWidgetsReviewsUploadUrl } from "@cascade-commerce/api/widget"
client.setConfig({
baseUrl: "https://api.cascade.dev",
})
const { data, error } = await postWidgetsReviewsUploadUrl({
query: { apiPublishableKey: "pk_YOUR_PUBLISHABLE_KEY" },
body: {
fileName: "string",
contentType: "image/jpeg",
sizeBytes: 0,
},
})require "net/http"
uri = URI("https://api.cascade.dev/widgets/reviews/upload-url?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"fileName": "string",
"contentType": "image/jpeg",
"sizeBytes": 0
}
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 = """{
"fileName": "string",
"contentType": "image/jpeg",
"sizeBytes": 0
}"""
req = Request(
"https://api.cascade.dev/widgets/reviews/upload-url?apiPublishableKey=pk_YOUR_PUBLISHABLE_KEY",
data=body.encode(),
headers={"Content-Type": "application/json"},
)
with urlopen(req) as res:
print(json.load(res)){
"key": "string",
"uploadUrl": "string",
"expiresAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Send this back as one of `fileKeys` when you submit the review."
},
"uploadUrl": {
"type": "string",
"description": "PUT the file's bytes here. The link is good for five minutes."
},
"expiresAt": {
"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 upload link stops working."
}
},
"required": ["key", "uploadUrl", "expiresAt"],
"additionalProperties": false
}