Customer API
Base URL https://api.plunk.io/cx/v1. Versioned,
key-scoped, and self-describing: start every integration at
GET /capabilities.
Authentication
Every request carries an API key, issued from your workspace. Send it as a bearer
token, or in the X-Api-Key header; both are
equivalent.
curl https://api.plunk.io/cx/v1/capabilities \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"
# or
curl https://api.plunk.io/cx/v1/capabilities \
-H "X-Api-Key: sk_live_XXXXXXXXXXXX"
| Key class | Prefix | Where it lives | Rules the API enforces |
|---|---|---|---|
| Publishable pk | pk_live_ / pk_test_ |
Browser JS and mobile bundles: shippable, public | Origin-locked: requests are accepted only from the web origins registered on the key. Reads are aggregate and non-PII; revenue fields and person-level data never appear. The one write is event ingest. |
| Secret sk | sk_live_ / sk_test_ |
Your server only | Full scope your workspace exposes, including revenue-bearing analytics and identity-scoped resources. A secret key presented from a browser origin is rejected outright. |
Each key also carries scopes (e.g.
products:read). A key with no explicit scope grant
holds every scope its class allows; a key with a grant is limited to exactly those.
The scope each endpoint requires is listed with the endpoint below, and the pk/sk
pill on each section shows the minimum key class.
Errors & limits
| Status | Meaning | Body |
|---|---|---|
401 |
Missing or invalid key; a publishable key from an unregistered origin; a secret key from a browser. | Message text with WWW-Authenticate: ApiKey |
403 |
The key is valid but the scope isn't permitted; the resource family isn't enabled for your workspace, isn't available to the key's class, or wasn't granted to the key. | { "error": "scope_not_permitted", "scope": "…" } |
404 |
The resource doesn't exist; each endpoint returns a named error, e.g.
product_not_found,
dealer_not_found,
contact_not_found. |
{ "error": "…", … } |
400 |
Invalid request: a required field or parameter is missing or unsupported. The body names the field and what's accepted. | { "error": "…", "note": "…" } |
429 |
Rate limit exceeded for the key. Back off and retry. | Message text |
Every response is scoped to your workspace by the key; there is no cross-workspace surface. Requests are metered per key.
Date ranges
Endpoints with a {range} segment share one
vocabulary: a named token, or a custom window as
custom_YYYYMMDD_YYYYMMDD
(e.g. custom_20260101_20260630).
An unrecognized token falls back to this_year.
today · yesterday · last_7_days · last_30_days · last_90_days this_month · last_month · ytd · this_year · last_year · all_time custom_YYYYMMDD_YYYYMMDD
Exception: /markets reads
pre-aggregated snapshots and accepts its own named set (listed there) without
custom_ windows.
Capabilities pksk
The discovery document and your integration contract. It lists exactly the resource families the presented key can reach: publishable keys never see secret-only families, and families light up as the matching data exists in your workspace. Fetch it once at startup and code against it, not a static list.
GET/cx/v1/capabilities
{
"tenant": "yourworkspace",
"tenantType": "retailer", // manufacturer | retailer | vertical
"mode": "live", // live | test, from the key
"aggregationFloor": 5,
"labels": {
"location": "Showroom" // white-label terms, when configured
},
"families": [
{
"name": "products",
"key": "publishable",
"scopes": ["products:read", "recommendations:read"],
"label": null,
"endpoints": [
"GET /cx/v1/products/top/{range}",
"GET /cx/v1/products/{id}",
"GET /cx/v1/recommendations"
]
},
{
"name": "store-performance",
"key": "secret",
"scopes": ["store-performance:read"],
"label": "Showroom",
"endpoints": [
"GET /cx/v1/stores/{range}",
"GET /cx/v1/stores/{range}/{storeId}"
]
}
]
}
Ingest pk
The write path: behavioral events from your storefront or app into your workspace.
Scope ingest:write. A 202
with an event id means the event is recorded on the tracking rail; if it cannot be
recorded you get a 503 with
ingest_unavailable. Retry with backoff on 503.
POST/cx/v1/ingest/event
| Field | Type | Notes |
|---|---|---|
type | string | Required. e.g. page_view, product_view, add_to_cart, conversion, or a custom name |
visitorRef | string | Your anonymous visitor reference |
url | string | Page URL |
productId | string | Product the event concerns |
value | number | Monetary value (e.g. conversion amount) |
properties | object | Free-form additional attributes |
curl -X POST https://api.plunk.io/cx/v1/ingest/event \
-H "Authorization: Bearer pk_live_XXXX" -H "Content-Type: application/json" \
-d '{
"type": "product_view",
"visitorRef": "v_84af",
"productId": "L-2750-52"
}'
# 202 Accepted
{
"accepted": true,
"eventId": "9d1c…",
"type": "product_view"
}
POST/cx/v1/ingest/identify
Associates an anonymous visitor with a known identity.
visitorRef is required, plus at least one of
email, hashedEmail,
userId; optional traits
object.
{
"accepted": true,
"eventId": "41be…",
"visitorRef": "v_84af"
}
Products pk
Product intelligence from your sales rail. Scope
products:read.
GET/cx/v1/products/top/{range}
| Param | Default | Notes |
|---|---|---|
groupBy | product | product | category |
category | Filter to one category | |
limit | 20 | 1–100 |
Ranked by revenue. Publishable keys receive
rank, units and
orders, with an aggregation floor (rows under 5
orders are suppressed). Secret keys additionally receive
revenue and aov,
with no floor.
curl "https://api.plunk.io/cx/v1/products/top/this_month?limit=3" \
-H "Authorization: Bearer sk_live_XXXX"
{
"range": "this_month",
"groupBy": "product",
"keyClass": "secret",
"count": 3,
"items": [
{
"rank": 1,
"id": "L-2750-52",
"title": "Track-Arm Sofa",
"category": "Sofas",
"units": 41,
"orders": 38,
"revenue": 61250.00,
"aov": 1611.84
}
]
}
GET/cx/v1/products/{id}
The public product card, safe for a storefront. Never includes cost.
{
"id": "L-2750-52",
"title": "Track-Arm Sofa",
"category": "Sofas",
"price": 1899.00,
"salePrice": null,
"image": "https://…",
"link": "https://…",
"availability": "in stock",
"brand": "…"
}
Recommendations pk
"Customers also bought," computed from real order co-occurrence. Scope
recommendations:read. Returns rank and co-purchase
counts, never revenue.
GET/cx/v1/recommendations
| Param | Default | Notes |
|---|---|---|
product | With it: products most often in the same order as this one. Without: top sellers (popular fallback). | |
limit | 10 | 1–50 |
{
"basis": "co_purchase",
"product": "L-2750-52",
"count": 10,
"items": [
{
"rank": 1,
"id": "L-2750-OTT",
"title": "Matching Ottoman",
"category": "Ottomans",
"coPurchasedOrders": 19
}
]
}
Reviews pk
Aggregated review ratings from your connected social profiles: trust badges for
your storefront. Scope reviews:read; available once
a social account is connected to the workspace.
GET/cx/v1/reviews
| Param | Default | Notes |
|---|---|---|
locationRef | Filter to one location's reviews | |
limit | 10 | Recent reviews returned, 1–50 |
{
"locationRef": null,
"averageRating": 4.6,
"reviewCount": 212,
"recent": [
{
"author": "…",
"rating": 5,
"text": "…",
"date": "2026-08-14T00:00:00",
"sentiment": "positive"
}
]
}
Locations pk
Your physical stores and outlets: the feed behind a store locator. Scope
locations:read; available for workspaces with a
retail channel.
GET/cx/v1/locations
{
"locations": [
{
"id": "S01",
"name": "Main Street Showroom",
"type": "store",
"city": "Charlotte",
"region": "NC",
"postalCode": "28202",
"lat": 35.2271,
"lng": -80.8431
},
{
"id": "O02",
"name": "Clearance Outlet",
"type": "outlet"
}
]
}
Markets pk
Where your demand comes from: sessions, views and conversions by city, with
coordinates for maps. Scope markets:read.
GET/cx/v1/markets
| Param | Default | Notes |
|---|---|---|
range | this_year | Named snapshots only: today, day, this_week, last_week, this_month, last_month, this_quarter, last_quarter, this_year, last_7_days |
limit | 25 | 1–200 |
Secret keys additionally receive
conversionValue per market.
{
"range": "this_month",
"keyClass": "publishable",
"count": 25,
"items": [
{
"rank": 1,
"city": "Charlotte",
"region": "NC",
"sessions": 4210,
"uniqueUsers": 3384,
"views": 11840,
"conversions": 96,
"lat": 35.22,
"lng": -80.84
}
]
}
Catalog sk
The full product catalog, cursor-paged, with incremental sync, built for a trade
site or portal that mirrors your line. Scope
catalog:read.
GET/cx/v1/catalog
| Param | Default | Notes |
|---|---|---|
after | Cursor from the previous page's nextCursor | |
category | Filter to one category | |
updatedSince | ISO date-time; only products modified since (incremental sync) | |
limit | 50 | 1–200 |
{
"count": 50,
"nextCursor": "L-2750-52",
"items": [
{
"id": "…",
"title": "…",
"category": "…",
"price": 1899.00,
"salePrice": null,
"image": "https://…",
"link": "https://…",
"availability": "in stock",
"brand": "…",
"modifiedDate": "2026-08-20T03:10:00"
}
]
}
# Page forward with ?after=nextCursor until nextCursor is null.
Visitor sk
Personalization for a resolved contact: which audiences they belong to, for
content targeting and gating on your site. Scope
visitor:read. {reference}
is the numeric contact id in your workspace.
GET/cx/v1/visitor/{reference}/profile
{
"reference": "18442",
"firstName": "…",
"lastName": "…",
"lifecycleStage": null,
"segmentCount": 3,
"segments": [
{
"id": "…",
"name": "High-intent sofas",
"type": "behavioral"
}
]
}
GET/cx/v1/visitor/{reference}/segments
Just the audience memberships:
{
"reference": "18442",
"count": 3,
"segments": [
{
"id": "…",
"name": "High-intent sofas",
"type": "behavioral"
}
]
}
Dealers sk
Wholesale account analytics for manufacturers: every dealer, ranked and
trended. Scopes dealers:read and
dealer-performance:read; available for workspaces
with wholesale sales.
GET/cx/v1/dealers
The directory with lifetime totals. ?limit= 1–200,
default 50.
{
"count": 50,
"items": [
{
"id": "D-1042",
"revenue": 412870.00,
"orders": 214,
"units": 1130,
"firstOrder": "2023-02-11T00:00:00",
"lastOrder": "2026-08-19T00:00:00"
}
]
}
GET/cx/v1/dealers/performance/{range}
Ranked by revenue in the window, with percent change vs the prior equal-length
period. Scope dealer-performance:read.
{
"range": "this_quarter",
"count": 50,
"items": [
{
"id": "D-1042",
"revenue": 88450.00,
"orders": 41,
"aov": 2157.32,
"deltaPct": 12.4
}
]
}
GET/cx/v1/dealers/{id}
One dealer's totals plus its top products.
{
"dealer": {
"id": "D-1042",
"revenue": 412870.00,
"orders": 214,
"units": 1130,
"aov": 1929.30,
"firstOrder": "…",
"lastOrder": "…"
},
"topProducts": [
{
"id": "…",
"title": "…",
"revenue": 61200.00,
"units": 84
}
]
}
Stores sk
Retail store performance: the retailer twin of dealer analytics. Scope
store-performance:read; available for workspaces
with a retail channel.
GET/cx/v1/stores/{range}
The roster with chain totals, percent change vs the prior equal-length period, and each store's share of chain revenue.
{
"range": "this_month",
"chainRevenue": 2410083.00,
"chainOrders": 1181,
"count": 5,
"stores": [
{
"id": "S01",
"name": "Main Street",
"type": "store",
"revenue": 612044.00,
"orders": 292,
"units": 631,
"aov": 2096.04,
"deltaPct": 7.9,
"sharePct": 25.4
}
]
}
GET/cx/v1/stores/{range}/{storeId}
One store's KPIs and top categories.
{
"range": "this_month",
"id": "S01",
"name": "Main Street",
"type": "store",
"kpi": {
"revenue": 612044.00,
"orders": 292,
"units": 631,
"aov": 2096.04
},
"topCategories": [
{
"category": "Sofas",
"revenue": 228412.00,
"units": 121
}
]
}
Audiences sk
Audience performance: membership, engagement, and conversion value per audience.
Scope audience-performance:read. Named ranges read
the latest snapshot; custom_ windows aggregate the
daily series (flows sum, membership takes the window max).
GET/cx/v1/audiences/{range}
{
"range": "this_month",
"count": 12,
"audiences": [
{
"name": "High-intent sofas",
"audienceId": "…",
"members": 1840,
"conversions": 44,
"conversionValue": 96410.00,
"engagements": 3120,
"engagementValue": 8140.00,
"views": 20110,
"sessions": 6120,
"uniqueUsers": 1710,
"avgTimespan": 84.2
}
]
}
GET/cx/v1/audiences/{range}/{audienceId}/trend
Daily membership counts across the window.
{
"range": "last_90_days",
"audienceId": "…",
"name": "High-intent sofas",
"points": [
{
"date": "2026-06-01",
"members": 1502
}
]
}
Customer sales sk
Identified-customer purchase analytics: summary KPIs, repeat rate, and top
customers by revenue. Scope customer-sales:read.
GET/cx/v1/customers/sales/{range}
?top= customers returned, 1–100, default 25.
{
"range": "ytd",
"summary": {
"revenue": 1804210.00,
"orders": 902,
"units": 1911,
"customers": 781,
"aov": 2000.23,
"repeatCustomers": 84,
"repeatRatePct": 10.8
},
"topCustomers": [
{
"customerId": "C-2201",
"revenue": 41210.00,
"orders": 6,
"units": 14,
"aov": 6868.33,
"lastOrder": "2026-08-12"
}
]
}
Contact interest sk
The behavioral profile behind one contact: what they viewed, did, and bought.
Scope contact-interest:read. PII in, PII out:
server-to-server only.
GET/cx/v1/contacts/interest
| Param | Default | Notes |
|---|---|---|
email | Required. The contact's email | |
range | all_time | Any shared range token |
{
"range": "all_time",
"email": "…",
"lastSeen": "2026-08-21T14:02:11Z",
"impressions": 214,
"engagements": 38,
"conversions": 2,
"conversionValue": 4210.00,
"topPages": [
{
"url": "/sofas/track-arm",
"views": 31
}
],
"topActions": [
{
"type": "form",
"action": "quote_request",
"count": 2
}
]
}
GET/cx/v1/contacts/interest/top/{range}
The most-engaged contacts in the window. ?top=
1–100, default 25.
{
"range": "last_30_days",
"count": 25,
"contacts": [
{
"email": "…",
"engagements": 41,
"lastSeen": "2026-08-22T09:12:44Z",
"conversions": 1,
"conversionValue": 2210.00
}
]
}
Forecasting sk
The demand-forecast rail: projected demand per product, and similarity forecasts
for designs that don't exist yet, built from your own comparable products. Scope
forecast:read.
GET/cx/v1/forecast/top/{range}
Products ranked by projected views in the window.
?top= 1–100, default 25.
{
"range": "this_month",
"count": 25,
"products": [
{
"productId": "L-2750-52",
"title": "Track-Arm Sofa",
"imageLink": "https://…",
"projectedViews": 4210,
"projectedUniqueUsers": 3180,
"contacts": 112,
"locations": 3,
"places": 14
}
]
}
GET/cx/v1/products/{id}/forecast/{range}
One product's forecast: the prediction series plus projected units by store.
{
"range": "this_quarter",
"productId": "L-2750-52",
"title": "Track-Arm Sofa",
"series": [
{
"date": "2026-09-01",
"grain": "day",
"views": 140,
"uniqueUsers": 104
}
],
"projectedSalesByStore": [
{
"storeId": "S01",
"projectedUnits": 9
}
]
}
GET/cx/v1/forecast/similarity
All similarity forecasts, newest first. Returns
404 similarity_forecast_not_available until the
similarity engine has produced output for your workspace.
{
"count": 4,
"forecasts": [
{
"id": "…",
"name": "Curved-back lounge concept",
"category": "Chairs",
"status": "complete",
"imageLink": "https://…",
"projectedViews": 1840,
"projectedUniqueUsers": 1420,
"contacts": 40,
"locations": 2,
"places": 8,
"modified": "2026-08-14",
"similarProducts": 6
}
]
}
GET/cx/v1/forecast/similarity/{id}
One similarity forecast in full: the prediction series, the comparable products it was built from (with similarity scores), and projected units by store.
{
"forecast": {
"id": "…",
"name": "…",
"category": "…",
"description": "…",
"status": "complete",
"imageLink": "…",
"projectedViews": 1840
},
"series": [
{
"date": "2026-09-01",
"grain": "day",
"views": 61,
"uniqueUsers": 48
}
],
"similarProducts": [
{
"productId": "…",
"title": "…",
"link": "…",
"imageLink": "…",
"similarityScore": 0.9142
}
],
"projectedSalesByStore": [
{
"storeId": "S01",
"projectedUnits": 4
}
]
}
Enrichment sk
Contact append from the workspace's enrichment cache: name, address and
demographics for an email you already hold. Scope
enrich:read. PII-bearing: server-to-server only.
POST/cx/v1/enrich/contact
Body: { "email": "…" } (required).
{
"matched": true,
"source": "cache",
"contact": {
"firstName": "…",
"lastName": "…",
"email": "…",
"phone": "…",
"address": "…",
"address2": null,
"city": "…",
"region": "…",
"postalCode": "…",
"country": "US",
"age": 44
}
}
# No cached match:
{
"matched": false,
"email": "…",
"source": "cache",
"note": "…"
}