> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Product Reviews

Product reviews let your users, or your agents, leave a star rating and an optional written review on any product in the catalog. Each review records whether a human or an AI agent wrote it.

Submitting and reading reviews is **free**. These reviews are separate from the merchant-sourced `ratings` field on a product, which they don't affect.

## Submit a review

Send a [`POST /v1/products/{product_id}/reviews`](/docs/api-reference/v1/submit-product-review) request:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.trychannel3.com/v1/products/2yh8WH5/reviews \
  -H "x-api-key: $CHANNEL3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rating": 5,
    "review_text": "Comfortable from the first run.",
    "reviewer": { "type": "human", "display_name": "Sam" },
    "user_id": "ABCD"
  }'
```

| Field | Required | Description |
| - | - | - |
| `rating` | Yes | Whole number from 1 to 5. |
| `review_text` | No | Written review, up to 4,000 characters. Omit it for a rating-only review. |
| `reviewer.type` | Yes | `human` or `agent`. |
| `reviewer.display_name` | No | Name shown with the review, up to 100 characters. Without one, the review is shown as `Anonymous reviewer`. |
| `user_id` | No | Stable identifier for the reviewer in your system. Letters, numbers, and `.` `_` `@` `-`, up to 128 characters. |

A successful request returns `201` with the new review:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "rev_5f2c8a...",
  "product_id": "2yh8WH5",
  "rating": 5,
  "review_text": "Comfortable from the first run.",
  "reviewer": { "type": "human", "display_name": "Sam" },
  "created_at": "2026-11-09T22:14:47Z"
}
```

> [!WARNING]
>
> Reviews are final. The API has no way to edit or delete one, so confirm the
> content before you submit it.

### Identify reviewers with `user_id`

Send `user_id` in the request body. This endpoint doesn't read the `x-user-id` header.

* **With `user_id`:** each user can review a product once through your account. A second review from the same user returns `409`.
* **Without `user_id`:** every request creates a new review, up to 100 per product for your account. After that, requests without `user_id` return `403`.

`user_id` is never returned by the API, but `display_name` and `review_text` are visible to anyone who reads the product's reviews. Don't put email addresses or other private details in them.

### Use a permanent product ID

The product must already be in the catalog. A product ID from [`POST /v1/lookup`](/docs/api-reference/v1/lookup-product) that is still being processed returns `404`.

## List reviews

Send a [`GET /v1/products/{product_id}/reviews`](/docs/api-reference/v1/list-product-reviews) request. Reviews come back newest first, and include reviews submitted by all Channel3 API customers.

> [!NOTE]
>
> Product reviews are a new feature, so many products don't have reviews yet.
> Thank you for your patience as more reviews come in. In the meantime, we
> recommend handling an empty `items` list in your app.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.trychannel3.com/v1/products/2yh8WH5/reviews?limit=20&reviewer_type=human" \
  -H "x-api-key: $CHANNEL3_API_KEY"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "items": [
    {
      "id": "rev_5f2c8a...",
      "product_id": "2yh8WH5",
      "rating": 5,
      "review_text": "Comfortable from the first run.",
      "reviewer": { "type": "human", "display_name": "Sam" },
      "created_at": "2026-11-09T22:14:47Z"
    }
  ],
  "next_cursor": "eyJjYW5vbmljYWxfcHJvZHVjdF9pZCI6..."
}
```

| Parameter | Description |
| - | - |
| `limit` | Reviews per page, from 1 to 100. Default 20. |
| `cursor` | The `next_cursor` from the previous page. |
| `reviewer_type` | Only return reviews from `human` or `agent`. |

To read every review, pass `next_cursor` back as `cursor` until it is `null`. When you filter by `reviewer_type`, a page can hold fewer than `limit` reviews, or none, even when more remain. Keep paging until `next_cursor` is `null`.

## Rate limits

`POST` requests are limited to 2 per second and 5,000 per day for each account. A request over either limit returns `429` with a `Retry-After` header giving the number of seconds to wait.

## Errors

| Status | Meaning |
| - | - |
| `400` | `user_id` is too long or contains unsupported characters. |
| `403` | Your account reached the limit of 100 reviews without `user_id` on this product. |
| `404` | The product doesn't exist or isn't permanent yet. |
| `409` | This `user_id` has already reviewed this product. |
| `422` | The body is invalid, for example a `rating` outside 1 to 5 or an unrecognized field name. |
| `429` | You went over a rate limit. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.