Skip to main content
Documentation

Reports

reports.insights

List the advisor's report on what to fix in this business
Read-onlyRead budget

REST

Shipping
POST /api/v1/reports/insights

MCP tool

Live
reports.insights

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

Operating contract

THE ADVICE THE SHOP HAS ALREADY BEEN GIVEN, from the most recent advisor run: a headline, a summary, and a set of cards that each name one thing to do about one number.

READING IS FREE; RUNNING IS NOT. This returns the last run's output and spends nothing. Starting a NEW analysis charges the shop's AI credits and stays a person pressing the button on /insights — so when the latest run is stale, say when it was and offer the button rather than triggering one.

severity sorts the work. warning is something getting worse, opportunity is money on the table, and win is something that improved — read the wins out too, because an owner who only ever hears warnings stops opening the page.

action is the concrete next step in the shop's own context. It is the field to act on; body is the reasoning behind it.

metric_keys links each card back to reports.metrics, which is where the number behind the claim lives. A card quoted without its number is an opinion.

status of done or dismissed means somebody has already dealt with this card — those are in history, not in current, and repeating one back as new advice is how a shop learns to ignore the advisor.

latest_run_at: null means this shop has never run the advisor. There is then no advice, and inventing some from the metrics would be putting words in the product's mouth.

The latest run's cards and a dozen resolved ones are returned in full, so nothing is truncated.

Who may call it

Permission
reports.accessThe caller must hold Reports & insights at the ACT level. A read-only dashboard grant on the same section is refused.
Plan
GrowthIncluded from Growth up. A shop below it gets 402 plan_required, which names the 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.

Input

FieldTypeDescription
include_historyboolean

True also returns the recently resolved cards — what the shop has already acted on or dismissed.

Output

FieldTypeDescription
industrystring | null

run_countnumber

latest_run_atstring | null

headlinestring | null

summarystring | null

rangestring | null

currentobject[]

current[].idstring

current[].categorystring

current[].severitystring

current[].titlestring

current[].bodystring

current[].actionstring | null

current[].metric_keysstring[]

current[].statusstring

current[].created_atstring

historyobject[]

history[].idstring

history[].categorystring

history[].severitystring

history[].titlestring

history[].statusstring

history[].updated_atstring

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/reports/insights \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

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

// 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/reports/insights",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={},
    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": "reports.insights",
    "arguments": {}
  }
}

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.
402plan_requiredThe shop's plan does not include this capability.
403forbiddenThis login does not hold reports.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.