Reviews

The Reviews app collects product reviews from people who bought the product, moderates them, and keeps the per-product figures that the shop and its feeds read.

This page is the API reference for developers reading or writing reviews.

The Reviews app holds the product reviews a shop collects. A review is one customer's stars, title and text about one product. A review written through a review link is a verified purchase: the app checks that the link's order is completed and holds the product. A review submitted through a link waits for moderation, unless the app setting auto_publish_min_rating (default 0, off) lets ratings at or above it go live at once. A review recorded by hand or imported through POST /v1/reviews/reviews is stored as pending unless the caller sends state: published.

The app also keeps the figures of each product (count, average and star spread of its published reviews, per market and for all markets together) and answers them for the product page, the mail product blocks and the structured data and feeds. The app sends no mail itself: a review link is handed out through a journey.

Objects

ObjectWhat it holds
ReviewThe product, the order and contact it came from, the author's name, rating (1 to 5), title, body, verified_purchase, state (pending, published or rejected), the merchant reply (stored as merchant_reply), and the six trash fields.
Review requestOne link per completed order and contact: the products it names, its token, expires_at, state (issued, used or expired), journey_ref, market and locale. The token is never part of a list answer.
AggregateThe figures of one product in one market: count, average, the distribution of stars from 1 to 5, and verified_count. Market * holds the figures for all markets together.

A review is one per purchase. POST /v1/reviews/reviews refuses a second review of the same product from the same order and contact with 409 (already_reviewed), when both order_id and contact_id are sent. Through a link, a duplicate entry comes back in rejected with already_reviewed, or the call is 400 with nothing_accepted when every entry is refused. A review in the trash counts nowhere and leaves the product page and the figures until it is restored.

Base path: https://api.revenexx.com/v1/reviews.

Scopes

A key calls these routes with the capability key of each route. The App's manifest names one per operation, in the form reviews.<group>.<operation>, and the API keys page explains that a key holds capability keys or a namespace such as reviews.*. The scope names are listed in the tables below. A call that needs a scope the key does not hold is refused with 403.

The market header

The app's routes do not use X-Revenexx-Market: its code never looks at the header. A review's market comes from the body market of POST /v1/reviews/reviews. A review without one has no market, and it shows only in the all-markets figures (market *), not in any per-market figure. GET /v1/reviews/products/{product_id} with ?market=DE returns only the reviews assigned to DE; without market it returns every published review. Review requests take their market from the body market, or else from the order's metadata.market.

Reviews

OperationScopeWhat it does
GET /v1/reviews/reviewsreviews.reviews.listLists the reviews of the tenant in every state. The trash is left out unless trashed asks for it.
POST /v1/reviews/reviewsreviews.reviews.createRecords a review by hand or from an import.
GET /v1/reviews/reviews/{id}reviews.reviews.getReads one review.
DELETE /v1/reviews/reviews/{id}reviews.reviews.deleteMoves a review into the trash. A second DELETE on the same review deletes it for good.
POST /v1/reviews/reviews/{id}/restorereviews.reviews.restoreTakes a review back out of the trash.
POST /v1/reviews/reviews/trash/emptyreviews.reviews.empty-trashDeletes every review in the trash for good.
POST /v1/reviews/reviews/trash/purgereviews.reviews.purgeDeletes the reviews that have been in the trash longer than the tenant's trash retention. It is the nightly purge, callable by hand.
GET /v1/reviews/reviews/{id}/starsreviews.aggregates.review_starsReads the stars of the review's product as bar rows, five down to one, across all markets.
  • GET /v1/reviews/reviews filters: state (for the moderation queue use state=pending), product_id, contact_id, rating, verified_purchase=true, trashed (exclude, only or include), and further plain columns. limit takes 1 to 200 (default 50), with offset and order.
  • POST /v1/reviews/reviews body: required product_id and rating. Optional: title, body, sku, product_name, order_id, contact_id, author_first_name, author_last_name, locale, market, source (admin or import; omitted or null means admin, an empty string is source_invalid), state (pending by default, or published), published_at, external_id, external_refs, source_synced_at and source_data. title is at most 200 characters and body at most 5000. The purchase is verified only when order_id and contact_id name a completed order of that contact that holds the product.
  • POST /v1/reviews/reviews errors: 400 with product_required, rating_invalid, too_long, source_invalid or state_invalid (details.field names the field). 409 with already_reviewed, or in_trash when the review of that purchase is in the trash.
  • DELETE errors: 404 when no review has the id. A second DELETE on a review that is already in the trash deletes it for good.
  • POST .../restore error: 409 with not_in_trash.
  • POST .../publish, .../reject and .../reply on a trashed review: 409 with in_trash. Restore it first.
  • Trash empty and purge: a call from a storefront contact is 403 with purge_not_allowed. A review that something still blocks stays in the trash and is named with its reason. A purge is skipped when the trash retention cannot be read, and then nothing is deleted. Each call handles at most 1000 trashed rows; a larger trash needs another call.
  • GET .../stars: the rows are five stars down to one, each with its count and share, then footer rows: the average and the review count always, and the verified-purchases row only when at least one review is verified. The labels follow the Accept-Language header; German is the default unless English is asked for. No published review gives no rows.

