Skip to main content
Documentation

Reviews

reviews.request

Text one customer asking them to leave the shop a review
WritesReaches the customerSensitiveSend budget

REST

Shipping
POST /api/v1/reviews/request

MCP tool

Live
reviews.request

Exposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Reviews domain.

Operating contract

TEXTS A REAL CUSTOMER. It sends the shop's own review-request template to their mobile, with a tracked link to the shop's review page, and logs the ask so the cooldown knows about it.

SIX GATES RUN BEFORE ANYTHING SENDS, and every one of them can refuse: the cooldown (has this customer been asked recently), the shop's texting setup, quiet hours, the customer's opt-out, the per-contact courtesy budget, and the sender-ownership check. A refusal comes back as a plain sentence — read it and stop, rather than retrying.

THE COOLDOWN IS A PROMISE, NOT A RATE LIMIT. This product's claim is that a shop never asks the same customer twice, and pushing past a refusal here is what makes that claim false. Check reviews.unreviewed_asks and its can_nudge before asking anybody a second time.

QUIET HOURS ARE NOT BYPASSED even though a person asked for this. A review request is never urgent enough to text somebody at eleven at night.

job_id is optional and worth giving: it stamps the ask against the work, so the message says 'your install' meaningfully and the funnel can attribute the review. A job that is not this customer's is dropped rather than trusted.

There is NO body to write. The shop's own template is used, so every ask reads the same way and carries the opt-out line the law requires.

IDEMPOTENCY IS none in the sense that matters — but the cooldown makes a duplicate call refuse rather than send twice, which is the protection that counts here.

Who may call it

Permission
marketing.accessThe caller must hold Reviews & market at the ACT level. A read-only dashboard grant on the same section is refused.
Plan
Every planNo plan gate. Available on every Service VIN plan.
Retries
noneA repeat genuinely does it twice and no key can make it not. Read before you write; the operating contract says what to read.
Rate class
sendCounted against the send budget, the tightest one: what a runaway loop spends here is the shop's own sending reputation.

Input

FieldTypeDescription
customer_idrequiredstringuuid

The customer to ask, as returned by customers.list or customers.get.

job_idstringuuid

The finished job this is about, as returned by jobs.list. Must be theirs.

Output

FieldTypeDescription
sentboolean

request_idstring

customer_namestring

review_urlstring

Examples

Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.

curl
export SERVICEVIN_API_KEY=svk_live_…

curl -X POST https://www.servicevin.com/api/v1/reviews/request \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer_id":"9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20"}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/reviews/request", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20"
  }),
});

// Success and failure are both envelopes. Switch on error.code, never
// on error.message — the codes are stable, the messages are for people.
const payload = await res.json();
if (!res.ok) throw new Error(payload.error.code);
const data = payload.data;

Python (requests)
import os, requests

res = requests.post(
    "https://www.servicevin.com/api/v1/reviews/request",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "customer_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20"
},
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(payload["error"]["code"])
data = payload["data"]

MCP tools/call — https://www.servicevin.com/api/mcp
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "reviews.request",
    "arguments": {
      "customer_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20"
    }
  }
}

Refusals

The four gates run in this order on every surface, and the order is not arbitrary — see Authentication.

StatusCodeWhen
404not_foundThe id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both.
403forbiddenThis login does not hold marketing.access.
403insufficient_scopeThe credential is read-only and this capability writes.
422validation_errorAn argument was wrong. The message names the field.
429rate_limitedToo many send calls. Back off and retry.
500internal_errorSomething failed on our side. Nothing was changed.