VYG Docs

Build your first app

Build a dashboard that compares customer spending with your average lifetime value.

Build a customer dashboard that compares one customer's spending with your average customer lifetime value. The script prints a summary like:

jane@example.com — $1840.50 over 12 orders (brand average LTV $512.50)

The Quickstart gets you your first GET /v1/customers response. This guide builds on it with two reads: one customer and average customer spending.

Prerequisites

  • Customer data features enabled and a connected Shopify store. Without them the commerce endpoints return 403 (see Commerce data).
  • A vyg_ API key. It needs customers:read and insights:read. Create one as in the Quickstart; available permissions are covered in API keys.

Authenticate with the key as a bearer token. Base URL: https://api.vyg.app/v1.

Store your key in an environment variable so it never lands in source control:

export VYG_API_KEY="vyg_your_key_here"

Step 1 — Fetch a customer

GET /v1/customers/{id} returns one customer as a bare object. The {id} is a customer id; find it with GET /v1/customers/search?query=, which matches an email or phone prefix.

curl -s "https://api.vyg.app/v1/customers/8f2b0c1e-5d7a-4b7e-9a51-2c6f1e0d3a44" \
  -H "Authorization: Bearer $VYG_API_KEY"
const API = 'https://api.vyg.app';
const auth = {
	headers: { Authorization: `Bearer ${process.env.VYG_API_KEY}` },
};

const CUSTOMER_ID = '8f2b0c1e-5d7a-4b7e-9a51-2c6f1e0d3a44';
const res = await fetch(`${API}/v1/customers/${encodeURIComponent(CUSTOMER_ID)}`, auth);
const customer = await res.json();
{
	"id": "8f2b0c1e-5d7a-4b7e-9a51-2c6f1e0d3a44",
	"created_at": "2024-02-01T10:00:00.000Z",
	"email": "jane@example.com",
	"phone": "+15551234567",
	"currency": "USD",
	"orders_count": 12,
	"total_spend": 1840.5,
	"total_refunded": 0,
	"last_order_at": "2026-06-20T14:30:00.000Z",
	"coverage_from": "2024-02-01T10:00:00.000Z",
	"first_order_at": "2024-02-01T10:00:00.000Z",
	"events_30d": 41,
	"sessions_30d": 6,
	"product_views_30d": 19,
	"last_event_at": "2026-06-30T09:12:00.000Z",
	"identities": [
		{
			"type": "email",
			"value": "jane@example.com",
			"source": "shopify",
			"verified": true
		}
	],
	"merged_customer_ids": []
}
  • orders_count, total_spend, total_refunded reflect the order history available in VYG.
  • identities lists the identifiers resolved for the customer.
  • merged_customer_ids lists customers merged into this one.

Step 2 — Get average customer spending

GET /v1/insights/ltv returns the average customer spending you'll show alongside the customer.

curl -s "https://api.vyg.app/v1/insights/ltv" \
  -H "Authorization: Bearer $VYG_API_KEY"

Customer spending, grouped into ranges:

{
	"scope": "your-shop.myshopify.com",
	"total_revenue": "2050.00",
	"customer_count": 4,
	"average_ltv": "512.50",
	"currency_code": "USD",
	"distribution": [
		{ "label": "0-100", "min": 0, "max": 100, "customer_count": 1 },
		{ "label": "100-250", "min": 100, "max": 250, "customer_count": 1 },
		{ "label": "250-500", "min": 250, "max": 500, "customer_count": 1 },
		{ "label": "500-1000", "min": 500, "max": 1000, "customer_count": 0 },
		{ "label": "1000+", "min": 1000, "max": null, "customer_count": 1 }
	]
}

Brand LTV amounts are decimal strings in the store's currency, and every metric counts completed orders only. See Insights for the full definitions.

Step 3 — Assemble a minimal dashboard

Now wire the calls together. This script is dependency-free — it uses the built-in fetch (Node 18+ or Bun) and reads the key from VYG_API_KEY. It fetches the customer and average lifetime value in parallel, then renders both a one-line summary and a small HTML card.

function formatMoney(amount, currency) {
	return new Intl.NumberFormat(
		'en-US',
		currency ? { style: 'currency', currency } : { minimumFractionDigits: 2, maximumFractionDigits: 2 }
	).format(Number(amount));
}

const API = 'https://api.vyg.app';
const KEY = process.env.VYG_API_KEY;

async function get(path) {
	const res = await fetch(`${API}${path}`, {
		headers: { Authorization: `Bearer ${KEY}` },
	});
	if (!res.ok) {
		const body = await res.json().catch(() => ({}));
		throw new Error(`${res.status} ${body.error?.code ?? 'request_failed'}`);
	}
	return res.json();
}

async function loadDashboard(customerId) {
	const id = encodeURIComponent(customerId);
	const [customer, brand] = await Promise.all([get(`/v1/customers/${id}`), get('/v1/insights/ltv')]);

	const name = customer.email ?? customer.id;
	const spend = formatMoney(customer.total_spend, customer.currency);
	const brandAverage = formatMoney(brand.average_ltv, brand.currency_code);
	const orders = customer.orders_count;

	return { name, spend, orders, brandAverage };
}

function summaryLine(d) {
	return `${d.name} — ${d.spend} over ${d.orders} orders (brand average LTV ${d.brandAverage})`;
}

function renderCard(d) {
	return `<article class="customer-card">
  <h2>${d.name}</h2>
  <p>Spend: <strong>${d.spend}</strong> over ${d.orders} orders (brand average ${d.brandAverage})</p>
</article>`;
}

loadDashboard('8f2b0c1e-5d7a-4b7e-9a51-2c6f1e0d3a44')
	.then((d) => {
		console.log(summaryLine(d));
		console.log(renderCard(d));
	})
	.catch((err) => console.error('Dashboard load failed:', err.message));

Swap console.log(renderCard(d)) for wherever your app renders — inject the HTML string into the DOM, return it from a request handler, or map it into your framework of choice.

Handle errors

Every error is { "error": { "code": "...", "message": "..." } }. Check the HTTP status and branch on error.code, not on the message text (see Errors):

Statuserror.codeWhat it means
401unauthorizedMissing or invalid key.
403insufficient_scopeCustomer data features are unavailable, no Shopify store is connected, or your key needs additional permissions.
404not_foundNo customer was found for this ID.
429rate_limitedToo many requests (for example on the products catalog) — wait the Retry-After seconds and retry.

Next steps

On this page