Pullupfor developers

Moments & Highlights

Moments are user-generated, event-captured highlights — short video and photo records with engagement metadata. Fetch them programmatically for display on external surfaces. Every read scopes to your creator account: foreign, unpublished, or deleted moments are indistinguishable 404s, never partial data.

Prerequisites

  • An API key carrying the moments.read scope (granted via the moments product as moments.*). Secret keys travel in P-Sec-Key; publishable keys in P-Pub-Key or ?pPubKey=, subject to origin allowlisting on browser calls. Key model in Authentication.
  • The event_id you own (and optionally division and highlight ids) to read from.

Read by event

GET /v1/events/{eventId}/highlights returns every published highlight for one of your events, newest first (created_at descending, id descending).

GET /v1/events/{eventId}/highlights

Parameters
eventIdstringPathRequired—

Your event id. Another creator’s id fails the whole request with 404 Event not found — ownership is checked before any data loads.

limitinteger 1–100QueryOptional25

Items per page. Out-of-range values fail validation with 400 — never clamped. The effective value echoes back in pagination.limit.

cursorstringQueryOptionalfirst page

Base64url page cursor from the previous response’s next_cursor. Omit for the first page. Empty, invalid, or negative cursors restart at the first page with no error. Never construct cursors — the encoding is internal and may change.

No body
——BodyOptional—

None. Reads carry no body.

