Skip to main content

Attendance Analytics API

Author: Josh, Lead Backend Team Last Updated: 2026-05-23 Version: v2.0

Base URL: https://api.nexgate.com/api/v1

Short Description: The Attendance Management API provides comprehensive attendee tracking and analytics for event organizers. Organizers can view overall attendance statistics with per-day and per-ticket-type breakdowns, browse a unified attendance list (present, absent, and partially attended in one view) with rich filtering, and drill into the full check-in history for any individual ticket. The system supports single-day and multi-day events. All status filtering, day filtering, buyer-type filtering, and search are handled through query parameters on a single list endpoint.

Hints:

  • Organizer Only: All endpoints restricted to the event organizer
  • Unified List: Present and absent tickets are in one list — filter with ?status=PRESENT|ABSENT|PARTIALLY_ATTENDED
  • Ticket-Based: Each row in the list is one ticket, not one person — one buyer can appear multiple times
  • Attendee Number: Each ticket has a unique attendeeNumber = bookingReference + "-" + ticketSeries (e.g. EVT-A3F4B21C-VIP-0042)
  • Buyer vs Attendee: buyer = who paid, attendee = who holds the ticket — can be different people
  • Buyer Types: SYSTEM_USER = bought online, AT_DOOR = name captured at door (no account)
  • Multi-Day Support: Per-day filtering via ?dayNumber=1 — optional, omit for overall view
  • Partial Attendance: PARTIALLY_ATTENDED = checked in some days but not all (multi-day events only)
  • Pagination: Default 20 per page

Response Structures

AttendanceSummaryResponse

Returned by GET /summary. Always covers the full event — no filtering.

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2026",
  "totalDays": 3,
  "eventSchedule": [
    { "dayNumber": 1, "dayName": "Day 1 - Opening",    "date": "2026-12-15" },
    { "dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2026-12-16" },
    { "dayNumber": 3, "dayName": "Day 3 - Closing",    "date": "2026-12-17" }
  ],
  "overallStats": {
    "totalTickets": 500,
    "present": 310,
    "partiallyAttended": 45,
    "absent": 145,
    "attendanceRate": 71.0,
    "byDay": [
      {
        "dayNumber": 1,
        "dayName": "Day 1 - Opening",
        "date": "2026-12-15",
        "totalTickets": 500,
        "checkedIn": 400,
        "absent": 100,
        "attendanceRate": 80.0,
        "status": "COMPLETED"
      },
      {
        "dayNumber": 2,
        "dayName": "Day 2 - Conference",
        "date": "2026-12-16",
        "totalTickets": 500,
        "checkedIn": 355,
        "absent": 145,
        "attendanceRate": 71.0,
        "status": "ONGOING"
      },
      {
        "dayNumber": 3,
        "dayName": "Day 3 - Closing",
        "date": "2026-12-17",
        "totalTickets": 500,
        "checkedIn": 0,
        "absent": 500,
        "attendanceRate": 0.0,
        "status": "UPCOMING"
      }
    ]
  },
  "byTicketType": [
    {
      "ticketTypeId": "uuid",
      "ticketTypeName": "VIP Pass",
      "totalSold": 100,
      "present": 75,
      "partiallyAttended": 10,
      "absent": 15,
      "attendanceRate": 85.0,
      "byDay": [
        {
          "dayNumber": 1,
          "dayName": "Day 1 - Opening",
          "checkedIn": 90,
          "absent": 10,
          "attendanceRate": 90.0
        }
      ]
    }
  ]
}

AttendanceListResponse

Returned by GET /list. Reflects applied filters. summary always covers the filtered scope. partiallyAttended only appears when no dayNumber filter is set on a multi-day event.

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2026",

  "appliedFilters": {
    "ticketTypeId": "uuid",
    "ticketTypeName": "VIP Pass",
    "status": "ALL",
    "dayNumber": 1,
    "dayName": "Day 1 - Opening",
    "buyerType": "ALL",
    "search": null
  },

  "summary": {
    "totalTickets": 100,
    "present": 90,
    "absent": 10,
    "partiallyAttended": null,
    "attendanceRate": 90.0
  },

  "attendees": [
    {
      "ticketInstanceId": "uuid",
      "attendeeNumber": "EVT-A3F4B21C-VIP-0042",
      "ticketTypeId": "uuid",
      "ticketType": "VIP Pass",
      "ticketSeries": "VIP-0042",

      "attendeeName": "John Doe",
      "attendeeEmail": "john@example.com",
      "attendeePhone": "+255712345678",

      "buyer": {
        "buyerName": "Jane Doe",
        "buyerEmail": "jane@example.com",
        "buyerId": "uuid",
        "buyerType": "SYSTEM_USER"
      },

      "bookingReference": "EVT-A3F4B21C",
      "pricePaid": 150.00,

      "attendanceStatus": "PRESENT",
      "checkInTime": "2026-12-15T09:15:00+03:00",
      "checkInLocation": "Main Gate",
      "checkedInBy": "Scanner Operator 1",
      "scannerId": "uuid-string"
    },
    {
      "ticketInstanceId": "uuid-2",
      "attendeeNumber": "EVT-B5D2E12F-GENE-0101",
      "ticketType": "General Admission",
      "ticketSeries": "GENE-0101",

      "attendeeName": "Ali Hassan",
      "attendeeEmail": null,
      "attendeePhone": null,

      "buyer": {
        "buyerName": "Ali Hassan",
        "buyerEmail": null,
        "buyerId": null,
        "buyerType": "AT_DOOR"
      },

      "bookingReference": "EVT-B5D2E12F",
      "pricePaid": 50.00,

      "attendanceStatus": "ABSENT",
      "checkInTime": null,
      "checkInLocation": null,
      "checkedInBy": null,
      "scannerId": null
    }
  ],

  "pagination": {
    "currentPage": 0,
    "pageSize": 20,
    "totalPages": 5,
    "totalElements": 100,
    "hasNext": true,
    "hasPrevious": false
  }
}