Moderation

OperationScopeWhat it does
POST /v1/reviews/reviews/{id}/publishreviews.moderation.publishShows the review in the shop and recomputes the product figures. Publishing a published review answers it unchanged.
POST /v1/reviews/reviews/{id}/rejectreviews.moderation.rejectRejects a review, or takes a published one down.
POST /v1/reviews/reviews/{id}/replyreviews.moderation.replyAnswers a review publicly, or removes the answer.
  • reject body: reason, required. Without it the answer is 400 with reason_required.
  • reply body: a body field reply, a string of up to 5000 characters. An empty or null value removes the answer. The answer is stored in merchant_reply. A longer text is 400 with too_long. A rejected review cannot be answered: 409 with review_rejected.
  • Customer text: the app never edits the title or body of a review.
  • Errors on all three: 404 when no review has the id, and 409 with in_trash on a trashed review.

Review requests

OperationScopeWhat it does
POST /v1/reviews/requestsreviews.requests.issueIssues the review link for one completed order and its contact.
GET /v1/reviews/requestsreviews.requests.listLists the issued links, without their tokens.
GET /v1/reviews/requests/{token}reviews.requests.formReads the products of a link and which of them are reviewed. A storefront renders its form from this.
POST /v1/reviews/submit/{token}reviews.requests.submitWrites the reviews of a link, one per product.
  • POST /v1/reviews/requests body: required order_id and contact_id, both UUIDs. Optional: journey_ref, market, locale and product_ids. The same order and contact get the same link only while that link is issued and unexpired. Any other existing link, including a used one, is replaced by a new one. The link names the order's products that the contact has not reviewed yet.
  • POST /v1/reviews/requests errors: 400 with order_and_contact_required. 404 with order_not_found. 409 with order_not_completed, contact_not_on_order or nothing_to_review. 502 with orders_unavailable when the orders app does not answer.
  • GET /v1/reviews/requests filters: id, order_id, order_number, contact_id, organization_id, state, journey_ref, market, locale, reviewed_count and issued_count. A token cannot be filtered on.
  • GET /v1/reviews/requests/{token} errors: 404 with unknown_token. 410 with request_expired when the link has expired.
  • POST /v1/reviews/submit/{token} body: reviews, an array with one entry per product. Each entry has product_id and rating (required) and title and body (optional). The app writes the accepted entries and names the rest in rejected. Reviews wait for moderation unless the app setting auto_publish_min_rating is set and the rating is at or above it.
  • POST /v1/reviews/submit/{token} errors: 400 with reviews_required, or nothing_accepted with each entry's reason: product_not_in_request, already_reviewed, rating_invalid or too_long. 404 with unknown_token. 410 with request_expired when the link has expired.

Products and figures

OperationScopeWhat it does
GET /v1/reviews/products/{product_id}reviews.products.getReads the published reviews and the figures of one product.
GET /v1/reviews/aggregatesreviews.aggregates.listReads the figures of many products.
POST /v1/reviews/aggregates/rebuildreviews.aggregates.rebuildRecomputes the figures from the published reviews.
  • GET /v1/reviews/products/{product_id}: sort is newest, oldest, rating_desc or rating_asc. Also market, limit (default 10, at most 100) and offset. 400 when the id is not a product id.
  • GET /v1/reviews/aggregates: with product_ids (comma-separated, at most 200) it answers one entry per product, zero for a product nobody reviewed. Without it, the stored figures are paged. Filters: market (default *, the all-markets figures), product_id, limit (default 50, at most 200), offset and order (a column, optionally .asc or .desc, default count.desc). 400 with product_ids_invalid or too_many_products.
  • POST /v1/reviews/aggregates/rebuild body: product_ids, an optional array of UUIDs. Without it the rebuild covers every product with reviews or figures, up to 1000 per call. 400 with product_ids_invalid.

Privacy and jobs

OperationScopeWhat it does
POST /v1/reviews/contacts/{contact_id}/anonymisereviews.privacy.anonymiseRemoves a person from their reviews.
POST /v1/reviews/maintenance/runreviews.jobs.maintainRuns the hourly job by hand: it expires review links past their date.
  • Anonymise: the stars and the text stay, so the product figures do not move. The name, contact and company are cleared, and the person's review links are removed. The review text itself is not changed.
  • Automatic on deletion: not yet (as of 2026-10). The app is set to run this when the customers or marketing-consents apps report a deletion, but the platform does not deliver those events yet. Until it does, a caller must send POST /v1/reviews/contacts/{contact_id}/anonymise.

Vocabularies

OperationScopeWhat it does
GET /v1/reviews/vocabulariesreviews.vocabularies.listLists the closed vocabularies the app enforces.
GET /v1/reviews/vocabularies/{name}reviews.vocabularies.getReads one vocabulary with its values and labels. 404 when there is no such vocabulary.
Was this page helpful?