Happy House - Ecommerce Docs
Developer ResourcesReviews

Reviews API Reference

Complete API contracts for the Reviews module, including routes, auth, DTOs, responses, errors, examples, and integration notes.

Reviews - API Reference

Audience: Frontend engineers, mobile engineers, backend engineers, QA, and API consumers. Scope: All 20 review endpoints — 2 public, 7 customer, 11 admin.

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/reviews/customer/{product-review,my-review}/*.controller.ts, admin/{review,report}/*.controller.tsRoutes, methods, guards, permissions, rate limits
DTOsdto/*.tsValidation
Servicesshared + customer + admin servicesBehavior, transitions
Schemapackages/db/src/schema/reviews/*.tsTables, GENERATED aggregate
Error registryapps/api/src/common/types/error-codes.ts (// REVIEW)REVIEW_* codes

2. Module Summary

FieldValue
Module namereviews
Module slugreviews
Primary actorsguest, customer, admin
API surfacesmobile (public + customer), admin
Base route prefixes/api/mobile/products/:productId/reviews, /api/mobile/reviews, /api/admin/reviews, /api/admin/review-reports
Auth model@Public() (2 routes); JwtAuthGuard (customer); JwtAuthGuard + RoleGuard (admin)
PersistencePostgreSQL (4 tables), Redis (product_review cache domain)
Runtime source of truthproduct_review rows + the GENERATED aggregate
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
productIdThe product public_id uuid — exposed as basic.id, not the slugschemaPublic routes
statuspending / published / rejected / hidden / deletedschemaAll routes
versionOptimistic lock — edit and every moderation actionschemaEdit, moderation, withdraw
canCreateEligibility result — the key to key offeligibility serviceEligibility
existingReview.versionWhat the edit must send backeligibility serviceEdit
recordedfalse + 200 = already reported — a successreport serviceReport
moderationReasonThe only explanation the customer getsschemaRejected/hidden
verifiedPurchaseDerived — the order line FKs make it structuralresponse builderPublic reviews

4. API Surface Map

4.1 Public — /api/mobile/products/{productId}/reviews

MethodPathAuthPurpose
GET/nonePublished reviews (page/size/sort/rating/withTextOnly)
GET/summarynoneThe aggregate

{productId} is the public_id uuid — not the slug (a slug returns 400).

4.2 Customer — /api/mobile/reviews

MethodPathAuthPurpose
GET/eligibilityJWTCan this customer review this product?
GET/JWTMy reviews (every state without a filter)
GET/:idJWTOne of my reviews
POST/JWTCreate (201, idempotent)
PATCH/:idJWTEdit (→ pending)
DELETE/:idJWTWithdraw (version as query param)
POST/:id/reportJWTReport

4.3 Admin — /api/admin/reviews

MethodPathPermissionPurpose
GET/Reviews_READList (two queues)
GET/:idReviews_READDetail
GET/:id/eventsReviews_READTimeline
POST/:id/approveReviews_UPDATEApprove
POST/:id/rejectReviews_UPDATEReject (reason required)
POST/:id/hideReviews_UPDATEHide (reason required; resolves reports)
POST/:id/restoreReviews_RESTORERestore
POST/bulk/approveReviews_UPDATEBulk approve
POST/summary/:productId/recalculateReviews_UPDATERecalculate (202)

4.4 Admin — /api/admin/review-reports

MethodPathPermissionPurpose
GET/Reviews_READReport queue (default status=open)
POST/:id/dismissReviews_UPDATEDismiss

/api/admin/review-reports, never /api/admin/reviews/reports — the latter is matched against /api/admin/reviews/{id} and dies in ParseUUIDPipe.

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Public@Public() + ParseUUIDPipeNoneYesA missing pipe surfaced a 500 on an unauthenticated route; a non-uuid is a clean 400
CustomerJwtAuthGuardreq.user.idNoOwnership-scoped: another customer's review is 404
AdminJwtAuthGuard, RoleGuardreq.userReviews_*NoReviews is already in the permission catalog — permissions:sync only

Rate limits: create/edit CUSTOMER_REVIEW_SUBMIT 5/hour per account; report CUSTOMER_REVIEW_REPORT 10/hour per account; everything else the usual budgets. Five an hour is generous for a real shopper and tight for a form that retries on its own — do not auto-retry a failed submission without a backoff, and do not treat a 429 here as a bug.

6. DTO and Model Reference

6.1 CreateReviewDto

FieldTypeRequiredValidationNotes
productIdUUIDYes@IsUUID("7")
ratingnumberYesint 1–5
titlestringNo≤150
bodystringNo≤4000

Create requires an Idempotency-Key header — a retry replays the original 201 instead of colliding.

6.2 UpdateReviewDto

FieldTypeRequiredNotes
versionnumberYesThe concurrency guard
rating / title / bodyNoAsymmetry with create: an empty string clears the field, omitting it leaves it alone. On create the two mean the same thing; on update they mean opposite things

6.3 Report DTO

reasonspam · offensive · fake · irrelevant · personal_information · other (required); detail optional, ≤500, admin-only.

6.4 Moderation DTOs

approve/restore: { version }. reject/hide: { version, reason }reason required. bulk/approve: { ids: string[] } (max 100). Recalculate: no body.

6.5 Query DTOs

Public list: page, size (max 50), sort (newest/oldest/highest/lowest), rating (1–5), withTextOnly. My list: page, size, optional status. Admin list: page, size, sort (oldestPending/newest/mostReported/recentlyModerated/recentlyEdited), status, productId, customerId, rating, reportedOnly, createdFrom, createdTo. Report queue: page, size, status (default open), reviewId.

7. Enum Reference

EnumValueMeaningRuntime EffectSource
review_statuspending / published / rejected / hidden / deletedLifecycleDefaults publishedenums.ts
report_reasonspam / offensive / fake / irrelevant / personal_information / otherWhy reportedRequired

8. Endpoint Reference

8.1 GET /api/mobile/products/:productId/reviews

Purpose

The product page's review list. Published reviews only.

Response

200{ items: [{ id, rating, title, body, reviewer: { displayName, verifiedPurchase }, publishedAt, editedAt }], count, currentPage, totalPage }. title and body are independently nullable; editedAt is null unless edited — render both dates when not, so an edited review does not pass as an original. verifiedPurchase is derived from the order line FKs.

8.2 GET /api/mobile/products/:productId/reviews/summary

200{ productId, averageRating, reviewCount, distribution: { "1": n, ..., "5": n } }. Zeroes, never nulls, for an unreviewed product; reviewCount tells you whether averageRating: 0 means "bad" or "nobody has said". The three numbers are guaranteed to agree — computed by the database from one set of counters.

8.3 GET /api/mobile/reviews/eligibility?productId=

200{ eligible, orderId, existingReview: { id, status, rating, version }, canCreate }. Key off canCreate, not eligible — it already accounts for the delivered purchase and any existing review. existingReview.version is what the edit call must send back.

8.4 GET /api/mobile/reviews(/:id)

Purpose

The customer's own reviews — the only surface where non-published states are visible. Without a status filter the list returns every state including withdrawn.

Response

200{ id, product: { id, name, thumbnail }, rating, title, body, status, moderationReason, version, publishedAt, editedAt, createdAt }. moderationReason is populated on rejected and hidden — show it; it is the only explanation the customer gets.

8.5 POST /api/mobile/reviews

Purpose

Create a review. rating required; title/body optional. Idempotency-Key required. Returns 201 with the MyReview shape.

Error Cases

HTTPCodeCondition
400validationBad rating/uuid
403REVIEW_NOT_ELIGIBLENo delivered purchase of this product
404REVIEW_PRODUCT_NOT_FOUNDProduct missing or withdrawn
409REVIEW_ALREADY_EXISTSA live review exists — body carries existingReviewId
4295/hour budget

8.6 PATCH /api/mobile/reviews/:id

Purpose

Edit. version required. The review returns to pending and leaves the product's rating until a moderator approves — the single most surprising behaviour; tell the customer before they submit.

Error Cases

HTTPCodeCondition
404REVIEW_NOT_FOUNDNo such review, or not this customer's
409REVIEW_STALE_VERSIONSomebody changed it first
409REVIEW_NOT_EDITABLEhidden or deleted — offer withdraw instead

8.7 DELETE /api/mobile/reviews/:id?version={n}

Purpose

Withdraw. The version is a query parameter, not a body — DELETE bodies are dropped by enough proxies that relying on one would silently disable the guard. Returns 200. The customer may write a new review afterwards — withdrawing frees the slot.

8.8 POST /api/mobile/reviews/:id/report

Purpose

Report somebody else's published review. recorded: false with a 200 is a success — already reported. Reporting never hides anything; it raises a moderator's priority and nothing more.

Error Cases

HTTPCodeCondition
409REVIEW_REPORT_SELFReporting your own review
404REVIEW_NOT_FOUNDUnknown review

8.9 GET /api/admin/reviews

Two useful queues: ?status=pending&sort=oldestPending (edits awaiting approval) and ?reportedOnly=true&sort=mostReported (abuse). Row carries product/customer/order refs, reportCount, version, moderation fields.

8.10 Moderation — /{id}/approve|reject|hide|restore

RouteBody
approve{ version }
reject{ version, reason }reason required
hide{ version, reason }reason required; also resolves every open report on the review
restore{ version }

All return 200. Always send the version you fetched — it is what stops two moderators silently overwriting each other (409 REVIEW_STALE_VERSION). An approved review can be hidden; a rejected one stays rejected.

8.11 POST /api/admin/reviews/bulk/approve

{ ids }{ approved, skipped }. Max 100 distinct ids (409 REVIEW_BULK_LIMIT_EXCEEDED).

8.12 POST /api/admin/reviews/summary/:productId/recalculate

Queues a recount of one product's aggregate from its reviews. Returns 202. A repair path, not the write path — the route includes soft-deleted products deliberately (a withdrawn product's aggregate can still be wrong, and refusing to repair it because the product is off sale leaves bad data where nobody looks).

Error Cases

HTTPCodeCondition
404REVIEW_SUMMARY_NOT_FOUNDThe product does not exist. Not a product with no summary row — that is exactly the drift this endpoint repairs

8.13 GET /api/admin/review-reports

Report queue (default status=open). Rows carry the review's own text so a moderator decides without a second call; the reporting customer is deliberately not identified and never will be.

8.14 POST /api/admin/review-reports/:id/dismiss

The no-action outcome; optional { note }. 404 REVIEW_REPORT_NOT_FOUND; 409 REVIEW_REPORT_ALREADY_RESOLVED when another moderator handled it.

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence (create)

9.3 Error Branch (edit)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /api/mobile/products/:productId/reviewsoffset page/size2050newest/oldest/highest/lowestrating, withTextOnly
GET /api/mobile/reviewsoffset page/size20100standardstatus
GET /api/admin/reviewsoffset page/size201005 admin sortsstatus, product, customer, rating, reportedOnly, dates
GET /api/admin/review-reportsoffset page/size20100standardstatus (open), reviewId

11. Caching, Jobs, and External Integrations

IntegrationUsed?Details
Redis cacheYesproduct_review domain — public reviews + summary + the product detail rating block; invalidated on every write
BullMQYesREVIEW queue — recalculate + moderation emails via outbox (dedupe key carries the review version); hourly drift sweep logs repairs
EmailYesModeration notices via outbox inside their transactions
External APINo

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
GET /api/mobile/products/:productId/reviewslistquery DTOproduct review servicePublic+UUID pipeproduct_reviewreviewsYes
GET .../reviews/summarysummaryparamssummary servicePublic+UUID pipeproduct_reviewsummary404Yes
GET /api/mobile/reviews/eligibilityeligibilityqueryeligibility serviceJWTorders, reviewsYes
GET /api/mobile/reviewsfindAllquerymy-review serviceJWTreviewsYes
GET /api/mobile/reviews/:idfindByIdparamsmy-review serviceJWTreview404Yes
POST /api/mobile/reviewscreateCreateReviewDtocreate serviceJWT+Idempotencyinvalidatereview + summary400/403/404/409Yes
PATCH /api/mobile/reviews/:ideditUpdateReviewDtoedit serviceJWTinvalidateemailreview + summary404/409Yes
DELETE /api/mobile/reviews/:idwithdrawquery versionwithdraw serviceJWTinvalidatereview + summary404/409Yes
POST /api/mobile/reviews/:id/reportreportreport DTOreport serviceJWTreport + counter404/409Yes
GET /api/admin/reviewsfindAllquerymoderation listJWT+RoleReviews_READreviewsYes
GET /api/admin/reviews/:idfindByIdparamsmoderationJWT+RoleReviews_READreview404Yes
GET /api/admin/reviews/:id/eventseventsparamsevent serviceJWT+RoleReviews_READevents404Yes
POST /:id/approveapproveversion DTOmoderationJWT+RoleReviews_UPDATEinvalidateemailreview + summary404/409Yes
POST /:id/rejectrejectversion+reasonmoderationJWT+RoleReviews_UPDATEinvalidateemailreview404/409Yes
POST /:id/hidehideversion+reasonmoderationJWT+RoleReviews_UPDATEinvalidateemailreview + reports404/409Yes
POST /:id/restorerestoreversion DTOmoderationJWT+RoleReviews_RESTOREinvalidateemailreview + summary404/409Yes
POST /bulk/approvebulkApproveids DTOmoderationJWT+RoleReviews_UPDATEinvalidateemailreviews409Yes
POST /summary/:productId/recalculaterecalculateparamsrecalc serviceJWT+RoleReviews_UPDATEinvalidatequeuesummary202 / 404 REVIEW_SUMMARY_NOT_FOUNDYes
GET /api/admin/review-reportsfindAllqueryreport serviceJWT+RoleReviews_READreportsYes
POST /api/admin/review-reports/:id/dismissdismissnote DTOreport serviceJWT+RoleReviews_UPDATEreport404/409Yes

13.2 Request/Response Exhaustiveness

Covered in §8: minimal/full create payloads (§6.1/8.5), the update empty-string-vs-omit asymmetry (§6.2), the recorded:false success (§8.8), the pending-on-edit surprise (§8.6), zeroes-not-nulls summary (§8.2), domain errors per endpoint (§8 error tables), rate-limit behavior (5/hr, 10/hr), 202 recalculate.

13.3 API Diagram Pack

Route ownership (§9.1), sequence per endpoint family (§9.2, backend §7), error decision tree (§9.3), async flow (backend §7.2 — outbox email), cache flow (backend §8).

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Web frontendEdit → pending surprise; canCreate is the key; recorded:false is success409 STALE_VERSION → re-fetch, show current, ask againStable
Mobile appIdempotency-Key on create; 5/hr + 10/hr budgets; version as query param on DELETE429 → back off, never auto-retry without backoffStable
Admin panelTwo queues; version on every action; hide resolves reports; reporter never identified409s → refreshStable
QAAggregate invariants (428C9/23514), one-per-pair, pending semanticsReproduce via exact codesStable
Products integrationrating block on detail only; listings use the summary endpointStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
One review per pairEdit, not second voteMultiple reviewsOne honest opinionPartial unique
Auto-publishInstant feedbackPre-moderationTrust new contentBad content briefly liveReports
Edit → pendingHonest editsIn-placeNo bait-and-switchSurpriseDocumented
Aggregate GENERATEDConsistent numbersApp-computed428C9 structural proof
Version everywhereNo silent overwriteLast-write-winsTwo moderators409 churnRe-fetch
Detail-only ratingNo join per cardListing ratingsSecond callSummary endpoint
Report never hidesFree speechAuto-hideCounter orders queueAbuse lingersPriority queue

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
Product detail rating blockStorefrontProjectionNoneNoAdditive optional field
Future verified-purchase rule changeNoneEligibility serviceNoneNoStructural today

14. Zero-Omission API Checklist

  • Every controller route is documented (§4, §8, §13.1).
  • Every parent route prefix and runtime URL is documented (§2, §4).
  • Every DTO field, enum, default, transform and validator is documented (§6, §7).
  • Every response field and nullable field is documented (§8).
  • Every auth, guard, permission and guest identity branch is documented (§5).
  • Every success, validation, not-found, conflict, rate-limit and server-error branch is documented (§8).
  • Every DB read/write, cache hit/miss/invalidation, queue job and external call is documented (§11, backend §8/§9).
  • Every route has examples for minimal request, success response and representative failures (§8).
  • Every endpoint family has route, sequence and error diagrams (§9, backend §7).
  • Every tradeoff and compatibility risk is documented (§13.5, §13.6).
  • The API doc links to backend and features/flows (§1, See Also).

15. Integration Checklist

  • Every route from controllers is documented.
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every response envelope is documented.
  • Every error code is documented.
  • Every auth guard and permission is documented.
  • Every cache key, queue job and external call is documented.
  • Every diagram matches the current code.
  • The API doc links to backend and features/flows.

See Also

On this page

Reviews - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map4.1 Public — /api/mobile/products/{productId}/reviews4.2 Customer — /api/mobile/reviews4.3 Admin — /api/admin/reviews4.4 Admin — /api/admin/review-reports5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 CreateReviewDto6.2 UpdateReviewDto6.3 Report DTO6.4 Moderation DTOs6.5 Query DTOs7. Enum Reference8. Endpoint Reference8.1 GET /api/mobile/products/:productId/reviewsPurposeResponse8.2 GET /api/mobile/products/:productId/reviews/summary8.3 GET /api/mobile/reviews/eligibility?productId=8.4 GET /api/mobile/reviews(/:id)PurposeResponse8.5 POST /api/mobile/reviewsPurposeError Cases8.6 PATCH /api/mobile/reviews/:idPurposeError Cases8.7 DELETE /api/mobile/reviews/:id?version={n}Purpose8.8 POST /api/mobile/reviews/:id/reportPurposeError Cases8.9 GET /api/admin/reviews8.10 Moderation — /{id}/approve|reject|hide|restore8.11 POST /api/admin/reviews/bulk/approve8.12 POST /api/admin/reviews/summary/:productId/recalculateError Cases8.13 GET /api/admin/review-reports8.14 POST /api/admin/review-reports/:id/dismiss9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence (create)9.3 Error Branch (edit)10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.1 Route-by-Route Completeness Matrix13.2 Request/Response Exhaustiveness13.3 API Diagram Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also