Skip to content

Reviews & Testimonials API โ€‹

Create, list, update, and moderate custom reviews and testimonials. Both groups are handled by RecommendationsController and expose the same route shape, so every endpoint below has a /testimonials twin at the same path.

Base URL: https://your-site.com/wp-json/wpsocialreviews/v2

Policy: ReviewPolicy (/reviews) ยท TestimonialPolicy (/testimonials)

Changed in 4.4.0

The two prefixes are no longer interchangeable. They previously shared one closure and one set of handler methods; each now routes to its own method (index / indexTestimonial, create / createTestimonial, and so on), and the route fixes which rows the request may touch โ€” /testimonials is scoped to platform_name = 'testimonial' and /reviews to everything else.

A client-supplied type no longer widens that scope. Ids outside the route's scope are dropped silently rather than rejected, so a bulk call may report fewer affected rows than ids sent.


List Reviews โ€‹

GET /reviews

Return a filtered, paginated list of reviews.

Handler: RecommendationsController@index

Parameters โ€‹

ParameterTypeLocationRequiredDescription
typestringqueryNoreview or testimonial
source_idstringqueryNoFilter by connected source
searchstringqueryNoSearch text
filterstringqueryNoPlatform filter
order_bystringqueryNoasc or desc
status_filterstringqueryNoall, publish, spam, or unpublish
page / per_pageintegerqueryNoPagination

Response โ€‹

json
{
  "all_valid_platforms": {},
  "items": { "data": [], "total": 0 },
  "total_items": 0
}

Example โ€‹

bash
curl -X GET "https://example.com/wp-json/wpsocialreviews/v2/reviews?status_filter=publish&order_by=desc" \
  -H "X-WP-Nonce: <nonce>" --cookie "wordpress_logged_in_xxx=..."

Create a Review โ€‹

POST /reviews

Create a custom review (validated through ReviewRequest).

Handler: RecommendationsController@create

Parameters โ€‹

ParameterTypeLocationRequiredDescription
reviewobjectbodyYesReview fields โ€” reviewer_name, reviewer_email, rating, reviewer_text, platform_name, โ€ฆ

Response โ€‹

json
{ "message": "...", "review": { "id": 42 } }

Validation failures return 422 with an errors object.


Duplicate Reviews โ€‹

POST /reviews/duplicate

Duplicate one or more user-editable reviews.

Handler: RecommendationsController@duplicate

ParameterTypeLocationRequiredDescription
idsarraybodyYesReview IDs to duplicate

Response: { "message": "...", "review": [ ] }


Update a Review โ€‹

PUT /reviews/{id}

Update a review by ID.

Handler: RecommendationsController@update

ParameterTypeLocationRequiredDescription
idintegerpathYesReview ID
reviewobjectbodyYesUpdated fields

Response: { "message": "...", "review": {} } (or 422 on validation error).


Moderation โ€‹

ActionMethodEndpointHandlerInputs
Approve / unapprovePUT/reviews/status-update@statusUpdateids[], status (enable/disable), type (review/testimonial)
Mark / unmark spamPUT/reviews/spam@spamReviewsids[], action (mark-spam/not-spam)
DeleteDELETE/reviews@deleteids[]

Each returns { "message": "..." }.

Example โ€” approve two reviews โ€‹

bash
curl -X PUT "https://example.com/wp-json/wpsocialreviews/v2/reviews/status-update" \
  -H "X-WP-Nonce: <nonce>" --cookie "wordpress_logged_in_xxx=..." \
  -H "Content-Type: application/json" \
  -d '{"ids":[12,13],"status":"enable","type":"review"}'

Categories 4.4.0 โ€‹

Categories are one shared vocabulary across reviews and testimonials โ€” the same rows back both screens. That shape decides the authorization: reading and assigning stay behind each screen's own policy, while renaming and deleting reach beyond a single screen and therefore demand both capabilities.

There is deliberately no create endpoint. A category is only ever born attached to something โ€” the per-review select, the bulk-assign modal, or a CSV import โ€” so a category with zero reviews is not a state the API can produce. Create by naming one in a bulk assign.


List Categories โ€‹

GET /reviews/categories ยท /testimonials/categories

Return every category, ordered by name. Serves both the assign dropdowns and the management modal.

Handler: CategoriesController@indexPolicy: ReviewPolicy / TestimonialPolicy

Response โ€‹

A caller holding both wpsn_manage_reviews and wpsn_manage_testimonials gets relationship counts; a single-capability caller gets names only, so it cannot infer the other screen's usage totals from the counts.

json
{
  "categories": [
    { "id": 3, "name": "Service", "slug": "service", "reviews_count": 12 }
  ]
}

Example โ€‹

bash
curl -X GET "https://example.com/wp-json/wpsocialreviews/v2/reviews/categories" \
  -H "X-WP-Nonce: <nonce>" --cookie "wordpress_logged_in_xxx=..."

Bulk Assign or Remove a Category โ€‹

PUT /reviews/bulk-category ยท /testimonials/bulk-category

Attach or detach one category across many rows. This is also the only way to create a category โ€” pass new_category_name with action: "add".

Handler: RecommendationsController@bulkCategory (/reviews) ยท @bulkTestimonialCategory (/testimonials) Policy: ReviewPolicy / TestimonialPolicy

Parameters โ€‹

ParameterTypeLocationRequiredDescription
idsarraybodyYesRow IDs. Deduplicated and cast to int; ids outside the route's scope are dropped.
actionstringbodyNoadd (default) or remove.
category_idintegerbodyNoAn existing category. Validated to exist before anything is written.
new_category_namestringbodyNoCreate-and-assign. Only valid with action: "add".

Either category_id or new_category_name must resolve to a category, or the call is a 422. A new category is resolved only once there are matching rows to apply it to, so a request that matches nothing cannot leave an empty category behind.

Response โ€‹

json
{ "message": "Category assigned to 12 reviews" }

The count in the message is the number of in-scope rows, which may be fewer than the ids you sent.

422 responses: no ids, an action other than add/remove, new_category_name sent with remove, no in-scope rows matched, a category_id that no longer exists, or neither a category id nor a name.

Example โ€‹

bash
curl -X PUT "https://example.com/wp-json/wpsocialreviews/v2/reviews/bulk-category" \
  -H "X-WP-Nonce: <nonce>" --cookie "wordpress_logged_in_xxx=..." \
  -H "Content-Type: application/json" \
  -d '{"ids":[12,13],"action":"add","new_category_name":"Service"}'

Rename a Category โ€‹

PUT /reviews/categories/{id} ยท /testimonials/categories/{id}

Handler: CategoriesController@updatePolicy: CategoryManagementPolicy โ€” requires both wpsn_manage_reviews andwpsn_manage_testimonials.

ParameterTypeLocationRequiredDescription
idintegerpathYesCategory ID.
namestringbodyYesThe new name. The slug is re-derived from it.

Response: { "message": "...", "category": { "id": 3, "name": "โ€ฆ", "slug": "โ€ฆ" } }

422 when the name is empty, contains no letter or number (an empty slug), or collides with another category's slug. 404 when the id does not exist.


Delete a Category โ€‹

DELETE /reviews/categories/{id} ยท /testimonials/categories/{id}

Deletes the category and detaches it from every related row on both screens.

Handler: CategoriesController@deletePolicy: CategoryManagementPolicy

Response: { "message": "..." }

WARNING

Both prefixes reach the same global rows โ€” /testimonials/categories/3 renames or deletes the category for reviews too. The dual mounting exists only so each screen's manage-categories modal keeps its own URL; it is not a scope boundary.

WP Social Ninja Developer Documentation