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, VIPThe 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-commercepermission on your brand and a connected Shopify integration. Without them the commerce endpoints return403(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_bytells you which identity key resolved the commerce contact:email,shopify_customer_id,phone, ornone.profileis the behavioral CDP profile (identity, segments, visit rollups).commerceis the spend summary over the customer's completed orders.integrationsis the presence of your connected integrations — each carries anid, an opaqueintegration_id, anis_enabledflag,status, and connect time only, never settings or credentials. Theintegration_idis a UUID, not a provider name; to map it to a provider (shopify,skio, …), cross-reference List Integrations, which returns eachintegration_idalongside itsprovider.subscriptionis 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:
| Status | error / code | What it means |
|---|---|---|
403 | forbidden / beta_not_enabled | Your brand isn't enrolled in the commerce beta. |
403 | forbidden / integration_not_connected | No connected Shopify integration, so no shop scope resolves. |
401 | unauthorized | Missing or invalid key. |
404 | not_found | Unknown customer or one belonging to another brand — indistinguishable by design, so you can't probe for another brand's customers. |
429 | rate_limited / products_proxy_throttled, or shopify_throttled | Only on the products proxy — wait the Retry-After seconds and retry. |
Next steps
- List Orders — a customer's full order history.
- List Products — the live product catalog.
- Brand RFM and At-Risk Customers — segment and re-engage customers.
- List Integrations and List Subscriptions — what's connected and who's subscribed.
- Commerce Data and Insights — the data model and metric definitions behind these endpoints.