Permission Requests API
Last updated: September 13, 2026
A review on your product page is one thing. Putting a customer's words or photo in a paid ad, a social post, or a marketing email needs that customer's permission. These endpoints ask a specific reviewer for that permission by email and track the answer. When the reviewer says yes, BetterReviews records a dated permission release and the review's rights_status becomes cleared. The merchant-facing walkthrough is in the Requesting permission to feature reviews guide.
The email is a fixed, non-promotional transactional message (not merchant-editable). A reviewer who has unsubscribed, bounced, or whose Shopify marketing state is an explicit no is never emailed — the request is refused at creation and re-checked at send time.
Plan availability: requires the rights_management entitlement. Current plans (Unlimited, Studio) include it. On legacy plans (no longer sold), it is available on Growth and above, and on legacy Pro and Enterprise; stores on the legacy Starter plan do not have it. A store with no active subscription — never subscribed, or cancelled — is in the free state and does not have it either. Without it every endpoint below returns 403 entitlement_required.
Preview a Request
GET /api/v1/reviews/:id/permission-preview
Checks whether the reviewer can be reached, without sending anything. Use it before request-permission to show who will be asked. It checks reachability only — the consent and suppression gates run on request-permission, so a reachable reviewer can still be refused there. Rate limit: 120 per minute per store.
Response (200)
{"reachable": true, "masked_email": "j••@example.com"} masked_email is always masked — the full address never leaves the server. (An address that cannot be masked comes back as "".) An unreachable review (no email on the imported row and no originating review request) returns {"reachable": false, "masked_email": null}.
Errors
404 review_not_found— Review doesn't exist or belongs to another store
Request Permission
POST /api/v1/reviews/:id/request-permission
Resolves the reviewer's email, applies the consent gate, creates the permission request, marks the review rights_status: "requested", and queues the email. Requires an API key with the write scope.
Request
{"scope": "both"} | Field | Type | Required | Description |
|---|---|---|---|
scope | string | No | What you want to feature: text, photo, or both (default both) |
Response (201)
{"status": "sent", "permission_request_id": 91} Errors
400 invalid_scope—scopeis nottext,photo, orboth404 review_not_found— Review doesn't exist or belongs to another store422 already_requested— A request already exists for this review. A reviewer is asked at most once per review; a decline is final422 recipient_recently_asked— The same reviewer was asked about another of your reviews in the last 24 hours403 bot_key_not_allowed— Bot-scoped keys cannot use these endpoints422 no_reachable_email— The review carries no usable email422 consent_blocked— The reviewer's Shopify marketing state isUNSUBSCRIBED,REDACTED, orINVALID(never overridable)422 suppressed— The reviewer is on your store's suppression list (unsubscribed, bounced, or complained)403— The API key lacks thewritescope (body usesdetail, noterror)429— More than 10 requests per minute or 200 per day for the store (body:{"detail": "Rate limit exceeded"})- Any other
422 {"error": ...}value means the write was rejected; treat it as not sent
List Permission Requests
GET /api/v1/reviews/permission-requests
Your store's permission requests, newest first. No recipient PII is returned.
Query parameters
| Param | Description |
|---|---|
limit | Page size (default 50, max 200) |
offset | Pagination offset (default 0) |
Response (200)
{
"permission_requests": [
{
"id": 91,
"review_id": 4521,
"scope": "both",
"status": "sent",
"recipient_source": "imported",
"requested_at": "2026-09-13T10:12:00Z",
"responded_at": null
}
],
"total": 1,
"limit": 50,
"offset": 0,
"has_more": false
} Status values:
sent— Emailed; waiting on the reviewer.granted— The reviewer said yes. The review'srights_statusiscleared.declined— The reviewer said no. Don't feature the review; it cannot be asked again.no_response— Reserved; not yet emitted. An unanswered request stayssent.
recipient_source is imported when the email came with the review (Okendo, Judge.me, Yotpo, CSV) or direct_request when it came from the BetterReviews review request that produced the review.
Example: preview, then ask
REVIEW_ID=4521
curl -s "https://api.betterreviews.app/api/v1/reviews/$REVIEW_ID/permission-preview" \
-H "X-API-Key: YOUR_API_KEY"
# {"reachable":true,"masked_email":"j••@example.com"}
curl -s -X POST "https://api.betterreviews.app/api/v1/reviews/$REVIEW_ID/request-permission" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scope": "both"}'
# {"status":"sent","permission_request_id":91} The review's rights_status field (on GET /api/v1/reviews/:id) shows none, requested, or cleared; only cleared reviews are licensed for marketing use.