Documentation
Reports
reports.insights
REST
ShippingPOST /api/v1/reports/insightsMCP tool
Livereports.insightsExposed 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
| Field | Type | Description |
|---|---|---|
| include_history | boolean | True also returns the recently resolved cards — what the shop has already acted on or dismissed. |
Output
| Field | Type | Description |
|---|---|---|
| industry | string | null | — |
| run_count | number | — |
| latest_run_at | string | null | — |
| headline | string | null | — |
| summary | string | null | — |
| range | string | null | — |
| current | object[] | — |
| current[].id | string | — |
| current[].category | string | — |
| current[].severity | string | — |
| current[].title | string | — |
| current[].body | string | — |
| current[].action | string | null | — |
| current[].metric_keys | string[] | — |
| current[].status | string | — |
| current[].created_at | string | — |
| history | object[] | — |
| history[].id | string | — |
| history[].category | string | — |
| history[].severity | string | — |
| history[].title | string | — |
| history[].status | string | — |
| history[].updated_at | string | — |
Examples
Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.
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 '{}'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;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"]{
"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.
| Status | Code | When |
|---|---|---|
| 404 | not_found | The id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both. |
| 402 | plan_required | The shop's plan does not include this capability. |
| 403 | forbidden | This login does not hold reports.access. |
| 422 | validation_error | An argument was wrong. The message names the field. |
| 429 | rate_limited | Too many read calls. Back off and retry. |
| 500 | internal_error | Something failed on our side. Nothing was changed. |