AttendeeDetailResponse

Returned by GET /attendees/{ticketInstanceId}. Full per-day check-in history for one ticket.

{
  "ticketInstanceId": "uuid",
  "attendeeNumber": "EVT-A3F4B21C-VIP-0042",
  "attendeeName": "John Doe",
  "attendeeEmail": "john@example.com",
  "attendeePhone": "+255712345678",
  "ticketType": "VIP Pass",
  "ticketSeries": "VIP-0042",
  "bookingReference": "EVT-A3F4B21C",
  "pricePaid": 150.00,
  "overallStatus": "PARTIALLY_ATTENDED",
  "daysAttended": 2,
  "daysTotal": 3,
  "checkInsByDay": [
    {
      "dayNumber": 1,
      "dayName": "Day 1 - Opening",
      "dayDate": "2026-12-15",
      "status": "CHECKED_IN",
      "checkInTime": "2026-12-15T09:15:00+03:00",
      "checkInLocation": "Main Gate",
      "checkedInBy": "Scanner Operator 1",
      "scannerId": "uuid-string"
    },
    {
      "dayNumber": 2,
      "dayName": "Day 2 - Conference",
      "dayDate": "2026-12-16",
      "status": "NOT_CHECKED_IN",
      "checkInTime": null,
      "checkInLocation": null,
      "checkedInBy": null,
      "scannerId": null
    },
    {
      "dayNumber": 3,
      "dayName": "Day 3 - Closing",
      "dayDate": "2026-12-17",
      "status": "UPCOMING",
      "checkInTime": null,
      "checkInLocation": null,
      "checkedInBy": null,
      "scannerId": null
    }
  ]
}

Endpoints

1. Get Attendance Summary

Endpoint: GET /attendance/{eventId}/summary

Access: 🔒 Event Organizer Only

Path Parameters:

Parameter Type Required Description
eventId string (UUID) Yes Event identifier

Success Response: Returns AttendanceSummaryResponse

Success Response Message: "Attendance summary retrieved"

Behavior:

  • Validates organizer owns the event
  • Returns overall attendance metrics across all tickets
  • Breaks down by day (all events, including single-day)
  • Breaks down by ticket type with per-day stats
  • present = checked in for all days, partiallyAttended = some days, absent = none
  • attendanceRate includes both present and partiallyAttended tickets

Errors:

  • 403 FORBIDDEN: Not event organizer
  • 404 NOT_FOUND: Event not found

2. Get Attendance List

Endpoint: GET /attendance/{eventId}/list

Access: 🔒 Event Organizer Only

Path Parameters:

Parameter Type Required Description
eventId string (UUID) Yes Event identifier

Query Parameters:

Parameter Type Required Description
status enum No PRESENT, ABSENT, PARTIALLY_ATTENDED — omit for all
dayNumber integer No Filter for a specific day (1, 2, 3...) — omit for overall view
ticketTypeId string (UUID) No Filter by ticket type
buyerType enum No SYSTEM_USER, AT_DOOR — omit for all
search string No Search by attendee name, email, or phone (partial, case-insensitive)
page integer No Page number (0-indexed, default: 0)
size integer No Items per page (default: 20)

Success Response: Returns AttendanceListResponse with pagination

Success Response Message: "Attendance list retrieved"

Behavior:

  • Returns one row per ticket (not per person)
  • One buyer can appear multiple times if they purchased multiple tickets
  • attendeeNumber = bookingReference + "-" + ticketSeries (unique per ticket)
  • When dayNumber is set: attendanceStatus is PRESENT or ABSENT for that specific day
  • When dayNumber is omitted: attendanceStatus reflects overall across all days (PARTIALLY_ATTENDED possible)
  • summary block always reflects the current filter scope (not the full event)
  • partiallyAttended in summary is null when a dayNumber filter is active

Validation:

  • dayNumber must be within valid range for the event (1 to totalDays)
  • ticketTypeId must belong to the event

