Skip to main content
Documentation

Reviews

reviews.private_feedback

Read the private ratings customers gave without posting a review
Read-onlyRead budget

REST

Shipping
POST /api/v1/reviews/private_feedback

MCP tool

Live
reviews.private_feedback

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

Operating contract

What customers told the SHOP directly after a job — a star rating and, often, a note. This never appears in public, anywhere.

DO NOT CONFUSE THIS WITH A GOOGLE REVIEW, and never reply to one publicly. A customer who rated the shop three stars privately said that in confidence; a public reply quoting it has broadcast something they chose not to publish, and no apology fixes that.

routed_to says where the rating sent them next. A low rating is deliberately routed to the shop rather than to Google — that is the whole point of the mechanism, and it is why the private ratings skew lower than the public ones.

note is the customer's own words. It is the most useful text in this whole domain and nothing else surfaces it.

job_id and customer_id, where set, are what jobs.get and customers.get take — reading the work behind a bad rating is usually the answer to it.

TRUNCATION: total is the exact number in the window and omitted is how many are not here. When omitted is not 0, call again with offset set to next_offset.

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
naturalNaturally idempotent — running it twice leaves the same world as running it once. A retrying integration needs no key.
Rate class
readCounted against the read budget — the widest of the four.

Partial answers

omitted is how many matching records are not in the response, and 0 is a real answer meaning you have all of them. Continue with next_offset.

Do not summarise from a truncated response

When `omitted` is not 0 there are more private ratings in this window. Call again with `offset` set to `next_offset`.

Input

FieldTypeDescription
max_ratinginteger1–5

Only ratings at this level or below. 3 is the unhappy end.

daysinteger1–730

How many days back to read, 1-730. Defaults to 90.

Default: 90
limitinteger1–100

How many ratings to return, 1-100. Defaults to 25.

Default: 25
offsetintegermin 0

How many to skip. Pass the previous response's next_offset to continue.

Default: 0

Output

FieldTypeDescription
itemsobject[]

items[].idstring

items[].ratingnumber

items[].notestring | null

items[].routed_tostring

items[].sourcestring

items[].customer_idstring | null

items[].job_idstring | null

items[].rated_atstring

totalnumber

omittednumber

next_offsetnumber | null

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/private_feedback \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"days":90,"limit":25,"offset":0}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/reviews/private_feedback", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "days": 90,
    "limit": 25,
    "offset": 0
  }),
});

// 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/private_feedback",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "days": 90,
    "limit": 25,
    "offset": 0
},
    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.private_feedback",
    "arguments": {
      "days": 90,
      "limit": 25,
      "offset": 0
    }
  }
}

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.
422validation_errorAn argument was wrong. The message names the field.
429rate_limitedToo many read calls. Back off and retry.
500internal_errorSomething failed on our side. Nothing was changed.