#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. Every word has to match, from the start of a word, in any order and ignoring case and accents, 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, incentivized, connectedReviewer, sentiment, source, moderatedAt, moderationReason, surveyId, ipAddress, browserFingerprint, metadata, createdAt, product.id, product.slug, product.title, product.metadata, product.averageRating, product.ratingCount, 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, reports, collisions.
curl \
"https://your-store.meisei.app/api/reviews" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { getReviews } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviews()require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
page = meisei.reviews.listfrom meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
page = meisei.reviews.list(){
"data": [
{
"id": "A4Ws1igz",
"siteId": "A4Ws1igz",
"productId": "A4Ws1igz",
"productVariantId": "A4Ws1igz",
"customerId": "A4Ws1igz",
"surveyId": "A4Ws1igz",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"incentivized": true,
"connectedReviewer": true,
"flagged": true,
"flags": ["..."],
"moderatedAt": "2025-01-15T09:30:00Z",
"moderationReason": "customer_request",
"moderationNote": "string",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"sentiment": "positive",
"sentimentSource": "model",
"sentimentModelId": "string",
"sentimentPromptVersion": "string",
"source": "request",
"importedFrom": "string",
"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": "...",
"metadata": "...",
"notes": "...",
"emailOptOut": "...",
"emailOptOutAt": "...",
"emailOptOutSource": "...",
"createdAt": "...",
"updatedAt": "..."
},
"product": {
"id": "...",
"slug": "...",
"title": "...",
"description": "...",
"imageUrl": "...",
"url": "...",
"brand": "...",
"metadata": "...",
"notes": "...",
"averageRating": "...",
"ratingCount": "...",
"createdAt": "...",
"updatedAt": "..."
},
"response": {
"id": "...",
"reviewId": "...",
"body": "...",
"responderName": "...",
"published": "...",
"metadata": "...",
"createdAt": "...",
"updatedAt": "..."
},
"files": ["..."],
"answers": ["..."],
"reports": ["..."],
"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",
"description": "Unique identifier for the review.",
"idCodec": "sqid"
},
"siteId": {
"type": "string",
"description": "The site the review was left on.",
"idCodec": "sqid"
},
"productId": {
"anyOf": [
{
"type": "string",
"description": "The product being reviewed, or null if that product has since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"type": "string",
"description": "The variant being reviewed, if the reviewer named one.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"type": "string",
"description": "The customer who wrote the review, or null if they have since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"type": "string",
"description": "The survey the review came in through, or null if it was collected another way.",
"idCodec": "sqid"
},
{
"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, or when a signed-in shopper with an order for the product writes it on the storefront, and shown as the Verified buyer badge in the reviews widget. On an imported review (`source` is `import`) it is whatever the previous platform said, which Meisei did not check."
},
"incentivized": {
"type": "boolean",
"description": "Whether the reviewer earned a reward for writing the review, such as loyalty points or a discount. The Loyalty points for reviews automation sets it on each review it rewards, and you can set it yourself for a reward you gave some other way. The reviews widget, the carousel and the media grid show a Rewarded review label beside it."
},
"connectedReviewer": {
"type": "boolean",
"description": "Whether the reviewer works for the business or is otherwise connected to it, such as a staff member, a relative of one, or someone paid to promote the store. Only a person sets it: a review whose email address is on one of your verified domains is flagged with `store_domain_email` for you to decide. The reviews widget, the carousel and the media grid show a Connected to the store label beside it."
},
"flagged": {
"type": "boolean",
"description": "Whether the review is flagged for a person to look at: by content screening on arrival, or by shoppers reporting it from the storefront. A flagged review is never auto-published, and `flags` says why. Publishing or unpublishing a reported review answers its reports and lifts the `reported` flag."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links`, `profanity` or `reported`."
},
"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"
},
"count": {
"description": "On the `reported` flag, how many open reports shoppers have made about the review. The other flags carry no count.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"reasons": {
"description": "On the `reported` flag, every reason the open reports gave, most common first. The other flags carry no reasons.",
"type": "array",
"items": {
"type": "string",
"enum": [
"fake",
"offensive",
"private_information",
"not_about_product",
"other"
],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
}
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening and have no open reports."
},
"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."
},
"moderationReason": {
"anyOf": [
{
"type": "string",
"enum": [
"customer_request",
"spam",
"abusive_content",
"not_about_product",
"duplicate",
"test_data",
"conflict_of_interest",
"edited_by_reviewer",
"other"
],
"description": "Why the review was taken off the storefront or rejected: `customer_request` (the reviewer asked), `spam`, `abusive_content`, `not_about_product`, `duplicate`, `test_data`, `conflict_of_interest` (written by staff or someone with a stake in the business), `edited_by_reviewer` (changed after it was published, waiting for another look) or `other`, which needs a note. There is no entry for a low rating or a negative review."
},
{
"type": "null"
}
],
"description": "Why the review was taken off the storefront or rejected, or null while it is published or still waiting for a decision. Cleared when the review is published again."
},
"moderationNote": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The note recorded with `moderationReason`, or null if there is none."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from, for spotting coordinated reviews. Cleared 90 days after the moderation decision, or 90 days after submission for a review nobody moderated, 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, or 90 days after submission for a review nobody moderated."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address. Kept after the IP address is cleared."
},
"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, or 90 days after submission for a review nobody moderated."
},
"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."
},
"sentiment": {
"anyOf": [
{
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "How the review text reads: `positive`, `neutral` (mixed, or no clear lean) or `negative`. Judged from the words, not from the star rating, so the two can disagree."
},
{
"type": "null"
}
],
"description": "How the review text reads, judged from the words rather than the star rating, so the two can disagree. Null until the review has been labeled, and always null when AI review sentiment is off. Nothing about it is shown to shoppers."
},
"sentimentSource": {
"anyOf": [
{
"type": "string",
"enum": ["model", "rating", "manual"],
"description": "Where the sentiment label came from: `model` read the review text, `rating` derived it from the stars because there was no text, and `manual` means someone set it by hand. A manual label is never overwritten."
},
{
"type": "null"
}
],
"description": "What produced `sentiment`, or null while there is no label."
},
"sentimentModelId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The model that produced the label, or null when it did not come from a model."
},
"sentimentPromptVersion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Which version of the sentiment instructions produced the label, or null when it did not come from a model."
},
"source": {
"anyOf": [
{
"type": "string",
"enum": ["request", "storefront", "import"],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform."
},
{
"type": "null"
}
],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform. Recorded when the review is written and never changed. Null for a review written before Meisei recorded this, where it could not be worked out."
},
"importedFrom": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For an imported review, the platform it was imported from, when the import named one: the import format's ID for a CSV in another platform's format (such as `judgeme` or `yotpo`), or the name given in `importedFrom` on `POST /reviews/import`. Null for every review that was not imported."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"description": "The import job that wrote the record.",
"idCodec": "sqid"
},
"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",
"description": "Unique identifier for the customer.",
"idCodec": "sqid"
},
"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."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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 Meisei."
},
"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",
"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",
"description": "Unique identifier for the product.",
"idCodec": "sqid"
},
"slug": {
"type": "string",
"description": "The product's URL handle, unique within the organization. A Shopify store's sync sets it from the product's handle and follows a rename while no other product holds the new one. The product's Shopify ID is in `externalIds`."
},
"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."
},
"brand": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The product's brand, or null if it is not known. Shopping channels such as Google match reviews to a product by its brand together with a variant's `mpn`. A Shopify store's sync sets it from the product's vendor."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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."
},
"averageRating": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "The average star rating of the product's published reviews, out of 5 and rounded to two decimal places, or null if none of them has a rating."
},
"ratingCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many published reviews of the product have a star rating."
},
"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 Meisei."
},
"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",
"brand",
"metadata",
"notes",
"averageRating",
"ratingCount",
"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",
"description": "Unique identifier for the reply.",
"idCodec": "sqid"
},
"reviewId": {
"type": "string",
"description": "The review this reply answers.",
"idCodec": "sqid"
},
"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": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"description": "Unique identifier for the file.",
"idCodec": "sqid"
},
"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": "Where the file is served from. While the review is published this is its public URL; while it is not, the file is not public, and this is a signed link that stops working after an hour."
}
},
"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",
"description": "The review question that was answered.",
"idCodec": "sqid"
},
"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."
},
"reports": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the report.",
"idCodec": "sqid"
},
"reason": {
"type": "string",
"enum": [
"fake",
"offensive",
"private_information",
"not_about_product",
"other"
],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "What the shopper added in their own words, or null when they added nothing."
},
"resolvedAt": {
"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 a person published or unpublished the review after the report, which answers it. Null while the report is open."
},
"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 shopper reported the review."
}
},
"required": ["id", "reason", "note", "resolvedAt", "createdAt"],
"additionalProperties": false
},
"description": "Reports shoppers made about the review from the storefront widget, newest first, open and resolved alike. The open ones are what the `reported` flag counts."
},
"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",
"incentivized",
"connectedReviewer",
"flagged",
"flags",
"moderatedAt",
"moderationReason",
"moderationNote",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"sentiment",
"sentimentSource",
"sentimentModelId",
"sentimentPromptVersion",
"source",
"importedFrom",
"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. Taking reviews down or rejecting them needs a moderationReason, which is kept on each review. Each review is stamped as moderated either way, every product involved has its review stats recalculated once, and each review whose moderation changed gets an entry in its audit trail naming who decided and why. Each review whose visibility changes triggers the review.updated webhook, and review.published too when it goes live.
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. Every word has to match, from the start of a word, in any order and ignoring case and accents, 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. False needs a moderationReason: a request without one is refused.
moderationReasoncustomer_request | spam | abusive_content | not_about_product | duplicate | test_data | conflict_of_interest | edited_by_reviewer | otherWhy the selected reviews are being taken down or rejected. Required when published is false, and refused otherwise. Kept on each review until it is published again, and recorded in each one's audit trail.
moderationNotestringMore detail on why the reviews were taken down, recorded with the reason. Required when the reason is "other".
curl \
-X PATCH \
"https://your-store.meisei.app/api/reviews/bulk-update" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"published": true,
"moderationReason": "customer_request",
"moderationNote": "string"
}'import { client } from "@meisei/api/admin/client"
import { patchReviewsBulkUpdate } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsBulkUpdate({
body: {
published: true,
moderationReason: "customer_request",
moderationNote: "string",
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.bulk_update(
published: true,
moderation_reason: "customer_request",
moderation_note: "string",
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.bulk_update(
published=True,
moderation_reason="customer_request",
moderation_note="string",
){
"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 Meisei 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://your-store.meisei.app/api/reviews/import" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": [
{
"externalId": "string",
"site": "string",
"customer": "user@example.com",
"product": "string",
"productExternalId": "string",
"productUrl": "string",
"productVariant": "string",
"title": "string",
"body": "string",
"rating": 0,
"published": true,
"pending": true,
"verifiedBuyer": true,
"incentivized": true,
"connectedReviewer": true,
"createdAt": "2025-01-15T09:30:00Z",
"importedFrom": "string",
"imageUrls": [
"..."
],
"response": "string",
"responderName": "string",
"responseCreatedAt": "2025-01-15T09:30:00Z",
"answers": [
"..."
],
"metadata": "..."
}
],
"strict": true
}'import { client } from "@meisei/api/admin/client"
import { postReviewsImport } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postReviewsImport({
body: {
data: [
{
externalId: "string",
site: "string",
customer: "user@example.com",
product: "string",
productExternalId: "string",
productUrl: "string",
productVariant: "string",
title: "string",
body: "string",
rating: 0,
published: true,
pending: true,
verifiedBuyer: true,
incentivized: true,
connectedReviewer: true,
createdAt: "2025-01-15T09:30:00Z",
importedFrom: "string",
imageUrls: ["..."],
response: "string",
responderName: "string",
responseCreatedAt: "2025-01-15T09:30:00Z",
answers: ["..."],
metadata: "...",
},
],
strict: true,
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.import(
data: [
{
external_id: "string",
site: "string",
customer: "user@example.com",
product: "string",
product_external_id: "string",
product_url: "string",
product_variant: "string",
title: "string",
body: "string",
rating: 0,
published: true,
pending: true,
verified_buyer: true,
incentivized: true,
connected_reviewer: true,
created_at: "2025-01-15T09:30:00Z",
imported_from: "string",
image_urls: ["..."],
response: "string",
responder_name: "string",
response_created_at: "2025-01-15T09:30:00Z",
answers: ["..."],
metadata: "...",
},
],
strict: true,
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.import_(
data=[
{
"external_id": "string",
"site": "string",
"customer": "user@example.com",
"product": "string",
"product_external_id": "string",
"product_url": "string",
"product_variant": "string",
"title": "string",
"body": "string",
"rating": 0,
"published": True,
"pending": True,
"verified_buyer": True,
"incentivized": True,
"connected_reviewer": True,
"created_at": "2025-01-15T09:30:00Z",
"imported_from": "string",
"image_urls": ["..."],
"response": "string",
"responder_name": "string",
"response_created_at": "2025-01-15T09:30:00Z",
"answers": [],
"metadata": {},
},
],
strict=True,
){
"imported": [
{
"index": 0,
"id": "A4Ws1igz",
"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",
"description": "The review that was created or updated.",
"idCodec": "sqid"
},
"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
idstringrequiredThe 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, reports, collisions.
curl \
"https://your-store.meisei.app/api/reviews/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { getReviewsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getReviewsById({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.retrieve("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.retrieve("A4Ws1igz"){
"id": "A4Ws1igz",
"siteId": "A4Ws1igz",
"productId": "A4Ws1igz",
"productVariantId": "A4Ws1igz",
"customerId": "A4Ws1igz",
"surveyId": "A4Ws1igz",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"incentivized": true,
"connectedReviewer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0,
"count": 0,
"reasons": ["..."]
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"moderationReason": "customer_request",
"moderationNote": "string",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"sentiment": "positive",
"sentimentSource": "model",
"sentimentModelId": "string",
"sentimentPromptVersion": "string",
"source": "request",
"importedFrom": "string",
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z",
"importJob": {
"id": "A4Ws1igz",
"type": "customers",
"action": "created",
"row": 0,
"createdAt": "2025-01-15T09:30:00Z"
},
"customer": {
"id": "A4Ws1igz",
"email": "user@example.com",
"phone": "string",
"firstName": "string",
"lastName": "string",
"country": "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": "A4Ws1igz",
"slug": "string",
"title": "string",
"description": "string",
"imageUrl": "string",
"url": "string",
"brand": "string",
"metadata": "...",
"notes": "string",
"averageRating": 0,
"ratingCount": 0,
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
},
"response": {
"id": "A4Ws1igz",
"reviewId": "A4Ws1igz",
"body": "string",
"responderName": "string",
"published": true,
"metadata": "...",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
},
"files": [
{
"id": "A4Ws1igz",
"fileName": "string",
"contentType": "string",
"sizeBytes": 0,
"url": "string"
}
],
"answers": [
{
"questionId": "A4Ws1igz",
"label": "string",
"type": "scale",
"step": 0,
"steps": 0,
"lowLabel": "string",
"highLabel": "string",
"middleLabel": "string",
"optionLabels": ["..."]
}
],
"reports": [
{
"id": "A4Ws1igz",
"reason": "fake",
"note": "string",
"resolvedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z"
}
],
"collisions": {
"browserFingerprint": 0,
"ipAddress": 0
}
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the review.",
"idCodec": "sqid"
},
"siteId": {
"type": "string",
"description": "The site the review was left on.",
"idCodec": "sqid"
},
"productId": {
"anyOf": [
{
"type": "string",
"description": "The product being reviewed, or null if that product has since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"type": "string",
"description": "The variant being reviewed, if the reviewer named one.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"type": "string",
"description": "The customer who wrote the review, or null if they have since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"type": "string",
"description": "The survey the review came in through, or null if it was collected another way.",
"idCodec": "sqid"
},
{
"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, or when a signed-in shopper with an order for the product writes it on the storefront, and shown as the Verified buyer badge in the reviews widget. On an imported review (`source` is `import`) it is whatever the previous platform said, which Meisei did not check."
},
"incentivized": {
"type": "boolean",
"description": "Whether the reviewer earned a reward for writing the review, such as loyalty points or a discount. The Loyalty points for reviews automation sets it on each review it rewards, and you can set it yourself for a reward you gave some other way. The reviews widget, the carousel and the media grid show a Rewarded review label beside it."
},
"connectedReviewer": {
"type": "boolean",
"description": "Whether the reviewer works for the business or is otherwise connected to it, such as a staff member, a relative of one, or someone paid to promote the store. Only a person sets it: a review whose email address is on one of your verified domains is flagged with `store_domain_email` for you to decide. The reviews widget, the carousel and the media grid show a Connected to the store label beside it."
},
"flagged": {
"type": "boolean",
"description": "Whether the review is flagged for a person to look at: by content screening on arrival, or by shoppers reporting it from the storefront. A flagged review is never auto-published, and `flags` says why. Publishing or unpublishing a reported review answers its reports and lifts the `reported` flag."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links`, `profanity` or `reported`."
},
"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"
},
"count": {
"description": "On the `reported` flag, how many open reports shoppers have made about the review. The other flags carry no count.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"reasons": {
"description": "On the `reported` flag, every reason the open reports gave, most common first. The other flags carry no reasons.",
"type": "array",
"items": {
"type": "string",
"enum": ["fake", "offensive", "private_information", "not_about_product", "other"],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
}
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening and have no open reports."
},
"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."
},
"moderationReason": {
"anyOf": [
{
"type": "string",
"enum": [
"customer_request",
"spam",
"abusive_content",
"not_about_product",
"duplicate",
"test_data",
"conflict_of_interest",
"edited_by_reviewer",
"other"
],
"description": "Why the review was taken off the storefront or rejected: `customer_request` (the reviewer asked), `spam`, `abusive_content`, `not_about_product`, `duplicate`, `test_data`, `conflict_of_interest` (written by staff or someone with a stake in the business), `edited_by_reviewer` (changed after it was published, waiting for another look) or `other`, which needs a note. There is no entry for a low rating or a negative review."
},
{
"type": "null"
}
],
"description": "Why the review was taken off the storefront or rejected, or null while it is published or still waiting for a decision. Cleared when the review is published again."
},
"moderationNote": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The note recorded with `moderationReason`, or null if there is none."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from, for spotting coordinated reviews. Cleared 90 days after the moderation decision, or 90 days after submission for a review nobody moderated, 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, or 90 days after submission for a review nobody moderated."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address. Kept after the IP address is cleared."
},
"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, or 90 days after submission for a review nobody moderated."
},
"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."
},
"sentiment": {
"anyOf": [
{
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "How the review text reads: `positive`, `neutral` (mixed, or no clear lean) or `negative`. Judged from the words, not from the star rating, so the two can disagree."
},
{
"type": "null"
}
],
"description": "How the review text reads, judged from the words rather than the star rating, so the two can disagree. Null until the review has been labeled, and always null when AI review sentiment is off. Nothing about it is shown to shoppers."
},
"sentimentSource": {
"anyOf": [
{
"type": "string",
"enum": ["model", "rating", "manual"],
"description": "Where the sentiment label came from: `model` read the review text, `rating` derived it from the stars because there was no text, and `manual` means someone set it by hand. A manual label is never overwritten."
},
{
"type": "null"
}
],
"description": "What produced `sentiment`, or null while there is no label."
},
"sentimentModelId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The model that produced the label, or null when it did not come from a model."
},
"sentimentPromptVersion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Which version of the sentiment instructions produced the label, or null when it did not come from a model."
},
"source": {
"anyOf": [
{
"type": "string",
"enum": ["request", "storefront", "import"],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform."
},
{
"type": "null"
}
],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform. Recorded when the review is written and never changed. Null for a review written before Meisei recorded this, where it could not be worked out."
},
"importedFrom": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For an imported review, the platform it was imported from, when the import named one: the import format's ID for a CSV in another platform's format (such as `judgeme` or `yotpo`), or the name given in `importedFrom` on `POST /reviews/import`. Null for every review that was not imported."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"description": "The import job that wrote the record.",
"idCodec": "sqid"
},
"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",
"description": "Unique identifier for the customer.",
"idCodec": "sqid"
},
"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."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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 Meisei."
},
"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",
"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",
"description": "Unique identifier for the product.",
"idCodec": "sqid"
},
"slug": {
"type": "string",
"description": "The product's URL handle, unique within the organization. A Shopify store's sync sets it from the product's handle and follows a rename while no other product holds the new one. The product's Shopify ID is in `externalIds`."
},
"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."
},
"brand": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The product's brand, or null if it is not known. Shopping channels such as Google match reviews to a product by its brand together with a variant's `mpn`. A Shopify store's sync sets it from the product's vendor."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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."
},
"averageRating": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "The average star rating of the product's published reviews, out of 5 and rounded to two decimal places, or null if none of them has a rating."
},
"ratingCount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many published reviews of the product have a star rating."
},
"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 Meisei."
},
"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",
"brand",
"metadata",
"notes",
"averageRating",
"ratingCount",
"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",
"description": "Unique identifier for the reply.",
"idCodec": "sqid"
},
"reviewId": {
"type": "string",
"description": "The review this reply answers.",
"idCodec": "sqid"
},
"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": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"description": "Unique identifier for the file.",
"idCodec": "sqid"
},
"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": "Where the file is served from. While the review is published this is its public URL; while it is not, the file is not public, and this is a signed link that stops working after an hour."
}
},
"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",
"description": "The review question that was answered.",
"idCodec": "sqid"
},
"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."
},
"reports": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the report.",
"idCodec": "sqid"
},
"reason": {
"type": "string",
"enum": ["fake", "offensive", "private_information", "not_about_product", "other"],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "What the shopper added in their own words, or null when they added nothing."
},
"resolvedAt": {
"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 a person published or unpublished the review after the report, which answers it. Null while the report is open."
},
"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 shopper reported the review."
}
},
"required": ["id", "reason", "note", "resolvedAt", "createdAt"],
"additionalProperties": false
},
"description": "Reports shoppers made about the review from the storefront widget, newest first, open and resolved alike. The open ones are what the `reported` flag counts."
},
"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",
"incentivized",
"connectedReviewer",
"flagged",
"flags",
"moderatedAt",
"moderationReason",
"moderationNote",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"sentiment",
"sentimentSource",
"sentimentModelId",
"sentimentPromptVersion",
"source",
"importedFrom",
"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. Taking a review down or rejecting it needs a moderationReason, which stays on the review until it is published again; every publish, unpublish and rejection is written to the review's audit trail with who decided and why. 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. Set sentiment to overrule the label a model gave the review, or send null to clear it. Set incentivized to mark a review whose reviewer earned a reward for writing it, which the widgets show as a Rewarded review label, and connectedReviewer to mark a reviewer who works for or is connected to the store, which they show as a Connected to the store label. The rating is the customer's and cannot be changed. Triggers the review.updated webhook.
Requires the reviews:write permission.
Path parameters
idstringrequiredThe 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. Setting it to false, to take a review down or reject it, needs a moderationReason: a request without one is refused.
moderationReasoncustomer_request | spam | abusive_content | not_about_product | duplicate | test_data | conflict_of_interest | edited_by_reviewer | otherWhy the review is being taken down or rejected. Required when published is false, and refused otherwise. Kept on the review until it is published again, and recorded in its audit trail.
moderationNotestringMore detail on why the review was taken down, recorded with the reason. Required when the reason is "other".
incentivizedbooleanMarks the review as one whose reviewer earned a reward for writing it, or clears the mark. Set it for a reward you gave by hand, such as a discount code. It changes only the label shoppers see, not the review's moderation. Omit to leave it alone.
connectedReviewerbooleanMarks the reviewer as someone who works for or is otherwise connected to the store, or clears the mark. It changes only the label shoppers see, not the review's moderation. Omit to leave it alone.
sentimentpositive | neutral | negative | nullSets the sentiment label by hand, which then survives any later pass. Send null to clear it, and the review is labeled again the next time its text changes. Omit to leave it alone.
metadataobjectReplaces the review's metadata wholesale. Omit to leave it alone.
curl \
-X PATCH \
"https://your-store.meisei.app/api/reviews/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "string",
"body": "string",
"published": true,
"moderationReason": "customer_request",
"moderationNote": "string",
"incentivized": true,
"connectedReviewer": true,
"sentiment": "positive",
"metadata": "..."
}'import { client } from "@meisei/api/admin/client"
import { patchReviewsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsById({
path: { id: "A4Ws1igz" },
body: {
title: "string",
body: "string",
published: true,
moderationReason: "customer_request",
moderationNote: "string",
incentivized: true,
connectedReviewer: true,
sentiment: "positive",
metadata: "...",
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.update(
"A4Ws1igz",
title: "string",
body: "string",
published: true,
moderation_reason: "customer_request",
moderation_note: "string",
incentivized: true,
connected_reviewer: true,
sentiment: "positive",
metadata: "...",
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.update(
"A4Ws1igz",
title="string",
body="string",
published=True,
moderation_reason="customer_request",
moderation_note="string",
incentivized=True,
connected_reviewer=True,
sentiment="positive",
metadata={},
){
"id": "A4Ws1igz",
"siteId": "A4Ws1igz",
"productId": "A4Ws1igz",
"productVariantId": "A4Ws1igz",
"customerId": "A4Ws1igz",
"surveyId": "A4Ws1igz",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"incentivized": true,
"connectedReviewer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0,
"count": 0,
"reasons": ["..."]
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"moderationReason": "customer_request",
"moderationNote": "string",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"sentiment": "positive",
"sentimentSource": "model",
"sentimentModelId": "string",
"sentimentPromptVersion": "string",
"source": "request",
"importedFrom": "string",
"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",
"description": "Unique identifier for the review.",
"idCodec": "sqid"
},
"siteId": {
"type": "string",
"description": "The site the review was left on.",
"idCodec": "sqid"
},
"productId": {
"anyOf": [
{
"type": "string",
"description": "The product being reviewed, or null if that product has since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"type": "string",
"description": "The variant being reviewed, if the reviewer named one.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"type": "string",
"description": "The customer who wrote the review, or null if they have since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"type": "string",
"description": "The survey the review came in through, or null if it was collected another way.",
"idCodec": "sqid"
},
{
"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, or when a signed-in shopper with an order for the product writes it on the storefront, and shown as the Verified buyer badge in the reviews widget. On an imported review (`source` is `import`) it is whatever the previous platform said, which Meisei did not check."
},
"incentivized": {
"type": "boolean",
"description": "Whether the reviewer earned a reward for writing the review, such as loyalty points or a discount. The Loyalty points for reviews automation sets it on each review it rewards, and you can set it yourself for a reward you gave some other way. The reviews widget, the carousel and the media grid show a Rewarded review label beside it."
},
"connectedReviewer": {
"type": "boolean",
"description": "Whether the reviewer works for the business or is otherwise connected to it, such as a staff member, a relative of one, or someone paid to promote the store. Only a person sets it: a review whose email address is on one of your verified domains is flagged with `store_domain_email` for you to decide. The reviews widget, the carousel and the media grid show a Connected to the store label beside it."
},
"flagged": {
"type": "boolean",
"description": "Whether the review is flagged for a person to look at: by content screening on arrival, or by shoppers reporting it from the storefront. A flagged review is never auto-published, and `flags` says why. Publishing or unpublishing a reported review answers its reports and lifts the `reported` flag."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links`, `profanity` or `reported`."
},
"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"
},
"count": {
"description": "On the `reported` flag, how many open reports shoppers have made about the review. The other flags carry no count.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"reasons": {
"description": "On the `reported` flag, every reason the open reports gave, most common first. The other flags carry no reasons.",
"type": "array",
"items": {
"type": "string",
"enum": ["fake", "offensive", "private_information", "not_about_product", "other"],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
}
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening and have no open reports."
},
"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."
},
"moderationReason": {
"anyOf": [
{
"type": "string",
"enum": [
"customer_request",
"spam",
"abusive_content",
"not_about_product",
"duplicate",
"test_data",
"conflict_of_interest",
"edited_by_reviewer",
"other"
],
"description": "Why the review was taken off the storefront or rejected: `customer_request` (the reviewer asked), `spam`, `abusive_content`, `not_about_product`, `duplicate`, `test_data`, `conflict_of_interest` (written by staff or someone with a stake in the business), `edited_by_reviewer` (changed after it was published, waiting for another look) or `other`, which needs a note. There is no entry for a low rating or a negative review."
},
{
"type": "null"
}
],
"description": "Why the review was taken off the storefront or rejected, or null while it is published or still waiting for a decision. Cleared when the review is published again."
},
"moderationNote": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The note recorded with `moderationReason`, or null if there is none."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from, for spotting coordinated reviews. Cleared 90 days after the moderation decision, or 90 days after submission for a review nobody moderated, 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, or 90 days after submission for a review nobody moderated."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address. Kept after the IP address is cleared."
},
"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, or 90 days after submission for a review nobody moderated."
},
"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."
},
"sentiment": {
"anyOf": [
{
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "How the review text reads: `positive`, `neutral` (mixed, or no clear lean) or `negative`. Judged from the words, not from the star rating, so the two can disagree."
},
{
"type": "null"
}
],
"description": "How the review text reads, judged from the words rather than the star rating, so the two can disagree. Null until the review has been labeled, and always null when AI review sentiment is off. Nothing about it is shown to shoppers."
},
"sentimentSource": {
"anyOf": [
{
"type": "string",
"enum": ["model", "rating", "manual"],
"description": "Where the sentiment label came from: `model` read the review text, `rating` derived it from the stars because there was no text, and `manual` means someone set it by hand. A manual label is never overwritten."
},
{
"type": "null"
}
],
"description": "What produced `sentiment`, or null while there is no label."
},
"sentimentModelId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The model that produced the label, or null when it did not come from a model."
},
"sentimentPromptVersion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Which version of the sentiment instructions produced the label, or null when it did not come from a model."
},
"source": {
"anyOf": [
{
"type": "string",
"enum": ["request", "storefront", "import"],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform."
},
{
"type": "null"
}
],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform. Recorded when the review is written and never changed. Null for a review written before Meisei recorded this, where it could not be worked out."
},
"importedFrom": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For an imported review, the platform it was imported from, when the import named one: the import format's ID for a CSV in another platform's format (such as `judgeme` or `yotpo`), or the name given in `importedFrom` on `POST /reviews/import`. Null for every review that was not imported."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"incentivized",
"connectedReviewer",
"flagged",
"flags",
"moderatedAt",
"moderationReason",
"moderationNote",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"sentiment",
"sentimentSource",
"sentimentModelId",
"sentimentPromptVersion",
"source",
"importedFrom",
"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
idstringrequiredThe 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://your-store.meisei.app/api/reviews/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "customer_request",
"note": "string"
}'import { client } from "@meisei/api/admin/client"
import { deleteReviewsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteReviewsById({
path: { id: "A4Ws1igz" },
body: {
reason: "customer_request",
note: "string",
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.delete(
"A4Ws1igz",
reason: "customer_request",
note: "string",
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.delete(
"A4Ws1igz",
reason="customer_request",
note="string",
){
"id": "A4Ws1igz",
"siteId": "A4Ws1igz",
"productId": "A4Ws1igz",
"productVariantId": "A4Ws1igz",
"customerId": "A4Ws1igz",
"surveyId": "A4Ws1igz",
"title": "string",
"body": "string",
"originalTitle": "string",
"originalBody": "string",
"redactedAt": "2025-01-15T09:30:00Z",
"rating": 0,
"published": true,
"verifiedBuyer": true,
"incentivized": true,
"connectedReviewer": true,
"flagged": true,
"flags": [
{
"code": "string",
"detail": "string",
"score": 0,
"count": 0,
"reasons": ["..."]
}
],
"moderatedAt": "2025-01-15T09:30:00Z",
"moderationReason": "customer_request",
"moderationNote": "string",
"ipAddress": "string",
"browserFingerprint": "string",
"ipCountry": "string",
"userAgent": "string",
"submitSeconds": 0,
"sentiment": "positive",
"sentimentSource": "model",
"sentimentModelId": "string",
"sentimentPromptVersion": "string",
"source": "request",
"importedFrom": "string",
"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",
"description": "Unique identifier for the review.",
"idCodec": "sqid"
},
"siteId": {
"type": "string",
"description": "The site the review was left on.",
"idCodec": "sqid"
},
"productId": {
"anyOf": [
{
"type": "string",
"description": "The product being reviewed, or null if that product has since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The product being reviewed, or null if that product has since been deleted."
},
"productVariantId": {
"anyOf": [
{
"type": "string",
"description": "The variant being reviewed, if the reviewer named one.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The variant being reviewed, if the reviewer named one."
},
"customerId": {
"anyOf": [
{
"type": "string",
"description": "The customer who wrote the review, or null if they have since been deleted.",
"idCodec": "sqid"
},
{
"type": "null"
}
],
"description": "The customer who wrote the review, or null if they have since been deleted."
},
"surveyId": {
"anyOf": [
{
"type": "string",
"description": "The survey the review came in through, or null if it was collected another way.",
"idCodec": "sqid"
},
{
"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, or when a signed-in shopper with an order for the product writes it on the storefront, and shown as the Verified buyer badge in the reviews widget. On an imported review (`source` is `import`) it is whatever the previous platform said, which Meisei did not check."
},
"incentivized": {
"type": "boolean",
"description": "Whether the reviewer earned a reward for writing the review, such as loyalty points or a discount. The Loyalty points for reviews automation sets it on each review it rewards, and you can set it yourself for a reward you gave some other way. The reviews widget, the carousel and the media grid show a Rewarded review label beside it."
},
"connectedReviewer": {
"type": "boolean",
"description": "Whether the reviewer works for the business or is otherwise connected to it, such as a staff member, a relative of one, or someone paid to promote the store. Only a person sets it: a review whose email address is on one of your verified domains is flagged with `store_domain_email` for you to decide. The reviews widget, the carousel and the media grid show a Connected to the store label beside it."
},
"flagged": {
"type": "boolean",
"description": "Whether the review is flagged for a person to look at: by content screening on arrival, or by shoppers reporting it from the storefront. A flagged review is never auto-published, and `flags` says why. Publishing or unpublishing a reported review answers its reports and lifts the `reported` flag."
},
"flags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Machine-readable reason the review was flagged, such as `links`, `profanity` or `reported`."
},
"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"
},
"count": {
"description": "On the `reported` flag, how many open reports shoppers have made about the review. The other flags carry no count.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"reasons": {
"description": "On the `reported` flag, every reason the open reports gave, most common first. The other flags carry no reasons.",
"type": "array",
"items": {
"type": "string",
"enum": ["fake", "offensive", "private_information", "not_about_product", "other"],
"description": "Why the shopper reported the review: `fake` (looks fake or not from a real customer), `offensive` (offensive or abusive), `private_information` (shares someone's private information), `not_about_product` (not about this product) or `other` (something else)."
}
}
},
"required": ["code", "detail"],
"additionalProperties": false
},
"description": "Why the review was flagged. Empty for reviews that passed screening and have no open reports."
},
"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."
},
"moderationReason": {
"anyOf": [
{
"type": "string",
"enum": [
"customer_request",
"spam",
"abusive_content",
"not_about_product",
"duplicate",
"test_data",
"conflict_of_interest",
"edited_by_reviewer",
"other"
],
"description": "Why the review was taken off the storefront or rejected: `customer_request` (the reviewer asked), `spam`, `abusive_content`, `not_about_product`, `duplicate`, `test_data`, `conflict_of_interest` (written by staff or someone with a stake in the business), `edited_by_reviewer` (changed after it was published, waiting for another look) or `other`, which needs a note. There is no entry for a low rating or a negative review."
},
{
"type": "null"
}
],
"description": "Why the review was taken off the storefront or rejected, or null while it is published or still waiting for a decision. Cleared when the review is published again."
},
"moderationNote": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The note recorded with `moderationReason`, or null if there is none."
},
"ipAddress": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "IP address the review was submitted from, for spotting coordinated reviews. Cleared 90 days after the moderation decision, or 90 days after submission for a review nobody moderated, 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, or 90 days after submission for a review nobody moderated."
},
"ipCountry": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Two-letter country code the review was submitted from, derived from the IP address. Kept after the IP address is cleared."
},
"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, or 90 days after submission for a review nobody moderated."
},
"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."
},
"sentiment": {
"anyOf": [
{
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "How the review text reads: `positive`, `neutral` (mixed, or no clear lean) or `negative`. Judged from the words, not from the star rating, so the two can disagree."
},
{
"type": "null"
}
],
"description": "How the review text reads, judged from the words rather than the star rating, so the two can disagree. Null until the review has been labeled, and always null when AI review sentiment is off. Nothing about it is shown to shoppers."
},
"sentimentSource": {
"anyOf": [
{
"type": "string",
"enum": ["model", "rating", "manual"],
"description": "Where the sentiment label came from: `model` read the review text, `rating` derived it from the stars because there was no text, and `manual` means someone set it by hand. A manual label is never overwritten."
},
{
"type": "null"
}
],
"description": "What produced `sentiment`, or null while there is no label."
},
"sentimentModelId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The model that produced the label, or null when it did not come from a model."
},
"sentimentPromptVersion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Which version of the sentiment instructions produced the label, or null when it did not come from a model."
},
"source": {
"anyOf": [
{
"type": "string",
"enum": ["request", "storefront", "import"],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform."
},
{
"type": "null"
}
],
"description": "Where the review came from: `request` was written through a review request sent for an order, `storefront` was written on the storefront without one, and `import` was brought in from another platform. Recorded when the review is written and never changed. Null for a review written before Meisei recorded this, where it could not be worked out."
},
"importedFrom": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For an imported review, the platform it was imported from, when the import named one: the import format's ID for a CSV in another platform's format (such as `judgeme` or `yotpo`), or the name given in `importedFrom` on `POST /reviews/import`. Null for every review that was not imported."
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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",
"incentivized",
"connectedReviewer",
"flagged",
"flags",
"moderatedAt",
"moderationReason",
"moderationNote",
"ipAddress",
"browserFingerprint",
"ipCountry",
"userAgent",
"submitSeconds",
"sentiment",
"sentimentSource",
"sentimentModelId",
"sentimentPromptVersion",
"source",
"importedFrom",
"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
idstringrequiredThe 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://your-store.meisei.app/api/reviews/A4Ws1igz/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "string",
"published": true,
"metadata": "...",
"responderName": "string"
}'import { client } from "@meisei/api/admin/client"
import { postReviewsByIdResponse } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postReviewsByIdResponse({
path: { id: "A4Ws1igz" },
body: {
body: "string",
published: true,
metadata: "...",
responderName: "string",
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.response.create(
"A4Ws1igz",
body: "string",
published: true,
metadata: "...",
responder_name: "string",
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.response.create(
"A4Ws1igz",
body="string",
published=True,
metadata={},
responder_name="string",
){
"id": "A4Ws1igz",
"reviewId": "A4Ws1igz",
"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",
"description": "Unique identifier for the reply.",
"idCodec": "sqid"
},
"reviewId": {
"type": "string",
"description": "The review this reply answers.",
"idCodec": "sqid"
},
"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": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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
idstringrequiredThe 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://your-store.meisei.app/api/reviews/A4Ws1igz/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "string",
"responderName": "string",
"published": true,
"metadata": "..."
}'import { client } from "@meisei/api/admin/client"
import { patchReviewsByIdResponse } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchReviewsByIdResponse({
path: { id: "A4Ws1igz" },
body: {
body: "string",
responderName: "string",
published: true,
metadata: "...",
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.response.update(
"A4Ws1igz",
body: "string",
responder_name: "string",
published: true,
metadata: "...",
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.response.update(
"A4Ws1igz",
body="string",
responder_name="string",
published=True,
metadata={},
){
"id": "A4Ws1igz",
"reviewId": "A4Ws1igz",
"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",
"description": "Unique identifier for the reply.",
"idCodec": "sqid"
},
"reviewId": {
"type": "string",
"description": "The review this reply answers.",
"idCodec": "sqid"
},
"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": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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
idstringrequiredThe ID of the replied-to review.
curl \
-X DELETE \
"https://your-store.meisei.app/api/reviews/A4Ws1igz/response" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { deleteReviewsByIdResponse } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteReviewsByIdResponse({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.reviews.response.delete("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.reviews.response.delete("A4Ws1igz"){
"id": "A4Ws1igz",
"reviewId": "A4Ws1igz",
"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",
"description": "Unique identifier for the reply.",
"idCodec": "sqid"
},
"reviewId": {
"type": "string",
"description": "The review this reply answers.",
"idCodec": "sqid"
},
"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": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A metadata value: a string, a number, true or false, or a list of strings. A key with a metadata definition holds the type its definition names."
},
"description": "Key/value pairs for your own data. Keys you define with `/metadata-definitions` are typed and validated; any other key is free-form. 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."
}