LiveRecoverVYG Developer Docs
CDP REST API

Build Your First App

Build a tiny customer dashboard on the unified commerce layer — fetch a unified customer, pull their insights and your brand LTV, and render a compact summary.

This guide builds a small customer dashboard on top of the unified commerce data layer. By the end you'll have a dependency-free script that takes a customer identifier, pulls their profile, spend, and lifetime value, and prints a one-line summary like:

Alice — LTV $300.00 over 3 orders, VIP

The Quickstart is the profiles "hello world" — it gets you your first GET /cdp/profiles response. This guide picks up from there and builds on the unified commerce layer: the customer view that joins a CDP profile with its order history, subscription, and connected integrations.

Prerequisites

  • The beta:cdp-commerce permission on your brand and a connected Shopify integration. Without them the commerce endpoints return 403 (see Commerce Data).
  • A vyg_ API key. Issue one from the Quickstart; credential classes and scope are covered in Authentication.

All reads authenticate with the key as a Bearer token, and every response is bound to your connected shop. Base URL: https://cdp.vyg.app.

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 unified customer

GET /cdp/customers/{identifier} returns one unified record. The {identifier} can be an email, a phone (starts with +), a Shopify customer id (all digits), or a CDP profile id — the API classifies it by shape.

curl -s "https://cdp.vyg.app/cdp/customers/alice@example.com" \
  -H "Authorization: Bearer $VYG_API_KEY"
const API = 'https://cdp.vyg.app';
const auth = { headers: { Authorization: `Bearer ${process.env.VYG_API_KEY}` } };

const res = await fetch(`${API}/cdp/customers/${encodeURIComponent('alice@example.com')}`, auth);
const customer = await res.json();

The response splits into four independent parts:

{
	"identifier": "alice@example.com",
	"matched_by": "email",
	"scope": "your-shop.myshopify.com",
	"profile": {
		"id": "shopify_your-shop_5483611717768",
		"provenance": "server",
		"identity": {
			"email": "alice@example.com",
			"phoneNumber": "+14155550123",
			"firstName": "Alice",
			"shopifyCustomerId": "5483611717768",
			"shopDomain": "your-shop.myshopify.com"
		},
		"segments": ["vip", "repeat-buyer"],
		"behavior": {
			"nb_of_visits": 12,
			"first_visit": "2026-01-04T10:15:00.000Z",
			"last_visit": "2026-06-30T18:02:00.000Z"
		}
	},
	"commerce": {
		"total_spend": "200.00",
		"currency_code": "USD",
		"order_count": 2,
		"aov": "100.00",
		"first_order_at": "2026-02-20T00:00:00.000Z",
		"last_order_at": "2026-03-02T00:00:00.000Z"
	},
	"integrations": [
		{
			"id": "b1a2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
			"integration_id": "d1e3f5a7-2b4c-6d8e-0f1a-3c5e7b9d1f23",
			"is_enabled": true,
			"status": "connected",
			"connected_at": "2026-01-01T00:00:00.000Z"
		}
	],
	"subscription": {
		"status": "active",
		"active_count": 1,
		"total_count": 1,
		"next_billing_date": "2026-08-01T00:00:00.000Z"
	}
}
  • matched_by tells you which identity key resolved the commerce contact: email, shopify_customer_id, phone, or none.
  • profile is the behavioral CDP profile (identity, segments, visit rollups).
  • commerce is the spend summary over the customer's completed orders.
  • integrations is the presence of your connected integrations — each carries an id, an opaque integration_id, an is_enabled flag, status, and connect time only, never settings or credentials. The integration_id is a UUID, not a provider name; to map it to a provider (shopify, skio, …), cross-reference List Integrations, which returns each integration_id alongside its provider.
  • subscription is the customer's subscription status.

The two halves are joined but independent, so partial matches degrade cleanly: a profile with no orders returns commerce: null and subscription.status: "none"; orders with no profile return profile: null.

Step 2 — Pull insights

For lifetime value, call GET /cdp/customers/{identifier}/insights for the one customer, and GET /cdp/insights/ltv for the brand-wide aggregate you'll show alongside it.

curl -s "https://cdp.vyg.app/cdp/customers/alice@example.com/insights" \
  -H "Authorization: Bearer $VYG_API_KEY"

curl -s "https://cdp.vyg.app/cdp/insights/ltv" \
  -H "Authorization: Bearer $VYG_API_KEY"
const insightsRes = await fetch(`${API}/cdp/customers/${encodeURIComponent('alice@example.com')}/insights`, auth);
const insights = await insightsRes.json();

const brandRes = await fetch(`${API}/cdp/insights/ltv`, auth);
const brand = await brandRes.json();

The per-customer summary (the LTV fields shown here; the same insights object also carries rfm, top_products, and churn for the customer — see Insights):

{
	"identifier": "alice@example.com",
	"matched_by": "email",
	"scope": "your-shop.myshopify.com",
	"insights": {
		"ltv": "300.00",
		"order_count": 3,
		"aov": "100.00",
		"first_order_at": "2026-01-10T00:00:00.000Z",
		"last_order_at": "2026-03-20T00:00:00.000Z",
		"currency_code": "USD"
	}
}

The brand-wide LTV, with the fixed distribution buckets:

{
	"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 }
	]
}

Every amount is a dollar decimal string 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 their insights in parallel, then renders both a one-line summary and a small HTML card.

const API = 'https://cdp.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 ?? 'request_failed'}`);
	}
	return res.json();
}

async function loadDashboard(identifier) {
	const id = encodeURIComponent(identifier);
	const [customer, { insights }] = await Promise.all([
		get(`/cdp/customers/${id}`),
		get(`/cdp/customers/${id}/insights`),
	]);

	const name = customer.profile?.identity?.firstName ?? identifier;
	const segments = customer.profile?.segments ?? [];
	const tag = segments.includes('vip') ? 'VIP' : (segments[0] ?? 'customer');

	return { name, tag, ltv: insights.ltv, orders: insights.order_count };
}

function summaryLine(d) {
	return `${d.name} — LTV $${d.ltv} over ${d.orders} orders, ${d.tag}`;
}

function renderCard(d) {
	return `<article class="customer-card">
  <h2>${d.name} <small>${d.tag}</small></h2>
  <p>Lifetime value: <strong>$${d.ltv}</strong> over ${d.orders} orders</p>
</article>`;
}

loadDashboard('alice@example.com')
	.then((d) => {
		console.log(summaryLine(d)); // Alice — LTV $300.00 over 3 orders, VIP
		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. The data layer is the same either way.

Handle errors

Check the HTTP status and branch on the machine-readable fields, not on the description text (see Errors). The commerce gate returns error: "forbidden" with the specific reason in code; other errors carry the reason directly in error:

Statuserror / codeWhat it means
403forbidden / beta_not_enabledYour brand isn't enrolled in the commerce beta.
403forbidden / integration_not_connectedNo connected Shopify integration, so no shop scope resolves.
401unauthorizedMissing or invalid key.
404not_foundUnknown customer or one belonging to another brand — indistinguishable by design, so you can't probe for another brand's customers.
429rate_limited / products_proxy_throttled, or shopify_throttledOnly on the products proxy — wait the Retry-After seconds and retry.

Next steps

On this page