Developers · Marketing Suite

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 classPrefixWhere it livesRules 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

StatusMeaningBody
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

FieldTypeNotes
typestringRequired. e.g. page_view, product_view, add_to_cart, conversion, or a custom name
visitorRefstringYour anonymous visitor reference
urlstringPage URL
productIdstringProduct the event concerns
valuenumberMonetary value (e.g. conversion amount)
propertiesobjectFree-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}

ParamDefaultNotes
groupByproductproduct | category
categoryFilter to one category
limit201–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

ParamDefaultNotes
productWith it: products most often in the same order as this one. Without: top sellers (popular fallback).
limit101–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

ParamDefaultNotes
locationRefFilter to one location's reviews
limit10Recent 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

ParamDefaultNotes
rangethis_yearNamed snapshots only: today, day, this_week, last_week, this_month, last_month, this_quarter, last_quarter, this_year, last_7_days
limit251–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

ParamDefaultNotes
afterCursor from the previous page's nextCursor
categoryFilter to one category
updatedSinceISO date-time; only products modified since (incremental sync)
limit501–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

ParamDefaultNotes
emailRequired. The contact's email
rangeall_timeAny 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": "…"
}
Ready to build?

Keys come with Studio Scale.

API access, including the Customer API and the Shopper App API, is part of the Studio Scale plan. Building something larger, or want us to build it with you?

See pricing Talk to us