Errors:

  • 403 FORBIDDEN: Not event organizer
  • 404 NOT_FOUND: Event not found / invalid day number / ticket type not found

3. Get Attendee Detail

Endpoint: GET /attendance/{eventId}/attendees/{ticketInstanceId}

Access: 🔒 Event Organizer Only

Path Parameters:

Parameter Type Required Description
eventId string (UUID) Yes Event identifier
ticketInstanceId string (UUID) Yes Individual ticket instance identifier

Success Response: Returns AttendeeDetailResponse

Success Response Message: "Attendee detail retrieved"

Behavior:

  • Returns the full check-in history for one specific ticket across all event days
  • overallStatus computed from all days
  • checkInsByDay lists every event day with its status and check-in details
  • Day status reflects whether the day has passed, is today, or is upcoming

Errors:

  • 403 FORBIDDEN: Not event organizer
  • 404 NOT_FOUND: Event or ticket not found

Key Concepts

Attendee Number

Every ticket has a unique attendeeNumber formatted as:

{bookingReference}-{ticketSeries}

Example: EVT-A3F4B21C-VIP-0042

Use this as the primary human-readable identifier for a ticket on the attendance list — safer than ticketInstanceId (UUID) for display and manual lookup.


Buyer vs Attendee

These are two different people on the same ticket:

Field Who
attendeeName / attendeeEmail The person who will attend (holds the ticket)
buyer.buyerName / buyer.buyerEmail The person who paid

Example: Jane buys 3 VIP tickets. She is the buyer on all 3. The attendees are John, Ali, and herself.

One buyer can appear multiple times in the list — once per ticket they purchased.


Buyer Types

Value Meaning
SYSTEM_USER Bought online — has a platform account, buyerId is populated
AT_DOOR Walk-in sale — only name captured at door, buyerEmail and buyerId are null

Attendance Status

Value Meaning When
PRESENT Checked in With dayNumber: checked in that day. Without: checked in all days
ABSENT Not checked in With dayNumber: not that day. Without: never checked in
PARTIALLY_ATTENDED Checked in some days but not all Only possible without dayNumber filter, on multi-day events

Day Status (in detail view)

Value Meaning
CHECKED_IN Scanned on that day
NOT_CHECKED_IN Day has passed, was not scanned
UPCOMING Day has not happened yet

Attendance Rate Calculation

attendanceRate = ((present + partiallyAttended) / totalTickets) × 100

Rounded to 2 decimal places.

Per-day rate (in summary byDay):

rate = (checkedIn for that day / totalTickets) × 100

Multi-Day Event Logic

Day Filter Behaviour

dayNumber Status scope
Provided (e.g. ?dayNumber=2) Status = did this ticket check in on Day 2? (PRESENT or ABSENT only)
Omitted Status = overall across all days (PRESENT, ABSENT, or PARTIALLY_ATTENDED)

Day number validation:

Event has 3 days → dayNumber must be 1, 2, or 3
Request dayNumber=5 → 404: "Invalid day 5. Valid: 1-3"

Single-day events: dayNumber is always optional. Omitting it gives the same result as ?dayNumber=1.


Use Cases

Dashboard Overview

GET /attendance/{eventId}/summary

Use: top-of-page stats card
Shows: overall rate, per-day breakdown, per-ticket-type breakdown

Full Attendance Table

GET /attendance/{eventId}/list

Use: default organizer table view — all tickets, all statuses

Who Attended Today (Day 2)

GET /attendance/{eventId}/list?dayNumber=2&status=PRESENT

Shows: tickets checked in on Day 2

Who Missed Today

GET /attendance/{eventId}/list?dayNumber=2&status=ABSENT

Shows: tickets that didn't check in on Day 2 (regardless of other days)

At-Door Walk-Ins Only

GET /attendance/{eventId}/list?buyerType=AT_DOOR

Shows: all tickets sold at the door

Partially Attended (Multi-Day)

GET /attendance/{eventId}/list?status=PARTIALLY_ATTENDED

Shows: tickets that attended some days but not all
Requires: no dayNumber filter

Search by Name

GET /attendance/{eventId}/list?search=john

Matches: attendee name, email, or phone containing "john"

Individual Ticket History

GET /attendance/{eventId}/attendees/{ticketInstanceId}

Shows: full per-day check-in record for one ticket

Quick Reference

HTTP Status Codes

  • 200 OK: Success
  • 401 UNAUTHORIZED: Not authenticated
  • 403 FORBIDDEN: Not the event organizer
  • 404 NOT_FOUND: Event / ticket / day number not found

Attendance Status Values

  • PRESENT — attended (all days if no day filter)
  • ABSENT — did not attend
  • PARTIALLY_ATTENDED — attended some days (multi-day, no day filter)

Day Status Values

  • COMPLETED — day has passed
  • ONGOING — day is today
  • UPCOMING — day has not started

Buyer Type Values

  • SYSTEM_USER — online purchase, has platform account
  • AT_DOOR — walk-in, name only, no account

Default Pagination

  • page: 0 (first page)
  • size: 20 items per page