Submit a product review
Submits a review on behalf of a logged-in customer. Requires both an API key and a customer session token.
Reviews are created with status: "pending" and must be approved by the
store owner before they appear in public listing results.
Authentication: API key in Authorization: Bearer header and a
customer JWT in the x-auth-token header (obtained from
POST /v1/auth/login).
Duplicate prevention: A customer may only submit one non-rejected review
per product. Attempting a second submission returns 409 Conflict.
Verified purchase: If you provide an order_id that belongs to the
authenticated customer’s order history at this store, the review is marked
verified_purchase: true.
Rate limit: 10 submissions/hour per IP address.
curl --request POST \
--url https://api.tybritelabs.com/v1/reviews \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"product_id": "7d425c0a-50e7-4e39-af79-6f7945f9dfa8",
"rating": 5,
"variant_id": "9a47e047-b1b6-4c35-9617-820629e22e04",
"order_id": "770a0622-0401-63f6-c938-557766551111",
"title": "Absolutely love it",
"body": "Best purchase I've made this year. Quality is outstanding.",
"media_urls": [
"https://cdn.example.com/review-photo-1.jpg"
]
}
EOFimport requests
url = "https://api.tybritelabs.com/v1/reviews"
payload = {
"product_id": "7d425c0a-50e7-4e39-af79-6f7945f9dfa8",
"rating": 5,
"variant_id": "9a47e047-b1b6-4c35-9617-820629e22e04",
"order_id": "770a0622-0401-63f6-c938-557766551111",
"title": "Absolutely love it",
"body": "Best purchase I've made this year. Quality is outstanding.",
"media_urls": ["https://cdn.example.com/review-photo-1.jpg"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
product_id: '7d425c0a-50e7-4e39-af79-6f7945f9dfa8',
rating: 5,
variant_id: '9a47e047-b1b6-4c35-9617-820629e22e04',
order_id: '770a0622-0401-63f6-c938-557766551111',
title: 'Absolutely love it',
body: JSON.stringify('Best purchase I\'ve made this year. Quality is outstanding.'),
media_urls: ['https://cdn.example.com/review-photo-1.jpg']
})
};
fetch('https://api.tybritelabs.com/v1/reviews', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.tybritelabs.com/v1/reviews",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'product_id' => '7d425c0a-50e7-4e39-af79-6f7945f9dfa8',
'rating' => 5,
'variant_id' => '9a47e047-b1b6-4c35-9617-820629e22e04',
'order_id' => '770a0622-0401-63f6-c938-557766551111',
'title' => 'Absolutely love it',
'body' => 'Best purchase I\'ve made this year. Quality is outstanding.',
'media_urls' => [
'https://cdn.example.com/review-photo-1.jpg'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tybritelabs.com/v1/reviews"
payload := strings.NewReader("{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tybritelabs.com/v1/reviews")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/reviews")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"review": {
"id": "914a8c3e-83fa-4a97-9403-03b1eb5d11f5",
"product_id": "0186665f-f0a1-4c79-976a-919b5b1c2daa",
"variant_id": null,
"order_id": null,
"customer_id": "3380d312-0813-4f2a-bc9b-feb60837a9d3",
"customer_name": "Jane Doe",
"rating": 5,
"title": "Outstanding quality",
"body": "Exactly as described — fast shipping and great build quality. Would buy again.",
"media_urls": [],
"status": "pending",
"rejection_reason": null,
"helpful_count": 0,
"verified_purchase": false,
"created_at": "2026-06-20T13:49:21.693561+00:00",
"updated_at": "2026-06-20T13:49:21.693561+00:00"
}
}{
"error": {
"code": "invalid_request",
"message": "Invalid request parameters",
"details": "The 'email' field is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid Authorization header"
}
}{
"error": {
"code": "forbidden",
"message": "This operation requires a secret key. Publishable keys cover catalogue reads and the actions a shopper takes for themselves."
}
}{
"error": {
"code": "not_found",
"message": "Resource not found"
}
}{
"error": {
"code": "conflict",
"message": "A customer with this email already exists for this store"
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Please slow down and try again shortly.",
"remaining": 0,
"reset": 1706198400
}
}{
"error": {
"code": "server_error",
"message": "An unexpected error occurred"
}
}Authorizations
API Key Authentication
Use your API key in the Authorization header:
Authorization: Bearer tybrite_sk_live_YOUR_KEY
Key Types:
Secret Keys (Server-Side Only):
- Format:
tybrite_sk_live_*(production) ortybrite_sk_test_*(sandbox) - Full read/write access to all endpoints
- ⚠️ NEVER expose in client-side code or public repositories
- Required for: every write a shopper does not make for themselves, authentication, payment verification, AI recommendations
Publishable Keys (Client-Safe):
- Format:
tybrite_pk_live_*(production) ortybrite_pk_test_*(sandbox) - Catalogue reads, plus the actions a shopper takes for themselves
- ✅ Safe for client-side JavaScript, mobile apps, and public code
- Allowed for: browsing products, search, CMS content, pricing queries, cart and wishlist, reviews, storefront events, and the Agent API's quotes, cart drafts and checkout intents
Endpoint-Specific Requirements:
- Authentication endpoints (
/v1/auth/*): Secret key required - Payment verification (
POST /v1/payments/verify): Secret key required - AI Recommendations (
POST /v1/recommendations): Secret key required - Semantic Search (
POST /v1/search): Both key types allowed (read-only operation) - Shopper actions (cart, wishlist, reviews, storefront events, Agent API quotes, cart drafts and checkout intents): Both key types allowed
- All other write operations: Secret key required
- All read operations: Both key types allowed
Using a publishable key for restricted operations returns 403 Forbidden.
Headers
Customer session JWT from POST /v1/auth/login or POST /v1/auth/verify-otp. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Bring-your-own-auth assertion for stores that manage authentication in an external identity provider (Auth0, Clerk, Cognito, Firebase, NextAuth, SSO). Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Format: <base64url(JSON)>.<base64url(HMAC-SHA256(JSON))> where the JSON is { "external_id": "...", "iat": <unix>, "exp": <unix> } and the HMAC is keyed on the store's signing secret. Claim lifetime capped at 300 seconds.
A raw token from the store's own identity provider (e.g. a Firebase ID token). Galactic Core forwards it to the store's configured Auth verifier, which validates it and returns the identity.
Verification is fail-closed: if the verifier rejects the token or is unreachable, the request is unauthenticated (401). Requires an Auth verifier to be configured for the store. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Body
Product being reviewed.
"7d425c0a-50e7-4e39-af79-6f7945f9dfa8"
Star rating from 1 (lowest) to 5 (highest).
1 <= x <= 55
Optional — specific variant being reviewed.
"9a47e047-b1b6-4c35-9617-820629e22e04"
Optional — the order this review relates to. Providing a valid order
that belongs to the authenticated customer marks the review as
verified_purchase: true.
"770a0622-0401-63f6-c938-557766551111"
Optional short headline for the review.
"Absolutely love it"
Optional full review text.
"Best purchase I've made this year. Quality is outstanding."
Optional array of image or video URLs attached to the review.
[ "https://cdn.example.com/review-photo-1.jpg" ]
Response
Review submitted and pending moderation
Show child attributes
Show child attributes
curl --request POST \
--url https://api.tybritelabs.com/v1/reviews \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"product_id": "7d425c0a-50e7-4e39-af79-6f7945f9dfa8",
"rating": 5,
"variant_id": "9a47e047-b1b6-4c35-9617-820629e22e04",
"order_id": "770a0622-0401-63f6-c938-557766551111",
"title": "Absolutely love it",
"body": "Best purchase I've made this year. Quality is outstanding.",
"media_urls": [
"https://cdn.example.com/review-photo-1.jpg"
]
}
EOFimport requests
url = "https://api.tybritelabs.com/v1/reviews"
payload = {
"product_id": "7d425c0a-50e7-4e39-af79-6f7945f9dfa8",
"rating": 5,
"variant_id": "9a47e047-b1b6-4c35-9617-820629e22e04",
"order_id": "770a0622-0401-63f6-c938-557766551111",
"title": "Absolutely love it",
"body": "Best purchase I've made this year. Quality is outstanding.",
"media_urls": ["https://cdn.example.com/review-photo-1.jpg"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
product_id: '7d425c0a-50e7-4e39-af79-6f7945f9dfa8',
rating: 5,
variant_id: '9a47e047-b1b6-4c35-9617-820629e22e04',
order_id: '770a0622-0401-63f6-c938-557766551111',
title: 'Absolutely love it',
body: JSON.stringify('Best purchase I\'ve made this year. Quality is outstanding.'),
media_urls: ['https://cdn.example.com/review-photo-1.jpg']
})
};
fetch('https://api.tybritelabs.com/v1/reviews', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.tybritelabs.com/v1/reviews",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'product_id' => '7d425c0a-50e7-4e39-af79-6f7945f9dfa8',
'rating' => 5,
'variant_id' => '9a47e047-b1b6-4c35-9617-820629e22e04',
'order_id' => '770a0622-0401-63f6-c938-557766551111',
'title' => 'Absolutely love it',
'body' => 'Best purchase I\'ve made this year. Quality is outstanding.',
'media_urls' => [
'https://cdn.example.com/review-photo-1.jpg'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tybritelabs.com/v1/reviews"
payload := strings.NewReader("{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tybritelabs.com/v1/reviews")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/reviews")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"product_id\": \"7d425c0a-50e7-4e39-af79-6f7945f9dfa8\",\n \"rating\": 5,\n \"variant_id\": \"9a47e047-b1b6-4c35-9617-820629e22e04\",\n \"order_id\": \"770a0622-0401-63f6-c938-557766551111\",\n \"title\": \"Absolutely love it\",\n \"body\": \"Best purchase I've made this year. Quality is outstanding.\",\n \"media_urls\": [\n \"https://cdn.example.com/review-photo-1.jpg\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"review": {
"id": "914a8c3e-83fa-4a97-9403-03b1eb5d11f5",
"product_id": "0186665f-f0a1-4c79-976a-919b5b1c2daa",
"variant_id": null,
"order_id": null,
"customer_id": "3380d312-0813-4f2a-bc9b-feb60837a9d3",
"customer_name": "Jane Doe",
"rating": 5,
"title": "Outstanding quality",
"body": "Exactly as described — fast shipping and great build quality. Would buy again.",
"media_urls": [],
"status": "pending",
"rejection_reason": null,
"helpful_count": 0,
"verified_purchase": false,
"created_at": "2026-06-20T13:49:21.693561+00:00",
"updated_at": "2026-06-20T13:49:21.693561+00:00"
}
}{
"error": {
"code": "invalid_request",
"message": "Invalid request parameters",
"details": "The 'email' field is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid Authorization header"
}
}{
"error": {
"code": "forbidden",
"message": "This operation requires a secret key. Publishable keys cover catalogue reads and the actions a shopper takes for themselves."
}
}{
"error": {
"code": "not_found",
"message": "Resource not found"
}
}{
"error": {
"code": "conflict",
"message": "A customer with this email already exists for this store"
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Please slow down and try again shortly.",
"remaining": 0,
"reset": 1706198400
}
}{
"error": {
"code": "server_error",
"message": "An unexpected error occurred"
}
}
