Appearance
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/reviewsReturn a filtered, paginated list of reviews.
Handler: RecommendationsController@index
Parameters โ
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
type | string | query | No | review or testimonial |
source_id | string | query | No | Filter by connected source |
search | string | query | No | Search text |
filter | string | query | No | Platform filter |
order_by | string | query | No | asc or desc |
status_filter | string | query | No | all, publish, spam, or unpublish |
page / per_page | integer | query | No | Pagination |
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/reviewsCreate a custom review (validated through ReviewRequest).
Handler: RecommendationsController@create
Parameters โ
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
review | object | body | Yes | Review 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/duplicateDuplicate one or more user-editable reviews.
Handler: RecommendationsController@duplicate
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
ids | array | body | Yes | Review IDs to duplicate |
Response: { "message": "...", "review": [ ] }
Update a Review โ
PUT/reviews/{id}Update a review by ID.
Handler: RecommendationsController@update
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
id | integer | path | Yes | Review ID |
review | object | body | Yes | Updated fields |
Response: { "message": "...", "review": {} } (or 422 on validation error).
Moderation โ
| Action | Method | Endpoint | Handler | Inputs |
|---|---|---|---|---|
| Approve / unapprove | PUT | /reviews/status-update | @statusUpdate | ids[], status (enable/disable), type (review/testimonial) |
| Mark / unmark spam | PUT | /reviews/spam | @spamReviews | ids[], action (mark-spam/not-spam) |
| Delete | DELETE | /reviews | @delete | ids[] |
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/categoriesReturn 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-categoryAttach 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 โ
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
ids | array | body | Yes | Row IDs. Deduplicated and cast to int; ids outside the route's scope are dropped. |
action | string | body | No | add (default) or remove. |
category_id | integer | body | No | An existing category. Validated to exist before anything is written. |
new_category_name | string | body | No | Create-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.
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
id | integer | path | Yes | Category ID. |
name | string | body | Yes | The 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.