curl "https://api.pulluptoplay.com/v1/events/0hspDRQ-W3Bws3e3/highlights?limit=10&pPubKey=$PULLUP_PUBLIC_KEY"
{
  "status": "success",
  "code": 200,
  "message": "Moments retrieved",
  "data": {
    "moments": [
      {
        "id": "EQvwGMnOXd3oevo-3BzyTd",
        "event_id": "0hspDRQ-W3Bws3e3",
        "event_division_id": null,
        "description": "Final check",
        "location": { "place": "Lagos" },
        "mentions": [],

















































Every nullable above arrives explicitly as null — absent asset dimensions, locations, and creator avatars are never omitted. Asset metrics count engagement; metadata.creator identifies the posting account. Parse assets defensively: entries may carry kind values beyond VIDEO as capture modes evolve.

Read by division

GET /v1/events/{eventId}/divisions/{eventDivisionId}/highlights narrows the same read to one division. The division must belong to the event and the event must be yours — anything else is 404.

GET /v1/events/{eventId}/divisions/{eventDivisionId}/highlights

Parameters
eventIdstringPathRequired—

Your event id. Foreign ids fail with 404 Event not found.

eventDivisionIdstringPathRequired—

A division of that event. A division outside your events fails the whole request with 404 Event division not found.

limitinteger 1–100QueryOptional25

Items per page. Out-of-range values fail validation with 400 — never clamped.

cursorstringQueryOptionalfirst page

Base64url page cursor. Empty, invalid, or negative values restart at the first page with no error.

No body
——BodyOptional—

None. Reads carry no body.

curl "https://api.pulluptoplay.com/v1/events/0hspDRQ-W3Bws3e3/divisions/vip-gate/highlights?pPubKey=$PULLUP_PUBLIC_KEY"
{
  "status": "success",
  "code": 200,
  "message": "Moments retrieved",
  "data": {
    "moments": [
      {
        "id": "EQvwGMnOXd3oevo-3BzyTd",
        "event_id": "0hspDRQ-W3Bws3e3",
        "event_division_id": "vip-gate",
        "description": "Final check",
        "location": { "place": "Lagos" },
        "mentions": [],









Full parameter contracts for every read on this page also live in Highlights API.

Read one highlight

GET /v1/highlights/{highlightId} returns one published highlight you own — or 404 when it is missing, unpublished, deleted, or someone else’s. The three cases are deliberately indistinguishable: never branch access decisions on response differences that do not exist.

GET /v1/highlights/{highlightId}

Parameters
highlightIdstringPathRequired—

The highlight id. Anything unresolvable returns 404 Moment not found.

No query or body
——Query / BodyOptional—

None. Authentication travels in P-Sec-Key, P-Pub-Key, or ?pPubKey= — the examples below use ?pPubKey= so no custom headers are needed.

curl "https://api.pulluptoplay.com/v1/highlights/EQvwGMnOXd3oevo-3BzyTd?pPubKey=$PULLUP_PUBLIC_KEY"
{
  "status": "success",
  "code": 200,
  "message": "Moment retrieved",
  "data": {
    "moment": {
      "id": "EQvwGMnOXd3oevo-3BzyTd",
      "event_id": "0hspDRQ-W3Bws3e3",
      "event_division_id": null,
      "description": "Final check",
      "location": null,
      "mentions": [],
      "assets": [],
      "metrics"























Note the wrapper: single reads return { moment }, lists return { moments, pagination }. Code that handles both shapes must branch on the key — a list parser fed a single read silently yields nothing.

Global feed and bulk queries

Standard reads cover one event or division at a time. GET /v1/events/highlights spans every event you own with no id required — ownership derives from the key. POST /v1/events/highlights/query filters explicit id lists; POST exists because the filter body is too expressive for a query string, and nothing is created, updated, or deleted.

GET /v1/events/highlights

Global feed parameters
limitinteger 1–100QueryOptional25

Items per page. Out-of-range values fail validation with 400 — never clamped.

cursorstringQueryOptionalfirst page

Base64url page cursor. Empty, invalid, or negative values restart at the first page with no error.

No path or body
——Path / BodyOptional—

None. No event id is needed — the key selects the scope.

curl "https://api.pulluptoplay.com/v1/events/highlights?limit=20&pPubKey=$PULLUP_PUBLIC_KEY"
{
  "status": "success",
  "code": 200,
  "message": "Moments retrieved",
  "data": {
    "moments": [
      {
        "id": "EQvwGMnOXd3oevo-3BzyTd",
        "event_id": "0hspDRQ-W3Bws3e3",
        "event_division_id": null,
        "description": "Final check",
        "location": { "place": "Lagos" },
        "mentions": [],









An account with no owned events returns "moments": [], never an error.

POST /v1/events/highlights/query

Bulk query parameters
event_idsstring[]BodyOptional[]

Up to 50 event ids. Empty scopes to every event you own. Entries trim; blank entries and lists over 50 fail validation with 400.

event_division_idsstring[]BodyOptional[]

Up to 50 division ids with identical trimming and length rules. Each must belong to an event you own.

limitinteger 1–100BodyOptional25

Items per page. Out-of-range values fail validation with 400 — never clamped.

cursorstringBodyOptionalfirst page

Base64url page cursor. Empty, invalid, or negative values restart at the first page with no error.

No path or query
——Path / QueryOptional—

None. Filters travel in the JSON body.

Ownership is enforced per id: any event you do not own — or any division outside your events — fails the whole request with 404. Validate id lists before sending, or catch the 404 and drop the offending id.

curl -X POST https://api.pulluptoplay.com/v1/events/highlights/query \
  -H "Content-Type: application/json" \
  -H "P-Sec-Key: $PULLUP_SECRET_KEY" \
  -d '{
    "event_ids": ["0hspDRQ-W3Bws3e3", "hw4EzFN5oCZQLDYJ"],
    "event_division_ids": ["7f1c9a2e-4b6d-4e8f-9a0b-1c2d3e4f5a6b"],
    "limit": 20
  }'
{
  "status": "success",
  "code": 200,
  "message": "Moments retrieved",
  "data": {
    "moments": [
      {
        "id": "EQvwGMnOXd3oevo-3BzyTd",
        "event_id": "hw4EzFN5oCZQLDYJ",
        "event_division_id": "7f1c9a2e-4b6d-4e8f-9a0b-1c2d3e4f5a6b",
        "description": "Final check",
        "location": null,
        "mentions": [],
        "assets"



















Paginate every list

Every list endpoint paginates identically — event reads, division reads, the global feed, and bulk queries. Page numbers would break under live data (publishes between page 1 and page 2 shift every subsequent page, duplicating and skipping items), so cursors pin your position in an ordered stream instead. New publishes append at the head; your walk through older items stays stable regardless of mid-read arrivals.

Cursors are base64url-encoded row offsets. Never construct, parse, or store them beyond passing the next one back — the encoding is internal and may change.

Pagination parameters
limitinteger 1–100Query (GET) / Body (bulk POST)Optional25

Items requested per page. Out-of-range values fail validation with 400 — rejected, never clamped. Echoed back in pagination.limit.

cursorstringQuery (GET) / Body (bulk POST)Optionalfirst page

Opaque cursor from the previous page. Omit for the first page. Empty, unparseable, or negative values restart at offset zero with no error.

Every page returns the same envelope. has_more decides continuation; next_cursor is null on the final page:

{
  "status": "success",
  "code": 200,
  "message": "Moments retrieved",
  "data": {
    "moments": [],
    "pagination": {
      "limit": 25,
      "has_more"



const all: unknown[] = [];
let cursor: string | undefined;
 
do {
  const params = new URLSearchParams({
    pPubKey: process.env.PULLUP_PUBLIC_KEY!,
    limit: "100",
    ...(cursor ? { cursor } : {}),
  });
  const response = await fetch(
    `https://api.pulluptoplay.com/v1/events/highlights?${










Next steps