GET /v1/installations (partner admin session) gives you the module_id
and pinned version_id, GET /v1/modules/{moduleId}/versions returns each
version’s manifest, and the emits[] entry for your event type carries the
JSON Schema for its payload. An installation stays on its pinned version until
you adopt a newer one, so read the pinned manifest rather than assuming the
newest catalog version.
The catalog
“Pays once per item” is the always-on same-content guard described in
XP: the first paid event claims that
item (
content_id, quiz_id, video_id) for the member, and a later event
for the same item is declined as content_already_earned, even if it
arrives under a fresh event_id. The repeatable types have no per-item
identity to claim, so the brakes there are the per-action daily and lifetime
award limits your organization configures in the console.
Payload contracts
Send these as thepayload of your event. The schemas are closed: an
undeclared field is rejected. Every event needs a member identity: your
member_external_id at the envelope level or a platform user_id in the
payload; an event with neither is recorded but earns nothing.
engagement.content.viewed
Send
duration_seconds as a whole number of seconds. A fractional value such as 12.5 fails the platform’s check on the event, and the event earns nothing.
engagement.quiz.completed
Send
score as a whole number. A fractional score fails the same check, and the event earns nothing.
engagement.video.completed
Completion is your claim; the measurement fields use Google Analytics 4’s
parameter names so a video player integration can forward what it already
emits.
engagement.profile.completed
A profile milestone, not a field-change event. No required payload fields.
engagement.venue.attended
Check-in evidence is yours: the platform validates the field shapes, stores
them and passes them through, but does not verify a geofence, QR value or
beacon signal.
engagement.reply.posted
One event per reply, so a member can have many.
Choose your event ids deliberately
event_id is the deduplication anchor: a repeat send of the same id credits
nothing a second time. Derive it from the identity of the moment, not at
random, and reuse it on retries:
- Profile completed:
organization_id + member_id. One milestone per member. - Venue attended:
organization_id + member_id + venue_id + fixture_id, or the UTC calendar date ofoccurred_atwhen there is no fixture. One check-in per member, venue and occasion. - Video completed:
organization_id + member_id + video_id. The platform’s same-content guard backs this up regardless of event ids. - Reply posted:
organization_id + reply_id. A different reply must get a different id.
engagement.content.viewed and
engagement.video.completed are different types: if you emit both for the
same asset and both are priced, both pay. The platform does not infer that
they describe the same content.
Discover what each event pays
The manifest tells you what an event must look like. What it pays is your organization’s XP rules, which are set in the console. Your backend can read the current pricing withGET /v1/xp/event-definitions, so it doesn’t have to copy amounts out of the console.
The answer has one rule set per event type. A rule set has an optional default and an ordered list of cases. Cases are checked in the order returned, and the first case whose condition matches pays. If no case matches, the default pays, and with no default the set pays nothing. Exactly one member of a set pays per event.
Rules written outside the console’s sets appear under adds_on_top. Each one that matches adds its award to the set’s, so when that list isn’t empty the set is not the whole price. Retired rules and old versions are left out. A boost can still multiply the result, and the earning controls can still decline it.
Creating definitions needs a partner admin session; an org key can only read them.
API reference
List XP event definitions
GET /v1/xp/event-definitions authenticates with your organization API key as a bearer header (see Authentication). It takes no parameters and returns your organization’s active pricing.
200 OK:
An organization with no active rules returns
{ "rule_sets": [] }.