Skip to main content
Analytics is server-side. These reads use the secret Org-API-Key on your backend only - never ship it in an app or client code. A request with a member token is rejected with 403. See Authentication and Server-side API.
Three read-only endpoints roll up your program’s activity: a program-wide summary, per-offers performance, and a timeseries bucketed over a window. All require the Org-API-Key.
EndpointSDK methodReturns
GET /v1/analytics/summaryanalyticsSummary()Program-wide totals and redemption status breakdown.
GET /v1/analytics/offersanalyticsOffers()One row per offer.
GET /v1/analytics/timeseriesanalyticsTimeseries({ from, to, interval })Buckets over a window.

Summary

analyticsSummary() calls GET /v1/analytics/summary and returns program-wide totals plus a redemption status-count breakdown.
curl
curl https://api.expys.com/v1/analytics/summary \
  -H "Authorization: Bearer YOUR_ORG_API_KEY"
memberCount
number
required
Total members in the program.
pointsMinted
number
required
Total points ever minted across the program.
pointsSpent
number
required
Total points ever spent across the program.
completionRate
number
required
Share of redemptions that reached completion.
redemptions
RedemptionStatusCounts
required
A breakdown of redemptions by status (see below).

Redemption status counts

redemptions counts every redemption by its lifecycle status, plus a total:
KeyMeaning
submittedRedemptions submitted.
openRedemptions in the open state.
awaiting_vendorWaiting on the vendor.
awaiting_customerWaiting on the customer.
purchasedPurchased (sometimes called confirmed).
completedCompleted experiences.
canceledCanceled redemptions.
totalSum across all statuses.
All values are numbers.

Offers

analyticsOffers() calls GET /v1/analytics/offers and returns one OfferAnalytics row per offer.
curl
curl https://api.expys.com/v1/analytics/offers \
  -H "Authorization: Bearer YOUR_ORG_API_KEY"
offers
OfferAnalytics[]
required
Per-offer performance rows.
Each OfferAnalytics row:
FieldTypeDescription
offerIdstringThe offer this row describes.
signupsnumberMembers who signed up for the offer.
completionsnumberRedemptions of the offer that completed.
cancellationsnumberRedemptions of the offer that were canceled.
pointsSpentnumberPoints spent on this offer.

Timeseries

analyticsTimeseries({ from, to, interval }) calls GET /v1/analytics/timeseries and returns buckets over a window. All three query parameters are required.
from
string
required
Start timestamp of the window (inclusive). The first bucket begins here.
to
string
required
End timestamp of the window. The last bucket ends here.
interval
string
required
The bucket granularity - how the window between from and to is divided into buckets (for example by day or by hour).
curl
curl "https://api.expys.com/v1/analytics/timeseries?from=2026-06-01T00:00:00Z&to=2026-06-23T00:00:00Z&interval=day" \
  -H "Authorization: Bearer YOUR_ORG_API_KEY"
buckets
TimeseriesBucket[]
required
One entry per interval bucket across the window.
Each TimeseriesBucket:
FieldTypeDescription
startTimenumberStart of the bucket.
endTimenumberEnd of the bucket.
signupsnumberSignups in the bucket.
pointsMintednumberPoints minted in the bucket.
pointsSpentnumberPoints spent in the bucket.
from and to are timestamps bounding the window; interval is the bucket granularity that divides it. Omitting any of the three is an error - all are required.

In code

The server client is constructed with the Org-API-Key. analyticsSummary, analyticsOffers, and analyticsTimeseries are all called on it.
import { initialize } from "@expys/sdk";

const orgApiKey = process.env.EXPYS_ORG_API_KEY;
if (!orgApiKey) {
  throw new Error(
    "Set EXPYS_ORG_API_KEY (your secret Org-API-Key, e.g. expys_live_...). " +
      "Run this on a backend only, never in a client app.",
  );
}

// Construct the client with the machine credential as the token. Server-mode
// methods are guarded against member tokens client-side.
const expys = initialize({
  baseUrl: process.env.EXPYS_BASE_URL,
  environment: "sandbox",
  token: orgApiKey,
});

const externalUserID = process.env.EXPYS_EXTERNAL_USER_ID ?? "user_42";

async function main(): Promise<void> {
  // Mint a short-lived member token for your app to use (return this to the app,
  // never the Org-API-Key). Idempotent POST: a retry replays rather than re-mints.
  const grant = await expys.exchangeToken({ externalUserID });
  console.log(`minted member token expiring at ${grant.expiresAt}`);

  // Upsert the member's profile. PUT is idempotent by HTTP semantics (no key).
  const member = await expys.setMember(externalUserID, {
    displayName: "Ada Lovelace",
    tier: "gold",
  });
  console.log(`member ${member.externalUserID} is now tier=${member.tier}`);

  // Credit points to the member's wallet. Idempotent POST sends an Idempotency-Key.
  const credited = await expys.creditPoints({
    amount: 100,
    externalUserID,
    reason: "welcome bonus",
  });
  console.log(`new balance: ${credited.balance} ${credited.currency.symbol}`);

  // Register a webhook. The signingSecret is shown ONLY on creation - store it now.
  const webhook = await expys.createWebhook({
    events: ["redemption.created"],
    url: "https://example.com/expys/webhooks",
  });
  console.log(`webhook ${webhook.id} secret: ${webhook.signingSecret}`);

  // Org-wide analytics rollups.
  const summary = await expys.analyticsSummary();
  console.log(
    `members: ${summary.memberCount}, minted: ${summary.pointsMinted}`,
  );
}

Next steps

Members and tiers

The per-member counterpart: profiles, wallet, and redemption counts.

Points and wallet

How pointsMinted and pointsSpent are generated.