API reference
Base: https://reviews.leadit.tech/api/v1. Every request is signed. The Permission column is the key’s minimum scope.
Summary
Section titled “Summary”| Method | Path | Permission | What for |
|---|---|---|---|
POST |
/review-requests |
moderate |
Get an order’s link |
GET |
/review-requests |
read |
List requests and what changed |
POST |
/review-requests/{id}/dispatched |
moderate |
Confirm you sent it |
POST |
/review-requests/{id}/send-now |
moderate |
Send now |
POST |
/review-requests/{id}/cancel |
moderate |
Cancel |
POST |
/reviews |
moderate |
Create a review |
GET |
/reviews |
read |
List and filter reviews |
GET |
/reviews/{id} |
read |
One review |
PATCH |
/reviews/{id} |
moderate |
Moderate and reply |
DELETE |
/reviews/{id} |
full |
Delete |
POST |
/reviews/import |
full |
Import up to 1,000 reviews |
POST |
/uploads |
moderate |
Upload a photo or a video |
POST |
/uploads/{id}/complete |
moderate |
Report that it’s uploaded |
GET |
/stats |
read |
The Home metrics |
GET |
/settings |
read |
Settings, flow and moderation |
PATCH |
/settings |
full |
Change them |
GET |
/locations |
read |
Active locations |
GET |
/webhook-deliveries |
read |
Webhook deliveries |
POST |
/webhook-deliveries/{id}/retry |
full |
Retry a delivery |
Lists and pagination
Section titled “Lists and pagination”Lists (GET /review-requests, GET /reviews, GET /webhook-deliveries) share three parameters and are sorted by the last time they changed:
| Parameter | What it does |
|---|---|
since |
ISO 8601 date: returns what changed since then. Ideal for syncing without missing anything. |
limit |
From 1 to 100. Default, 50. |
cursor |
The next_cursor from the previous page. |
{ "data": [ … ], "next_cursor": "MjAyNi0xMC0xNlQxNjowMDowMC4wMDBafGNtMms4eDFmMzAwMDE" }When next_cursor is null, there’s nothing more.
Requests
Section titled “Requests”Get an order’s link
Section titled “Get an order’s link”POST /review-requests is the heart of the API: you give it an order and it returns its link.
{ "order": { "shopify_order_id": "gid://shopify/Order/5812345678901" }, "channel": "api", "locale": "en", "expires_in_days": 45}| Field | What it is | |
|---|---|---|
order.shopify_order_id |
required | The order’s gid or number. |
channel |
optional | api (default), leadit_whatsapp or email. |
locale |
optional | es or en: the language of the form. It also changes the language of a request that already existed. |
expires_in_days |
optional | From 1 to 365. Only counts when creating it. |
Response: 201 if we created it, 200 if the order already had a request (the same one, with the same link). The body is the request, the same as the data of request events.
- Getting the link doesn’t change the schedule. If you never confirm with
/dispatched, the request goes out on its date through the store’s channel. - If the order had no request, we create it without going through the app’s filters: asking for it is your decision.
- The order has to exist in Shopify and not be cancelled. If we can’t find it:
404 order_not_found.
Confirm you sent it
Section titled “Confirm you sent it”POST /review-requests/{id}/dispatched, no body. The request leaves the schedule and the reminders start running. Repeating it changes nothing.
Send now
Section titled “Send now”POST /review-requests/{id}/send-now, no body. The same as Send now in Requests: the date moves to this instant. It only works with a scheduled or failed request; otherwise, 409 invalid_state.
Cancel
Section titled “Cancel”POST /review-requests/{id}/cancel, no body. A request that was already answered or expired returns 409 request_closed.
GET /review-requests?status=due,dispatched&since=2026-10-01T00:00:00Z
status accepts several, separated by commas: scheduled, due, paused, dispatched, opened, partial, responded, expired, cancelled, failed. Each request also includes updated_at.
Reviews
Section titled “Reviews”Create a review
Section titled “Create a review”POST /reviews, for example with a review your customer left on another channel.
{ "order": { "shopify_order_id": "5812345678901" }, "product": { "sku": "SUD-ARENA-M" }, "rating": 5, "title": "Really comfy", "body": "The fabric is soft inside.", "author": { "name": "María G.", "email": "maria@example.com" }, "language": "en"}- The product is looked up in this order:
product.shopify_product_id,product.sku,product.handleand, if the order has a single product, the order. - Don’t send
verified_purchase: we compute it from the real Shopify order. - Without a verified order, the review stays pending and waits for someone to approve it, whether it’s 1 or 5 stars.
- One review per product per order: the second returns
409 review_existswith the id of the first. author.nameis required when there’s no order.- Accepts
Idempotency-Key.
Response: 201 with the review. To add photos or videos, continue with /uploads.
List and filter
Section titled “List and filter”GET /reviews?status=pending,flagged&rating=1&since=…
| Parameter | Values |
|---|---|
status |
pending, published, rejected, spam, private and flagged (pending with some flag: phone, email, blocked word…). Several, comma-separated. |
kind |
product or store. |
product_id |
The product’s gid or number. |
rating |
From 1 to 5. |
source |
request_link, qr_store, qr_location, manual, import, api or pos. |
One review
Section titled “One review”GET /reviews/{id} returns the review with its flags, its reply and its photos and videos.
Moderate and reply
Section titled “Moderate and reply”PATCH /reviews/{id}
{ "status": "rejected", "rejection_reason": "It's about a different product." }| Field | What it does |
|---|---|
status |
published, rejected or spam. |
rejection_reason |
Required when rejecting: from 3 to 500 characters. |
merchant_reply |
Your public reply, up to 2,000 characters. null removes it. |
A change moderation doesn’t allow returns 409 invalid_state.
Delete
Section titled “Delete”DELETE /reviews/{id}. full key.
Bulk import
Section titled “Bulk import”POST /reviews/import with { "reviews": [ … ] }: up to 1,000 reviews and 5 MB per call. full key. Accepts Idempotency-Key.
Each review takes external_id, product, rating, title, body, author.name, created_at (required) and, if you have them, order.shopify_order_id (it can be older than 60 days), author.email, status, verified_purchase, incentivized, reply.body, reply.replied_at, photos (up to 5 https URLs) and videos (1).
{ "created": 998, "existing": 1, "rejected": 1, "results": [ { "index": 0, "status": "created", "review_id": "cm2k…" }, { "index": 1, "status": "exists", "review_id": "cm2j…" }, { "index": 2, "status": "rejected", "error": { "code": "product_not_found", "message": "…" } } ]}One bad review doesn’t take down the others, and sending the same batch again returns everything as exists: retrying doesn’t duplicate.
Photos and videos
Section titled “Photos and videos”They’re uploaded straight to our storage, in three steps:
POST /uploadswith{ "review_id": "cm2k…", "content_type": "image/jpeg", "bytes": 2483123 }. It returns201withmedia_idandupload: the method (PUT), the signed URL and the headers.- Upload the file with a
PUTtoupload.url, with those headers. POST /uploads/{media_id}/complete. We process it in the background.
| Type | Formats | Maximum |
|---|---|---|
| Photo | image/jpeg, image/png, image/heic, image/heif |
15 MB, up to 5 per review |
| Video | video/mp4, video/quicktime |
100 MB and 90 seconds, 1 per review |
Metrics
Section titled “Metrics”GET /stats?period=30 (7, 30 or 90 days): the same you see in Home.
{ "period_days": 30, "since": "2026-09-16T16:00:00.000Z", "metrics": { "requests_sent": 412, "reviews_collected": 131, "response_rate": 31.8, "with_media_rate": 42.7, "average_rating": 4.7 }, "pending_reviews": 6, "alerts": { "failed_requests": 0, "webhook_dlq": 0, "flagged_reviews": 2 }, "setup": { "channel_connected": true, "timing_saved": true, "form_saved": true, "first_request_sent": true }}Settings
Section titled “Settings”GET /settings returns the store’s settings, the flow (timing and reminders) and moderation (threshold and blocked words). PATCH /settings changes only what you send, with the same validation as the admin; a field that doesn’t exist returns 422 with its path in details. full key.
Locations
Section titled “Locations”GET /locations returns the active locations, with their internal reviews and whether the location question is turned on.
Webhooks
Section titled “Webhooks”GET /webhook-deliveries?status=dlq lists the webhook deliveries (pending, delivered, failed, dlq). POST /webhook-deliveries/{id}/retry retries one by hand, the same as the button in Channels. full key.