Events API


Events Management API

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2026-07-09 Version: v1.3

Base URL: https://your-api-domain.com/api/v1/e-events

Short Description: The Event Core API provides full lifecycle management for events on the NextGate platform — from creating drafts and configuring schedules, to publishing, managing live events, and discovery. It is used by event organizers to build, publish, and manage events step-by-step, and by all authenticated users to browse, search, and filter published events.

Hints:


Event Creation User Journey

  [Organizer]
       │
       │  POST /draft
       ▼
  ┌─────────────┐
  │  BASIC INFO │  title, category, format, description, bannerFileId (optional)
  └──────┬──────┘
         │  PATCH /drafts/{id}/basic-info  (optional update)
         │
         │  PATCH /draft/{id}/schedule
         ▼
  ┌──────────────┐
  │   SCHEDULE   │  days[ date, startTime, endTime ], timezone
  └──────┬───────┘
         │
         │  PATCH /draft/{id}/location
         ▼
  ┌──────────────────┐
  │ LOCATION DETAILS │  venue (IN_PERSON/HYBRID) or virtualDetails (ONLINE/HYBRID)
  └──────┬───────────┘         or skip entirely (TBA format)
         │
         │  (Ticket creation via Tickets API — required before publish)
         ▼
  ┌──────────────┐
  │   TICKETS    │  at least one active ticket required
  └──────┬───────┘
         │
         │  (Optional enrichment)
         │  PATCH /drafts/{id}/highlights
         │  PATCH /drafts/{id}/faqs
         │  PATCH /drafts/{id}/lineup
         │  PATCH /drafts/{id}/agenda
         │  POST  /draft/{id}/products/{productId}
         │  POST  /draft/{id}/shops/{shopId}
         │
         │  PATCH /{eventId}/publish
         ▼
  ┌───────────────┐
  │   PUBLISHED   │  RSA keys generated, ctaLabel auto-derived if not set,
  │               │  category count incremented, event visible in public feed
  └───────────────┘

Event Status Flow

  DRAFT ◄──────────────────────► PUBLISHED
    │           (unpublish           │
    │        if 0 tickets sold)      │  (unpublish — only if no tickets sold)
    │                                │
    │  (discardDraft)                │
    ▼                                ▼
  [deleted]                     CANCELLED ◄─── (cancel from any non-terminal status)
                                     │
                              [terminal state]

  PUBLISHED ──► (system / scheduled job) ──► HAPPENING ──► COMPLETED

Status Rules:


Standard Response Format

All API responses follow a consistent structure using the Globe Response Builder pattern.

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": { }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, CREATED, BAD_REQUEST, etc.)
message string Human-readable description of the result
action_time string ISO 8601 timestamp of when the response was generated
data object / string Response payload on success; error detail on failure

Shared Response Object Definitions

The following objects are returned by multiple endpoints. They are defined once here and referenced throughout.

A. EventResponse (Full Event Object)

Returned by all draft and event management endpoints.

Field Type Description
id UUID Unique event identifier
title string Event title
slug string URL-friendly identifier, auto-generated
description string Full event description
category.categoryId UUID Category identifier
category.categoryName string Category display name
category.categorySlug string Category slug
eventFormat string IN_PERSON, ONLINE, HYBRID, or TBA
eventVisibility string PUBLIC, PRIVATE, or UNLISTED
status string DRAFT, PUBLISHED, HAPPENING, CANCELLED, COMPLETED
schedule.startDateTime ZonedDateTime Event start (ISO 8601 with offset)
schedule.endDateTime ZonedDateTime Event end (ISO 8601 with offset)
schedule.timezone string IANA timezone string (e.g., Africa/Dar_es_Salaam)
schedule.days[] array Day-level schedule entries (see EventDayInfo below)
venue.name string Venue name (IN_PERSON / HYBRID only)
venue.address string Venue address
venue.coordinates.latitude string Latitude decimal string
venue.coordinates.longitude string Longitude decimal string
virtualDetails.meetingLink string Meeting URL (ONLINE / HYBRID only). Stripped to null on the public GET /{eventId} endpoint — only returned to the organizer via draft/my-events endpoints
virtualDetails.meetingId string Platform meeting ID (same stripping rule as above)
virtualDetails.passcode string Meeting passcode (same stripping rule as above)
bannerMedia object | null Resolved banner media object. See EventBannerMedia definition below. null if no banner was ever set
highlights[] array See HighlightEntry definition below
faqs[] array See FaqEntry definition below
lineup[] array See LineupEntry definition below
agenda[] array See AgendaDay definition below
linkedProducts[] array { productId, productName, productSlug }
linkedShops[] array { shopId, shopName, shopSlug }
tickets[] array See TicketSummaryInfo definition below
organizer object See OrganizerInfo definition below
ctaLabel string CTA button label (e.g., "Get Tickets", "Register for Free"). Auto-derived from ticket pricing at publish if not explicitly set
hasApplicantForm boolean Whether an applicant form is configured for this event
applicantForm.displayTime string When the form is shown: BEFORE_CHECKOUT or AFTER_CHECKOUT
applicantForm.isRequired boolean Whether form submission is required for online attendees
applicantForm.applyToAtDoor boolean Whether the form also applies to at-door check-in
currentStage string Current event creation stage
completedStages[] array List of completed stage names
completionPercentage integer 0–100 completion percentage
canPublish boolean Whether all required stages are completed
createdAt ZonedDateTime Creation timestamp
updatedAt ZonedDateTime Last update timestamp
createdBy string Username of creator
updatedBy string Username of last editor

EventDayInfo

Field Type Description
id UUID Day entity ID
date string Date in YYYY-MM-DD format
startTime string Start time in HH:mm:ss
endTime string End time in HH:mm:ss
description string Optional day description
dayOrder integer Display order (1-based)

HighlightEntry

Field Type Description
type string AGE_RESTRICTION, CHECK_IN_TIME, PARKING, DRESS_CODE, FOOD_DRINKS, ACCESSIBILITY, REFUND_POLICY, WHAT_TO_BRING, PROHIBITED_ITEMS, WEATHER_INFO, CUSTOM
title string Display title for this highlight
value string Short value (e.g., "18+")
description string Longer explanation

FaqEntry

Field Type Description
question string The FAQ question
answer string The answer
order integer Display order

LineupEntry

Field Type Description
entryType string PLATFORM_USER or CUSTOM
userId UUID Platform user ID (only when entryType=PLATFORM_USER)
name string Display name (auto-enriched from user profile when PLATFORM_USER)
role string HEADLINER, PERFORMER, SPEAKER, DJ, HOST, PANELIST, MODERATOR, GUEST
title string Professional title (e.g., "Lead Vocalist")
bio string Short biography
profileMedia object | null Resolved profile picture — only populated for PLATFORM_USER entries, auto-refreshed from the linked account on every read. Same lean shape as EventBannerPrimaryMedia (fileId, status, thumbUrl, mimeType). CUSTOM entries have no image/media at all — there is no way to attach a photo to a custom lineup entry
performanceDay integer Which day number of the event they perform
performanceTime string Time of performance
order integer Display order

AgendaDay

Field Type Description
dayNumber integer Day number (1-based)
date string Date in YYYY-MM-DD format
sessions[] array See AgendaSession definition below

AgendaSession

Field Type Description
startTime string Session start time (HH:mm)
endTime string Session end time (HH:mm)
title string Session title
description string Session description
type string GENERAL, PERFORMANCE, CEREMONY, PANEL, WORKSHOP, NETWORKING, MEAL, BREAK
location string Sub-location within the event venue
presenterType string PLATFORM_USER or CUSTOM
presenterId UUID Platform user ID (when presenterType=PLATFORM_USER)
presenterName string Presenter name (auto-enriched from user profile when PLATFORM_USER)
presenterTitle string Professional title
presenterBio string Short biography
presenterMedia object | null Resolved profile picture — only populated for PLATFORM_USER presenters, auto-refreshed from the linked account on every read. Same lean shape as EventBannerPrimaryMedia (fileId, status, thumbUrl, mimeType). CUSTOM presenters have no image/media at all — there is no way to attach a photo to a custom presenter

TicketSummaryInfo

Field Type Description
id UUID Ticket type ID
name string Ticket type name (e.g., "VIP", "General Admission")
price BigDecimal Ticket price (0.00 for free)
salesChannel string Ticket sales channel enum value
ticketPricingType string FREE, PAID, or DONATION
totalTickets integer Total slots allocated
ticketsSold integer Number sold
ticketsAvailable integer Remaining slots
ticketsReserved integer Slots currently held in an active (unpaid) checkout
isSoldOut boolean True when available = 0
attendanceMode string Ticket attendance mode enum value
status string Ticket status enum value
isOnSale boolean Whether the ticket is currently within its sales window
saleStatusMessage string Human-readable explanation of the current sale status

EventBannerMedia (full media object — on EventResponse)

Resolved banner media, returned on the full event detail response. Built from the bannerFileId supplied at creation/update.

Field Type Description
fileId UUID FileThunder file identifier
status string File processing status enum (e.g., READY, PROCESSING, FAILED)
variants map<string,string> All resolved size/format variants, keyed by variant name (e.g., "thumb", "medium", "original") → signed/public URL
mimeType string MIME type of the uploaded file

EventBannerPrimaryMedia (lean media object — on EventSummaryResponse)

Lightweight single-URL counterpart used in list/search results — only the "thumb" variant is resolved (falls back to the first available variant if no thumb exists).

Field Type Description
fileId UUID FileThunder file identifier
status string File processing status enum
thumbUrl string Single resolved thumbnail URL
mimeType string MIME type of the uploaded file

OrganizerInfo (on EventResponse only)

Field Type Description
id UUID Organizer account ID
name string Organizer full name (firstName + " " + lastName)
username string Organizer system username
profileMedia object | null Organizer's profile picture, lean shape — same fields as EventBannerPrimaryMedia (fileId, status, thumbUrl, mimeType)

Note: EventSummaryResponse (list/search results) does not use this nested object — it still exposes flat organizerId / organizerName / organizerUsername fields with no profile picture at all. This is a known inconsistency versus the full EventResponse shape (which was updated to include profileMedia); the summary shape has not been migrated yet.


B. EventSummaryResponse (Lightweight List Object)

Returned by all paginated list and search endpoints.

Field Type Description
id UUID Event ID
title string Event title
slug string URL slug
shortDescription string First 150 characters of description
categoryId UUID Category ID
categoryName string Category display name
eventFormat string IN_PERSON, ONLINE, HYBRID, TBA
eventVisibility string PUBLIC, PRIVATE, UNLISTED
status string Event status
startDateTime ZonedDateTime Start date/time with offset
endDateTime ZonedDateTime End date/time with offset
timezone string IANA timezone
locationSummary string Human-readable location (e.g., "Dar es Salaam, TZ", "Online Event", "Location To Be Announced")
bannerMedia object | null Lean resolved banner media — see EventBannerPrimaryMedia above. null if no banner was ever set
hasApplicantForm boolean Whether an applicant form is configured
ctaLabel string CTA button label
pricing.pricingType string FREE, PAID, DONATION, or MIXED — frontend uses this directly for the display label
pricing.minPrice BigDecimal | null Lowest available ticket price (PAID/FREE tickets only, excludes DONATION). null if pricing is FREE-only or DONATION-only
pricing.maxPrice BigDecimal | null Highest available ticket price (same exclusion rule as minPrice)
pricing.hasFreeTickets boolean True when at least one active FREE ticket type exists
pricing.hasPaidTickets boolean True when at least one active PAID ticket type exists
pricing.hasDonationTickets boolean True when at least one active DONATION ticket type exists
organizerId UUID Organizer ID (flat field — no nested media/profile picture on this lightweight object, see note above)
organizerName string Organizer full name
organizerUsername string Organizer username
stats.totalTickets integer Sum of all ticket slots
stats.ticketsSold integer Total tickets sold
stats.ticketsAvailable integer Remaining tickets
stats.isSoldOut boolean True when no tickets remain
stats.attendeeCount integer Same as ticketsSold
createdAt ZonedDateTime Creation timestamp

C. Standard Paginated Response Wrapper

All list endpoints return data inside a Spring Page wrapper.

{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10
    },
    "totalElements": 42,
    "totalPages": 5,
    "last": false,
    "first": true,
    "empty": false
  }
}

Note: All list endpoints accept page (1-based, default 1) and size (default 10) as query parameters. Spring internally converts to 0-based before querying.


D. EventStatusResponse (Lightweight Status Object)

Returned by the publish/unpublish/cancel transition endpoints. These no longer return the full EventResponse — only the minimal fields needed to confirm the transition.

Field Type Description
id UUID Event ID
slug string Event URL slug
status string New status after the transition (PUBLISHED, DRAFT, or CANCELLED)

If you need the full updated event (media, tickets, organizer, etc.) after a publish/unpublish/cancel call, follow up with GET /{eventId} (Endpoint 25) or GET /drafts/{draftId} (Endpoint 3).


HTTP Method Badge Standards


Standard Error Types

Application-Level Exceptions (400–499)

Code Name When it occurs
400 BAD_REQUEST Invalid request data, already published event, duplicate product/shop, unpublish blocked due to ticket sales
401 UNAUTHORIZED Missing, expired, or malformed Bearer token
403 FORBIDDEN Authenticated but not the event organizer, or accessing a draft belonging to another user
404 NOT_FOUND Event, category, product, or shop ID not found
422 UNPROCESSABLE_ENTITY Bean validation failures with per-field detail

Server-Level Exceptions (500+)

Code Name When it occurs
500 INTERNAL_SERVER_ERROR RSA key generation failure, unexpected runtime error

Shared Error Response Examples

All endpoints may return these error shapes. Each endpoint section references them rather than repeating the full JSON.

401 — Unauthorized:

{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2025-02-17T10:30:45",
  "data": "Token has expired"
}

403 — Forbidden:

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Access denied: Insufficient permissions",
  "action_time": "2025-02-17T10:30:45",
  "data": "Access denied: Insufficient permissions"
}

404 — Not Found:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Event not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "action_time": "2025-02-17T10:30:45",
  "data": "Event not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

422 — Validation Error:

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "title": "size must be between 3 and 200",
    "categoryId": "must not be null",
    "eventFormat": "must not be null"
  }
}

Endpoints


1. Create Event Draft

Purpose: Creates a new event in DRAFT status as the first step of the event creation workflow. Marks BASIC_INFO stage as completed automatically.

Endpoint: POST /api/v1/e-events/drafts

Access Level: 🔒 Protected (Any authenticated user — becomes event organizer)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Request JSON Sample:

{
  "title": "Dar es Salaam Jazz Festival 2025",
  "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "eventFormat": "IN_PERSON",
  "eventVisibility": "PUBLIC",
  "description": "An annual celebration of jazz and live music at the heart of the city.",
  "bannerFileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
}

Request Body Parameters:

Parameter Type Required Description Validation
title string Yes Event title Min: 3, Max: 200 characters
categoryId UUID Yes ID of an active event category Must exist and be active
eventFormat string Yes Event format Enum: IN_PERSON, ONLINE, HYBRID, TBA
eventVisibility string No Who can see the event Enum: PUBLIC, PRIVATE, UNLISTED. Defaults to PUBLIC
description string No Full event description Max: 5000 characters
bannerFileId UUID No ID of a file already uploaded via FileThunder to use as the banner Must reference an existing uploaded file

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Event draft created",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "title": "Dar es Salaam Jazz Festival 2025",
    "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
    "description": "An annual celebration of jazz...",
    "category": {
      "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "categoryName": "Music & Concerts",
      "categorySlug": "music-concerts"
    },
    "eventFormat": "IN_PERSON",
    "eventVisibility": "PUBLIC",
    "status": "DRAFT",
    "currentStage": "BASIC_INFO",
    "completedStages": ["BASIC_INFO"],
    "completionPercentage": 25,
    "canPublish": false,
    "schedule": null,
    "venue": null,
    "virtualDetails": null,
    "bannerMedia": {
      "fileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
      "status": "READY",
      "variants": {
        "thumb": "https://cdn.example.com/ft/6b1a2c3d/thumb.jpg",
        "medium": "https://cdn.example.com/ft/6b1a2c3d/medium.jpg",
        "original": "https://cdn.example.com/ft/6b1a2c3d/original.jpg"
      },
      "mimeType": "image/jpeg"
    },
    "highlights": null,
    "faqs": null,
    "lineup": null,
    "agenda": null,
    "linkedProducts": [],
    "linkedShops": [],
    "tickets": [],
    "organizer": {
      "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "name": "Amina Hassan",
      "username": "amina.hassan",
      "profileMedia": {
        "fileId": "9f8e7d6c-5b4a-3c2d-1e0f-a1b2c3d4e5f6",
        "status": "READY",
        "thumbUrl": "https://cdn.example.com/ft/9f8e7d6c/thumb.jpg",
        "mimeType": "image/jpeg"
      }
    },
    "ctaLabel": null,
    "hasApplicantForm": false,
    "applicantForm": null,
    "createdAt": "2025-02-17T10:30:45+03:00",
    "updatedAt": null,
    "createdBy": "amina.hassan",
    "updatedBy": null
  }
}

Success Response Fields: See Shared Response Object A — EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token — see Shared Error: 401
404 Category ID not found
422 Missing required fields (title, categoryId, eventFormat) — see Shared Error: 422

2. Get My Drafts

Purpose: Returns a paginated list of all DRAFT events owned by the authenticated organizer.

Endpoint: GET /api/v1/e-events/drafts

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Drafts retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "title": "Dar es Salaam Jazz Festival 2025",
        "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
        "shortDescription": "An annual celebration of jazz and live music...",
        "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
        "categoryName": "Music & Concerts",
        "eventFormat": "IN_PERSON",
        "eventVisibility": "PUBLIC",
        "status": "DRAFT",
        "startDateTime": null,
        "endDateTime": null,
        "timezone": null,
        "locationSummary": null,
        "bannerMedia": {
          "fileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
          "status": "READY",
          "thumbUrl": "https://cdn.example.com/ft/6b1a2c3d/thumb.jpg",
          "mimeType": "image/jpeg"
        },
        "hasApplicantForm": false,
        "ctaLabel": null,
        "pricing": {
          "pricingType": "FREE",
          "minPrice": null,
          "maxPrice": null,
          "hasFreeTickets": false,
          "hasPaidTickets": false,
          "hasDonationTickets": false
        },
        "organizerId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
        "organizerName": "Amina Hassan",
        "organizerUsername": "amina.hassan",
        "stats": { "totalTickets": 0, "ticketsSold": 0, "ticketsAvailable": 0, "isSoldOut": false },
        "createdAt": "2025-02-17T10:30:45+03:00"
      }
    ],
    "totalElements": 3,
    "totalPages": 1,
    "first": true,
    "last": true,
    "empty": false
  }
}

Success Response Fields: data.content[] contains EventSummaryResponse objects. Pagination fields follow Standard Paginated Response Wrapper.

Possible Error Responses:

Status Scenario
401 No or expired token — see Shared Error: 401

3. Get Draft by ID

Purpose: Retrieves the full detail of a specific draft. Only the draft's organizer can access it.

Endpoint: GET /api/v1/e-events/drafts/{draftId}

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The ID of the draft event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Draft retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Authenticated but not the owner of this draft
404 Draft not found with given ID

4. Discard Draft

Purpose: Permanently deletes a draft event and all its associated day schedules, linked products, and linked shops. This action is irreversible.

Endpoint: DELETE /api/v1/e-events/drafts/{draftId}

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The ID of the draft to discard Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Draft discarded",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Authenticated but not the owner of this draft
404 Draft not found with given ID

5. Update Draft — Basic Info

Purpose: Updates the basic information of a draft. All fields are optional — only provided fields are updated. Advances currentStage to SCHEDULE upon completion.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/basic-info

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "title": "Dar es Salaam Jazz Festival 2025 — Updated",
  "description": "The biggest jazz event in East Africa returns for its 10th edition with over 30 artists.",
  "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "eventVisibility": "PUBLIC",
  "eventFormat": "IN_PERSON",
  "ctaLabel": "Get Tickets",
  "bannerFileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
}

Request Body Parameters:

Parameter Type Required Description Validation
title string No Updated event title Min: 3, Max: 200 characters
description string No Updated description Min: 15, Max: 5000 characters
categoryId UUID No New category Must exist and be active
eventVisibility string No Visibility change Enum: PUBLIC, PRIVATE, UNLISTED
eventFormat string No Format change Enum: IN_PERSON, ONLINE, HYBRID, TBA
ctaLabel string No CTA button label override Max: 50 characters. If omitted, existing value is kept; auto-derived at publish if never set
bannerFileId UUID No ID of a file already uploaded via FileThunder to replace the banner Must reference an existing uploaded file

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Basic info updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. Note that completedStages will now include "BASIC_INFO" and currentStage will be "SCHEDULE".

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft or category not found
422 Validation failure on provided fields

6. Update Draft — Schedule

Purpose: Sets the event's day-by-day schedule. Supports multi-day events. Each day must have a unique date in chronological order. Advances currentStage to LOCATION_DETAILS. The overall startDateTime and endDateTime on the event are derived automatically from the first and last day.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/schedule

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "timezone": "Africa/Dar_es_Salaam",
  "days": [
    {
      "date": "2025-07-18",
      "startTime": "18:00:00",
      "endTime": "23:00:00",
      "description": "Opening Night",
      "dayOrder": 1
    },
    {
      "date": "2025-07-19",
      "startTime": "16:00:00",
      "endTime": "23:59:00",
      "description": "Main Concert Day",
      "dayOrder": 2
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
timezone string No IANA timezone identifier Must be a valid IANA zone ID (e.g., Africa/Dar_es_Salaam). Defaults to UTC
days array Yes List of event days Min 1 day required
days[].date string Yes Date of the day YYYY-MM-DD format; must not be in the past
days[].startTime string Yes Day start time HH:mm:ss format
days[].endTime string Yes Day end time HH:mm:ss; must be after startTime
days[].description string No Optional day description
days[].dayOrder integer No Display order Defaults to position in array if omitted

Notes:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Schedule updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. schedule.days will be populated, and schedule.startDateTime / schedule.endDateTime will be set from the first and last day.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft not found
422 Days out of order, duplicate dates, date in the past, missing time fields

7. Update Draft — Location

Purpose: Sets the physical venue and/or virtual meeting details for the event. Required fields depend on eventFormat. For TBA format, this endpoint can be called but no fields are required — the stage is automatically marked complete. Advances currentStage to TICKETS.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/location

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample (IN_PERSON):

{
  "venue": {
    "name": "Mlimani City Arena",
    "address": "Sam Nujoma Road, Dar es Salaam",
    "coordinates": {
      "latitude": -6.7724,
      "longitude": 39.2083
    }
  }
}

Request JSON Sample (ONLINE):

{
  "virtualDetails": {
    "meetingLink": "https://zoom.us/j/123456789",
    "meetingId": "123 456 789",
    "passcode": "jazz2025"
  }
}

Request Body Parameters:

Parameter Type Required Description Validation
venue object Conditional Required when format is IN_PERSON or HYBRID
venue.name string Yes (if venue) Venue name Max: 200 characters
venue.address string No Full address Max: 500 characters
venue.coordinates.latitude BigDecimal No GPS latitude
venue.coordinates.longitude BigDecimal No GPS longitude
virtualDetails object Conditional Required when format is ONLINE or HYBRID
virtualDetails.meetingLink string Yes (if virtualDetails) Meeting URL Max: 500 characters
virtualDetails.meetingId string No Platform meeting ID Max: 100 characters
virtualDetails.passcode string No Meeting passcode Max: 100 characters

Format-based rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Location updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. completedStages will include "LOCATION_DETAILS" when requirements are met and currentStage will advance to "TICKETS".

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft not found
422 Missing required venue/virtual fields for the event format

8. Update Draft — Highlights

Purpose: Replaces the full list of event highlights (key attendee-facing info such as age restriction, dress code, parking, etc.). Sending an empty array clears all highlights.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/highlights

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "highlights": [
    {
      "type": "AGE_RESTRICTION",
      "title": "Age Limit",
      "value": "18+",
      "description": "This event is strictly for adults aged 18 and above."
    },
    {
      "type": "DRESS_CODE",
      "title": "Dress Code",
      "value": "Smart Casual",
      "description": "No sportswear or flip flops allowed."
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
highlights array Yes Full list of highlights Empty array is valid (clears existing)
highlights[].type string Yes Highlight category Enum: AGE_RESTRICTION, CHECK_IN_TIME, PARKING, DRESS_CODE, FOOD_DRINKS, ACCESSIBILITY, REFUND_POLICY, WHAT_TO_BRING, PROHIBITED_ITEMS, WEATHER_INFO, CUSTOM
highlights[].title string Yes Display title
highlights[].value string No Short value summary
highlights[].description string No Longer explanation

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Highlights updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.highlights will reflect the new list.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft not found

9. Update Draft — FAQs

Purpose: Replaces the full list of frequently asked questions for the event. Sending an empty array clears all FAQs.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/faqs

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "faqs": [
    {
      "question": "Is this event suitable for children?",
      "answer": "No. This event is strictly 18+ only.",
      "order": 1
    },
    {
      "question": "Is parking available?",
      "answer": "Yes, free parking is available on site.",
      "order": 2
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
faqs array Yes Full list of FAQs Empty array is valid (clears existing)
faqs[].question string Yes FAQ question text
faqs[].answer string Yes FAQ answer text
faqs[].order integer No Display order

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "FAQs updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.faqs will reflect the new list.

Possible Error Responses: Same as Endpoint 8 (401, 403, 404).


10. Update Draft — Lineup

Purpose: Replaces the full event lineup. Supports both platform users (whose profile data is auto-enriched) and custom entries for external performers or speakers. Sending an empty array clears the lineup.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/lineup

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "lineup": [
    {
      "entryType": "PLATFORM_USER",
      "userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "role": "HEADLINER",
      "performanceDay": 1,
      "performanceTime": "21:00",
      "order": 1
    },
    {
      "entryType": "CUSTOM",
      "name": "DJ Afrobeat",
      "role": "DJ",
      "title": "International Guest DJ",
      "bio": "Award-winning DJ from Lagos with 10 years of experience.",
      "performanceDay": 2,
      "performanceTime": "22:00",
      "order": 2
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
lineup array Yes Full lineup list Empty array is valid
lineup[].entryType string Yes Entry source Enum: PLATFORM_USER, CUSTOM
lineup[].userId UUID Conditional Platform user ID Required when entryType=PLATFORM_USER; must exist in the system
lineup[].name string Conditional Display name Required when entryType=CUSTOM; auto-set from user profile when PLATFORM_USER
lineup[].role string No Lineup role Enum: HEADLINER, PERFORMER, SPEAKER, DJ, HOST, PANELIST, MODERATOR, GUEST
lineup[].title string No Professional title
lineup[].bio string No Biography text Auto-set from user profile when PLATFORM_USER
lineup[].performanceDay integer No Day number of performance
lineup[].performanceTime string No Performance start time (HH:mm)
lineup[].order integer No Display order

Auto-enrichment: When entryType=PLATFORM_USER, the system automatically fetches and populates name, bio, and profileMedia (resolved profile picture object) from the user's profile. These are also refreshed on every GET of the event. There is no image field on this requestCUSTOM entries cannot have a photo at all; only PLATFORM_USER entries get one, sourced from the linked account.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Lineup updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.lineup will contain enriched entries.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft not found, or a userId in lineup does not exist

11. Update Draft — Agenda

Purpose: Replaces the full event agenda organized by day and session. Supports both platform users and custom entries as session presenters. Sending an empty array clears the agenda.

Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/agenda

Access Level: 🔒 Protected (Organizer — own drafts only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
draftId UUID Yes The draft to update Must be a valid UUID

Request JSON Sample:

{
  "agenda": [
    {
      "dayNumber": 1,
      "date": "2025-07-18",
      "sessions": [
        {
          "startTime": "18:00",
          "endTime": "19:00",
          "title": "Welcome & Opening Ceremony",
          "type": "CEREMONY",
          "location": "Main Stage",
          "presenterType": "PLATFORM_USER",
          "presenterId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
        },
        {
          "startTime": "20:00",
          "endTime": "22:00",
          "title": "Headliner Performance",
          "type": "PERFORMANCE",
          "presenterType": "CUSTOM",
          "presenterName": "The Sauti Sol Band",
          "presenterTitle": "Headline Act",
          "presenterBio": "East Africa's most celebrated group."
        }
      ]
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
agenda array Yes Full agenda list (by day) Empty array is valid
agenda[].dayNumber integer Yes Day number (1-based)
agenda[].date string No Date string (YYYY-MM-DD)
agenda[].sessions array Yes Sessions for this day
sessions[].startTime string Yes Session start (HH:mm)
sessions[].endTime string Yes Session end (HH:mm)
sessions[].title string Yes Session name
sessions[].description string No Session description
sessions[].type string No Session type Enum: GENERAL, PERFORMANCE, CEREMONY, PANEL, WORKSHOP, NETWORKING, MEAL, BREAK
sessions[].location string No Sub-location within venue
sessions[].presenterType string No Presenter source Enum: PLATFORM_USER, CUSTOM
sessions[].presenterId UUID Conditional Platform user ID Required when presenterType=PLATFORM_USER
sessions[].presenterName string Conditional Presenter name Required when presenterType=CUSTOM; auto-set when PLATFORM_USER
sessions[].presenterTitle string No Professional title Auto-enriched for platform users
sessions[].presenterBio string No Biography Auto-enriched for platform users

There is no presenterImage field on this requestCUSTOM presenters cannot have a photo at all; only PLATFORM_USER presenters get one (presenterMedia, resolved profile picture object), sourced from the linked account.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Agenda updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.agenda will contain enriched sessions.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the draft owner
404 Draft not found, or a presenterId does not exist

12. Attach Product to Event

Endpoint: POST /api/v1/e-events/{eventId}/products/{productId}

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID
productId UUID Yes The product to attach Must exist and have status ACTIVE

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product attached",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.linkedProducts will include the newly attached product.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event or product not found
400 Product is not active, already attached, or event is not in DRAFT/PUBLISHED status

13. Remove Product from Event

Purpose: Detaches a previously linked product from the event. Allowed on both DRAFT and PUBLISHED events.

Endpoint: DELETE /api/v1/e-events/{eventId}/products/{productId}

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID
productId UUID Yes The product to remove Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product removed",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, or product not currently attached

14. Attach Shop to Event

Endpoint: POST /api/v1/e-events/{eventId}/shops/{shopId}

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID
shopId UUID Yes The shop to attach Must exist and have status ACTIVE

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop attached",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.linkedShops will include the newly attached shop.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event or shop not found
400 Shop is not active, already attached, or event is not in DRAFT/PUBLISHED status

15. Remove Shop from Event

Purpose: Detaches a previously linked shop from the event. Allowed on both DRAFT and PUBLISHED events.

Endpoint: DELETE /api/v1/e-events/{eventId}/shops/{shopId}

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID
shopId UUID Yes The shop to remove Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop removed",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse.

Possible Error Responses: Same as Endpoint 13 (401, 403, 404).


16. Publish Event

Purpose: Transitions a draft event to PUBLISHED status. This is the final step of the creation workflow. The system validates all required stages are complete, performs a duplicate event check against existing published events, generates an RSA key pair used for secure ticket QR code signing, and auto-derives the ctaLabel from ticket pricing if the organizer has not set one.

Endpoint: PATCH /api/v1/e-events/{eventId}/publish

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to publish Must be a valid UUID

Pre-publish checklist (all must pass or the request is rejected with 422):

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event published successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
    "status": "PUBLISHED"
  }
}

Success Response Fields: data is an EventStatusResponsenot the full EventResponse.

Error Response Sample (duplicate detected):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "This event appears to be a duplicate of 'Dar es Salaam Jazz Night' by user123. Please make the title, date, or location more distinct.",
  "action_time": "2025-02-17T10:30:45",
  "data": "This event appears to be a duplicate..."
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is already published, or duplicate event detected
422 One or more required stages are incomplete, or no active tickets exist
500 RSA key generation failed

17. Unpublish Event

Purpose: Reverts a PUBLISHED event back to DRAFT status. Only allowed if zero tickets have been sold across all ticket types. If tickets have been sold, the organizer must cancel the event instead.

Endpoint: PATCH /api/v1/e-events/{eventId}/unpublish

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to unpublish Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event unpublished successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
    "status": "DRAFT"
  }
}

Success Response Fields: data is an EventStatusResponsenot the full EventResponse.

Error Response Sample (tickets sold):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot unpublish: tickets have already been sold. Please cancel the event instead.",
  "action_time": "2025-02-17T10:30:45",
  "data": "Cannot unpublish: tickets have already been sold. Please cancel the event instead."
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is not currently PUBLISHED, or tickets have been sold

18. Cancel Event

Purpose: Cancels an event. Can be triggered from any non-terminal status (DRAFT, PUBLISHED, HAPPENING). This action is irreversible. If the event was published and tickets were sold, a bulk refund process is triggered.

Endpoint: PATCH /api/v1/e-events/{eventId}/cancel

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to cancel Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event cancelled successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
    "status": "CANCELLED"
  }
}

Success Response Fields: data is an EventStatusResponsenot the full EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is already CANCELLED or COMPLETED

19. Update Published Event — Basic Info

Purpose: Updates the description and/or CTA label of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly. Banner updates are no longer part of this endpoint — use Endpoint 19a — Update Published Event — Banner instead.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/basic-info

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Request JSON Sample:

{
  "description": "Updated: The biggest jazz event in East Africa. Now featuring 35 artists across 3 stages.",
  "ctaLabel": "Get Tickets Now"
}

Request Body Parameters:

Parameter Type Required Description Validation
description string No Updated event description Min: 15, Max: 5000 characters
ctaLabel string No CTA button label override Max: 50 characters. If provided and non-blank → saved as-is. If omitted or null → re-derived from current ticket pricing

Excluded fields: title, categoryId, and eventFormat are not present on this request at all (too breaking for a published event whose slug is already shared). eventVisibility and banner media are also not accepted here — banner updates now go through Endpoint 19a as a separate call.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event info updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is not in PUBLISHED status
422 Validation failure on provided fields

19a. Update Published Event — Banner

Purpose: Replaces the banner image on a published event. Split out from the basic-info endpoint since the banner is a FileThunder-managed upload, not a plain string field.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/banner

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation
bannerFileId UUID Yes ID of a file already uploaded via FileThunder to use as the new banner Must reference an existing uploaded file

Note: Unlike every other update endpoint in this API, this one takes its input as a query parameter, not a JSON request body — there is no request body for this call.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event banner updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.bannerMedia reflects the newly resolved banner.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, or bannerFileId does not reference an existing file
400 Event is not in PUBLISHED status

20. Update Published Event — Highlights

Purpose: Replaces the full list of highlights for a published event. Behavior is identical to the draft equivalent — sending an empty array clears all highlights.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/highlights

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Request Body: Same format as Endpoint 8 — Update Draft Highlights.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Highlights updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is not PUBLISHED

21. Update Published Event — FAQs

Purpose: Replaces the full list of FAQs for a published event. Sending an empty array clears all FAQs.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/faqs

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Request Body: Same format as Endpoint 9 — Update Draft FAQs.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "FAQs updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Possible Error Responses: Same as Endpoint 20 (401, 403, 404, 400).


22. Update Published Event — Lineup

Purpose: Replaces the full event lineup on a published event. Supports the same platform user enrichment as the draft equivalent.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/lineup

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Request Body: Same format as Endpoint 10 — Update Draft Lineup.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Lineup updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, or a userId in lineup does not exist
400 Event is not PUBLISHED

22a. Update Check-in Settings

Purpose: Configures the check-in window for attendee entry — how early before start, and how late after start, attendees are allowed to check in.

Endpoint: PATCH /api/v1/e-events/{eventId}/checkin-settings

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to update Must be a valid UUID

Request JSON Sample:

{
  "checkInBeforeValue": 2,
  "checkInBeforeUnit": "HOURS",
  "checkInAfterValue": 30,
  "checkInAfterUnit": "MINUTES"
}

Request Body Parameters:

Parameter Type Required Description Validation
checkInBeforeValue integer Yes How long before start check-in opens Min: 1
checkInBeforeUnit string Yes Unit for checkInBeforeValue Enum: MINUTES, HOURS, DAYS
checkInAfterValue integer Yes How long after start check-in remains open Min: 0
checkInAfterUnit string Yes Unit for checkInAfterValue Enum: MINUTES, HOURS, DAYS

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Check-in settings updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
422 Missing/invalid check-in value or unit

23. Update Published Event — Agenda

Purpose: Replaces the full event agenda on a published event. Supports the same platform user enrichment as the draft equivalent.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/agenda

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event to update Must be a valid UUID

Request Body: Same format as Endpoint 11 — Update Draft Agenda.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Agenda updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, or a presenterId does not exist
400 Event is not PUBLISHED

24. Reveal Location (TBA → Actual)

Purpose: Reveals the actual location for an event that was originally published with eventFormat=TBA. The new format cannot be TBA. Required fields depend on the new format chosen.

Endpoint: PATCH /api/v1/e-events/{eventId}/published/reveal-location

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published TBA event Must be a valid UUID

Request JSON Sample (revealing as IN_PERSON):

{
  "eventFormat": "IN_PERSON",
  "venue": {
    "name": "Mlimani City Arena",
    "address": "Sam Nujoma Road, Dar es Salaam",
    "coordinates": {
      "latitude": -6.7724,
      "longitude": 39.2083
    }
  }
}

Request JSON Sample (revealing as ONLINE):

{
  "eventFormat": "ONLINE",
  "virtualDetails": {
    "meetingLink": "https://zoom.us/j/123456789",
    "meetingId": "123 456 789",
    "passcode": "jazz2025"
  }
}

Request Body Parameters:

Parameter Type Required Description Validation
eventFormat string Yes The actual event format Enum: IN_PERSON, ONLINE, HYBRIDcannot be TBA
venue object Conditional Physical venue details Required when new format is IN_PERSON or HYBRID
venue.name string Yes (if venue) Venue name Max: 200 characters
venue.address string No Full address Max: 500 characters
venue.coordinates.latitude BigDecimal No GPS latitude
venue.coordinates.longitude BigDecimal No GPS longitude
virtualDetails object Conditional Virtual meeting details Required when new format is ONLINE or HYBRID
virtualDetails.meetingLink string Yes (if virtualDetails) Meeting URL Max: 500 characters
virtualDetails.meetingId string No Platform meeting ID Max: 100 characters
virtualDetails.passcode string No Meeting passcode Max: 100 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Location revealed successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found
400 Event is not PUBLISHED, current format is not TBA, or new format is TBA
422 Missing required venue/virtual fields for the chosen format

25. Get Event by ID

Purpose: Retrieves full event details. Published events are publicly accessible without authentication. Draft events can only be viewed by their organizer.

Endpoint: GET /api/v1/e-events/{eventId}

Access Level: 🌐 Public (for PUBLISHED events) | 🔒 Protected (for DRAFT events — organizer only)

Authentication: Bearer Token (required only for DRAFT access)

Request Headers:

Header Type Required Description
Authorization string Conditional Required when accessing a DRAFT event

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to retrieve Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.virtualDetails is always null on this endpoint — it uses the public-response mapping, which strips meeting link/ID/passcode even when the requester is the event's own organizer. While the event is still DRAFT, virtual meeting details can be read via GET /drafts/{draftId} (Endpoint 3); there is currently no endpoint that returns virtualDetails for an already-PUBLISHED event, even to its organizer.

Possible Error Responses:

Status Scenario
403 Attempting to access a DRAFT event without being its organizer
404 Event not found or is soft-deleted

26. Get My Events

Purpose: Returns a paginated list of all events (any status) created by the authenticated organizer.

Endpoint: GET /api/v1/e-events/my-events

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 12,
    "totalPages": 2
  }
}

Success Response Fields: data.content[] contains EventSummaryResponse objects. Pagination follows Standard Paginated Response Wrapper.

Possible Error Responses: 401 — see Shared Error: 401.


27. Get My Events by Status

Purpose: Returns a paginated list of the authenticated organizer's events filtered by a specific status.

Endpoint: GET /api/v1/e-events/my-events/status/{status}

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
status string Yes Event status filter Enum: DRAFT, PUBLISHED, HAPPENING, CANCELLED, COMPLETED

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 5,
    "totalPages": 1
  }
}

Success Response Fields: Same as Endpoint 26.

Possible Error Responses:

Status Scenario
401 No or expired token
400 Invalid status enum value

28. Get Events Feed

Purpose: Returns a paginated list of all published events for the public discovery feed. Results are ordered by creation date descending. This endpoint will eventually incorporate a recommendation algorithm.

Endpoint: GET /api/v1/e-events/events-feed

Access Level: 🌐 Public

Authentication: None required

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Events feed retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects — PUBLISHED only ]" ],
    "totalElements": 87,
    "totalPages": 9
  }
}

Success Response Fields: data.content[] contains EventSummaryResponse objects (PUBLISHED status only).

Possible Error Responses: None expected (no authentication required).


28a. Get Past Events

Purpose: Returns a paginated list of PUBLISHED events whose end date/time has already passed (e.g., for an "Events you missed" / past-events archive view). Note that this is distinct from COMPLETED status — an event moves to COMPLETED via a separate scheduled job, so there can be a lag where a past-dated event still shows here as PUBLISHED.

Endpoint: GET /api/v1/e-events/events-feed/past

Access Level: 🌐 Public

Authentication: None required

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Past events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects — PUBLISHED, end date in the past ]" ],
    "totalElements": 23,
    "totalPages": 3
  }
}

Success Response Fields: data.content[] contains EventSummaryResponse objects.

Possible Error Responses: None expected (no authentication required).


29. Search Events

Purpose: Full-text search across published event titles. Results are ordered by start date ascending.

Endpoint: GET /api/v1/e-events/search

Access Level: 🌐 Public

Authentication: None required

Query Parameters:

Parameter Type Required Description Validation Default
query string Yes Search keyword(s) Non-empty string
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Search results retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 4,
    "totalPages": 1
  }
}

Success Response Fields: data.content[] contains EventSummaryResponse objects.

Possible Error Responses: None expected for normal queries (empty results return an empty content array, not a 404).


30. Filter Events by Date Range

Purpose: Returns published events whose schedule overlaps with the provided date range. An event is included if it starts before endDate AND ends after startDate (overlap logic, not exact range match).

Endpoint: GET /api/v1/e-events/filter/date

Access Level: 🌐 Public

Authentication: None required

Query Parameters:

Parameter Type Required Description Validation Default
startDate ZonedDateTime Yes Range start ISO 8601 with offset (e.g., 2025-07-01T00:00:00+03:00)
endDate ZonedDateTime Yes Range end ISO 8601 with offset; must be after startDate
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Filtered events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 7,
    "totalPages": 1
  }
}

Possible Error Responses:

Status Scenario
400 startDate is after endDate

31. Search and Filter Events (Combined)

Purpose: Combines keyword search with optional date range filtering in a single call. All parameters are optional — calling with no parameters is equivalent to getting the full published feed.

Endpoint: GET /api/v1/e-events/filter

Access Level: 🌐 Public

Authentication: None required

Query Parameters:

Parameter Type Required Description Validation Default
query string No Title keyword search
startDate ZonedDateTime No Date range start ISO 8601 with offset
endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Possible Error Responses:

Status Scenario
400 startDate is after endDate (when both are provided)

32. Search My Events (Organizer)

Purpose: Allows an organizer to search and filter within their own events across all statuses. Supports keyword, status filter, and date range simultaneously.

Endpoint: GET /api/v1/e-events/my-events/search

Access Level: 🔒 Protected (Organizer — own events only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Query Parameters:

Parameter Type Required Description Validation Default
query string No Keyword search on title
status string No Filter by status Enum: DRAFT, PUBLISHED, HAPPENING, CANCELLED, COMPLETED
startDate ZonedDateTime No Date range start ISO 8601 with offset
endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided
page integer No Page number (1-based) Min: 1 1
size integer No Items per page Min: 1 10

Possible Error Responses:

Status Scenario
401 No or expired token
400 startDate is after endDate

33. Get Event OG Meta by Slug

Purpose: Returns lightweight Open Graph metadata for a published event, for use by link-preview crawlers (social media unfurling, messaging apps) when someone shares an event URL.

Endpoint: GET /api/v1/e-events/slug/{slug}/og-meta

Access Level: 🌐 Public

Authentication: None required

Path Parameters:

Parameter Type Required Description Validation
slug string Yes The event's URL slug Must match an existing event

⚠️ Non-standard response shape: Unlike every other endpoint in this API, this one returns the EventOGMetaResponse object directly as the HTTP body — it is not wrapped in the standard { success, httpStatus, message, action_time, data } envelope described at the top of this document. Build OG-meta consumers accordingly.

Success Response JSON Sample:

{
  "title": "Dar es Salaam Jazz Festival 2025",
  "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "description": "An annual celebration of jazz and live music at the heart of the city.",
  "banner": "https://cdn.example.com/ft/6b1a2c3d/medium.jpg",
  "startDateTime": "2025-07-18T18:00:00+03:00",
  "locationSummary": "Dar es Salaam, TZ"
}

Success Response Fields:

Field Type Description
title string Event title
eventId UUID Event ID
description string Full event description
banner string | null Resolved banner image URL (single string, not the full bannerMedia object)
startDateTime ZonedDateTime Event start date/time with offset
locationSummary string Human-readable location summary

Possible Error Responses:

Status Scenario
404 No event found with the given slug

Data Format Standards

Concern Standard
Timestamps ISO 8601 with timezone offset: 2025-07-18T18:00:00+03:00
Dates YYYY-MM-DD format: 2025-07-18
Times HH:mm:ss (24-hour): 18:00:00
IDs UUID v4: 3fa85f64-5717-4562-b3fc-2c963f66afa6
Pagination 1-based page query parameter, Spring Page wrapper in response
Prices BigDecimal with 2 decimal places; 0.00 for free
Enums Uppercase strings as defined (e.g., IN_PERSON, PUBLISHED)

Ticket Management API

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2026-02-20 Version: v1.2

Base URL: https://your-api-domain.com/api/v1/e-events/tickets

Short Description: The Ticket Management API allows event organizers to define and manage ticket types for their events on NextGate. Organizers can create multiple ticket tiers (e.g. VIP, Early Bird, General Admission), control pricing, capacity, sales periods, visibility, and sales channels. Tickets are scoped to a specific event and follow the event's lifecycle from DRAFT through to PUBLISHED.

Hints:


User Journey

  [Organizer creates event in DRAFT status]
                    |
                    | (Event must be DRAFT or PUBLISHED for ticket work)
                    v
  . . . . . . . . . . . . . . . . . . . . . . .
  .                                             .
  .   TICKET SETUP PHASE (DRAFT or PUBLISHED)  .
  .                                             .
  .   [Create ticket types]                     .
  .      |-- General Admission (PAID)           .
  .      |-- VIP Pass (PAID)                    .
  .      |-- Student Discount (PAID)            .
  .      |-- Free Entry (FREE)                  .
  .      '-- Support the Artist (DONATION)      .
  .                                             .
  . . . . . . . . . . . . . . . . . . . . . . .
                    |
                    v
  [Review all ticket types via Get All Tickets]
                    |
                    v
  . . . . . . . . . . . . . . . . . . . . . . .
  .                                             .
  .   ADJUSTMENTS PHASE (DRAFT only)            .
  .                                             .
  .   Need to fix details?                      .
  .      --> Update Ticket (name, price, etc.)  .
  .                                             .
  .   Wrong capacity?                           .
  .      --> Update Ticket Capacity             .
  .                                             .
  .   Ticket no longer needed?                  .
  .      --> Delete Ticket (only if 0 sold)     .
  .                                             .
  . . . . . . . . . . . . . . . . . . . . . . .
                    |
                    v
  [Event published — tickets go live for buyers]
                    |
                    v
  . . . . . . . . . . . . . . . . . . . . . . .
  .                                             .
  .   LIVE EVENT PHASE (PUBLISHED)              .
  .                                             .
  .   Need a new ticket tier?                   .
  .      --> Create Ticket (allowed on PUBLISHED).
  .                                             .
  .   Need to adjust visibility/status?         .
  .      --> Update Published Ticket            .
  .                                             .
  .   Need to shift the sales window?           .
  .      --> Update Sales Window                .
  .                                             .
  .   Ticket sells out?                         .
  .      --> System auto-sets SOLD_OUT          .
  .      --> Organizer can increase capacity    .
  .          to reactivate it                   .
  .                                             .
  .   Want to pause sales temporarily?          .
  .      --> Update Status to INACTIVE          .
  .                                             .
  .   Want to stop sales permanently?           .
  .      --> Update Status to CLOSED            .
  .                                             .
  . . . . . . . . . . . . . . . . . . . . . . .

Sales Window Rules

Ticket sales must respect three nested time windows:

Event:          [eventStartDateTime ─────────────── eventEndDateTime]
Registration:         [registrationOpensAt ──── registrationClosesAt]
Ticket Sales:              [salesStartDateTime ── salesEndDateTime]

Rules enforced:


Standard Response Format

All API responses follow a consistent structure:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {}
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.)
message string Human-readable description of the operation result
action_time string ISO 8601 timestamp of when the response was generated
data object/string Response payload on success; error detail on failure

HTTP Method Badge Standards


Enum Reference

TicketPricingType

Value Description
PAID Standard paid ticket. Price must be greater than 0.00
FREE Free entry. Price must be exactly 0.00
DONATION Supporter ticket. No fixed price — buyer freely enters their donation amount at checkout. Restricted to ONLINE_ONLY channel, max 1 per order and per user. price is null in responses

SalesChannel

Value Description
EVERYWHERE Available both online and at the door
ONLINE_ONLY Available for purchase online only
AT_DOOR_ONLY Available for purchase at the venue door only

AttendanceMode

Value Description
IN_PERSON Ticket grants physical entry to the venue
ONLINE Ticket grants access to the online/virtual stream

For IN_PERSON events, only IN_PERSON tickets are allowed. For ONLINE events, only ONLINE tickets are allowed. For HYBRID events, both are permitted and at least one of each is required before publishing.

TicketVisibility

Value Description
VISIBLE Always shown to the public
HIDDEN Never shown to buyers (organizer use only)
HIDDEN_WHEN_NOT_ON_SALE Only visible while the ticket is actively on sale
CUSTOM_SCHEDULE Shown only within a defined date/time window. Requires visibilityStartDate and visibilityEndDate

TicketStatus

Value Description
ACTIVE Ticket is live and available for purchase
INACTIVE Temporarily paused. Organizer can reactivate
CLOSED Permanently stopped. Cannot be reopened
SOLD_OUT System-managed. Set automatically when ticketsSold >= totalTickets. Reverts to ACTIVE if capacity is increased
DELETED Soft-deleted. Only possible if zero tickets were sold. Use the Delete endpoint — cannot be set via status update

Endpoints


1. Create Ticket

Purpose: Creates a new ticket type for a specific event. The event must be in DRAFT or PUBLISHED status. The authenticated user must be the event organizer.

Endpoint: POST {base_url}/{eventId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to add the ticket to Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
name string Yes Ticket name (e.g. "VIP Pass", "Early Bird") Min: 2, Max: 100 characters. Must be unique per event per attendance mode
description string No Optional description of what the ticket includes Max: 500 characters
price decimal Conditional Ticket price. Use 0.00 for FREE tickets. Omit or send 0.00 for DONATION tickets Min: 0.00. PAID → must be > 0.00. FREE → must be 0.00. DONATION → ignored
ticketPricingType string Yes Pricing model Enum: PAID, FREE, DONATION
salesChannel string Yes Where the ticket can be purchased. DONATION must use ONLINE_ONLY Enum: EVERYWHERE, ONLINE_ONLY, AT_DOOR_ONLY. Defaults to EVERYWHERE
totalQuantity integer Yes Total number of tickets available Min: 1, Max: 1,000,000
salesStartDateTime datetime No When sales open. Must be within registration window ISO 8601 ZonedDateTime. Cannot be before registrationOpensAt or after registrationClosesAt
salesEndDateTime datetime No When sales close. Must be within registration window ISO 8601 ZonedDateTime. Must be after salesStartDateTime with at least 30 minutes gap
minQuantityPerOrder integer No Minimum tickets per order Min: 1. Defaults to 1
maxQuantityPerOrder integer No Maximum tickets per order. DONATION tickets must be 1 Min: 1, Max: 100. Must be ≥ minQuantityPerOrder
maxQuantityPerUser integer No Maximum tickets a single user can purchase across all orders. DONATION tickets must be 1 Min: 1, Max: 1000. Must be ≥ maxQuantityPerOrder
visibility string Yes Controls whether the ticket is shown to buyers Enum: VISIBLE, HIDDEN, HIDDEN_WHEN_NOT_ON_SALE, CUSTOM_SCHEDULE. Defaults to VISIBLE
visibilityStartDate datetime No When the ticket becomes visible. Required if visibility is CUSTOM_SCHEDULE ISO 8601 ZonedDateTime
visibilityEndDate datetime No When the ticket stops being visible. Required if visibility is CUSTOM_SCHEDULE ISO 8601 ZonedDateTime. Must be after visibilityStartDate
attendanceMode string Yes Whether this ticket is for physical or online attendance Enum: IN_PERSON, ONLINE. Must match the event format
inclusiveItems array of strings No List of perks included with this ticket (e.g. "Free T-Shirt", "Meet & Greet") Max: 50 items. Each item: max 200 characters, cannot be blank

Request JSON Sample (PAID):

{
  "name": "VIP Pass",
  "description": "Full weekend access with backstage entry and a complimentary gift bag.",
  "price": 150.00,
  "ticketPricingType": "PAID",
  "salesChannel": "EVERYWHERE",
  "totalQuantity": 200,
  "salesStartDateTime": "2026-03-18T08:00:00+03:00",
  "salesEndDateTime": "2026-04-17T23:59:00+03:00",
  "minQuantityPerOrder": 1,
  "maxQuantityPerOrder": 4,
  "maxQuantityPerUser": 4,
  "visibility": "VISIBLE",
  "attendanceMode": "IN_PERSON",
  "inclusiveItems": [
    "Backstage access",
    "Complimentary gift bag",
    "Priority seating"
  ]
}

Request JSON Sample (DONATION):

{
  "name": "Support the Artist",
  "description": "Show your support — donate any amount you choose at checkout.",
  "price": 0.00,
  "ticketPricingType": "DONATION",
  "salesChannel": "ONLINE_ONLY",
  "totalQuantity": 500,
  "salesStartDateTime": "2026-03-18T08:00:00+03:00",
  "salesEndDateTime": "2026-04-17T23:59:00+03:00",
  "minQuantityPerOrder": 1,
  "maxQuantityPerOrder": 1,
  "maxQuantityPerUser": 1,
  "visibility": "VISIBLE",
  "attendanceMode": "IN_PERSON"
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Ticket created successfully",
  "action_time": "2025-02-18T10:30:45",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
    "name": "VIP Pass",
    "description": "Full weekend access with backstage entry and a complimentary gift bag.",
    "price": 150.00,
    "ticketPricingType": "PAID",
    "salesChannel": "EVERYWHERE",
    "totalTickets": 200,
    "ticketsSold": 0,
    "ticketsRemaining": 200,
    "ticketsAvailable": 200,
    "isSoldOut": false,
    "salesStartDateTime": "2026-03-18T05:00:00Z",
    "salesEndDateTime": "2026-04-17T20:59:00Z",
    "isOnSale": false,
    "minQuantityPerOrder": 1,
    "maxQuantityPerOrder": 4,
    "maxQuantityPerUser": 4,
    "visibility": "VISIBLE",
    "visibilityStartDate": null,
    "visibilityEndDate": null,
    "isCurrentlyVisible": true,
    "attendanceMode": "IN_PERSON",
    "inclusiveItems": [
      "Backstage access",
      "Complimentary gift bag",
      "Priority seating"
    ],
    "status": "ACTIVE",
    "saleStatusMessage": "On sale until Apr 17, 2026",
    "createdAt": "2025-02-18T10:30:45+03:00",
    "updatedAt": null,
    "createdBy": "john_organizer",
    "updatedBy": null
  }
}

Note on DONATION response: For DONATION tickets, the price field is null in the response. The buyer enters their own amount at checkout.

Success Response Fields:

Field Description
id Unique identifier for this ticket type
eventId The event this ticket belongs to
name Ticket name
description Ticket description
price Ticket price. null for DONATION tickets
ticketPricingType Pricing model: PAID, FREE, or DONATION
salesChannel Where the ticket can be purchased
totalTickets Total number of tickets created
ticketsSold Number of tickets purchased so far
ticketsRemaining totalTickets - ticketsSold
ticketsAvailable Same as ticketsRemaining
isSoldOut true if ticketsSold >= totalTickets
salesStartDateTime When ticket sales open
salesEndDateTime When ticket sales close
isOnSale true if ticket is ACTIVE and currently within the sales window
saleStatusMessage Human-readable message describing the current sale state (e.g. "On sale until Apr 17, 2026", "Sales start Mar 18, 2026", "Sales ended", "Sold out")
minQuantityPerOrder Minimum per order
maxQuantityPerOrder Maximum per order (null = no limit)
maxQuantityPerUser Maximum per user across all orders (null = no limit)
visibility Visibility setting
visibilityStartDate Start of custom visibility window
visibilityEndDate End of custom visibility window
isCurrentlyVisible Whether the ticket is currently visible to buyers
attendanceMode IN_PERSON or ONLINE
inclusiveItems List of perks included with the ticket
status Ticket status: ACTIVE, INACTIVE, CLOSED, SOLD_OUT, DELETED
createdAt Timestamp when the ticket was created
updatedAt Timestamp of last update (null if never updated)
createdBy Username of the organizer who created the ticket
updatedBy Username of last person who updated the ticket

Possible Error Responses:

Status Scenario
401 No or expired token
403 Authenticated user is not the event organizer
404 Event not found
400 Event is not in DRAFT or PUBLISHED status
400 Ticket name already exists for this event and attendance mode
422 Validation errors (missing required fields, invalid price, sales window outside registration window, gap less than 30 minutes, etc.)

Error Response Examples:

Event in invalid status (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Tickets can only be created for DRAFT or PUBLISHED events. Current status: CANCELLED",
  "action_time": "2025-02-18T10:30:45",
  "data": "Tickets can only be created for DRAFT or PUBLISHED events. Current status: CANCELLED"
}

Duplicate Ticket Name (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "A ticket with name 'VIP Pass' and attendance mode 'IN_PERSON' already exists for this event",
  "action_time": "2025-02-18T10:30:45",
  "data": "A ticket with name 'VIP Pass' and attendance mode 'IN_PERSON' already exists for this event"
}

2. Update Ticket

Purpose: Updates the full details of an existing ticket type. The event must still be in DRAFT status. All fields are optional — only the fields you send will be updated.

Endpoint: PUT {base_url}/{ticketId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
ticketId UUID Yes The ticket to update Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
name string No Updated ticket name Min: 2, Max: 100 characters. Must remain unique per event per attendance mode
description string No Updated description Max: 500 characters
price decimal No Updated price. Ignored for DONATION tickets Min: 0.00. Must be consistent with ticketPricingType
ticketPricingType string No Updated pricing model Enum: PAID, FREE, DONATION
salesChannel string No Updated sales channel. DONATION tickets must be ONLINE_ONLY Enum: EVERYWHERE, ONLINE_ONLY, AT_DOOR_ONLY
totalQuantity integer No Updated total capacity Min: 1, Max: 1,000,000
salesStartDateTime datetime No Updated sales open time ISO 8601 ZonedDateTime. Cannot be before registrationOpensAt
salesEndDateTime datetime No Updated sales close time ISO 8601 ZonedDateTime. At least 30 minutes after salesStartDateTime
minQuantityPerOrder integer No Updated minimum per order Min: 1
maxQuantityPerOrder integer No Updated maximum per order. DONATION must be 1 Min: 1, Max: 100
maxQuantityPerUser integer No Updated maximum per user. DONATION must be 1 Min: 1, Max: 1000
attendanceMode string No Updated attendance mode Enum: IN_PERSON, ONLINE. Must match event format rules
inclusiveItems array of strings No Full replacement list of inclusive perks Max: 50 items. Each: max 200 characters, cannot be blank
visibility string No Updated visibility Enum: VISIBLE, HIDDEN, HIDDEN_WHEN_NOT_ON_SALE, CUSTOM_SCHEDULE
visibilityStartDate datetime No Updated visibility start Required if changing to CUSTOM_SCHEDULE
visibilityEndDate datetime No Updated visibility end Required if changing to CUSTOM_SCHEDULE. Must be after start

Request JSON Sample:

{
  "name": "VIP Weekend Pass",
  "price": 175.00,
  "maxQuantityPerOrder": 2,
  "inclusiveItems": [
    "Backstage access",
    "Complimentary gift bag",
    "Priority seating",
    "Artist meet & greet"
  ]
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket updated successfully",
  "action_time": "2025-02-18T11:00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "VIP Weekend Pass",
    "price": 175.00,
    "maxQuantityPerOrder": 2,
    "status": "ACTIVE",
    "updatedAt": "2025-02-18T11:00:00+03:00",
    "updatedBy": "john_organizer"
  }
}

Success Response Fields: Same as Create Ticket response fields.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 Event is not in DRAFT status
400 Updated name conflicts with an existing ticket on the same event
422 Validation errors (invalid price, quantity inconsistencies, sales window violations, etc.)

3. Get All Tickets for Event

Purpose: Retrieves a lightweight summary list of all active (non-deleted) tickets for a given event, ordered by creation date ascending.

Endpoint: GET {base_url}/{eventId}

Access Level: 🌐 Public

Authentication: None required

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to retrieve tickets for Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Tickets retrieved successfully",
  "action_time": "2025-02-18T10:30:45",
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "General Admission",
      "price": 25.00,
      "ticketPricingType": "PAID",
      "salesChannel": "EVERYWHERE",
      "visibility": "VISIBLE",
      "totalTickets": 1000,
      "ticketsSold": 342,
      "ticketsAvailable": 658,
      "isSoldOut": false,
      "attendanceMode": "IN_PERSON",
      "status": "ACTIVE",
      "isOnSale": true
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "VIP Pass",
      "price": 150.00,
      "ticketPricingType": "PAID",
      "salesChannel": "EVERYWHERE",
      "visibility": "VISIBLE",
      "totalTickets": 200,
      "ticketsSold": 200,
      "ticketsAvailable": 0,
      "isSoldOut": true,
      "attendanceMode": "IN_PERSON",
      "status": "SOLD_OUT",
      "isOnSale": false
    }
  ]
}

Success Response Fields (per item):

Field Description
id Unique identifier for the ticket type
name Ticket name
price Ticket price. null for DONATION tickets
ticketPricingType Pricing model
salesChannel Where it can be purchased
visibility Visibility setting
totalTickets Total quantity created
ticketsSold Quantity sold so far
ticketsAvailable Quantity still available
isSoldOut Whether the ticket is sold out
attendanceMode IN_PERSON or ONLINE
status Current ticket status
isOnSale Whether the ticket is currently purchasable
saleStatusMessage Human-readable message describing the current sale state

Possible Error Responses:

Status Scenario
404 Event not found

4. Get Ticket by ID

Purpose: Retrieves the full details of a single ticket type by its ID.

Endpoint: GET {base_url}/{eventId}/{ticketId}

Access Level: 🌐 Public

Authentication: None required

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event the ticket belongs to Must be a valid UUID
ticketId UUID Yes The specific ticket to retrieve Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket retrieved successfully",
  "action_time": "2025-02-18T10:30:45",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
    "name": "VIP Pass",
    "description": "Full weekend access with backstage entry and a complimentary gift bag.",
    "price": 150.00,
    "ticketPricingType": "PAID",
    "salesChannel": "EVERYWHERE",
    "totalTickets": 200,
    "ticketsSold": 45,
    "ticketsRemaining": 155,
    "ticketsAvailable": 155,
    "isSoldOut": false,
    "salesStartDateTime": "2026-03-18T05:00:00Z",
    "salesEndDateTime": "2026-04-17T20:59:00Z",
    "isOnSale": true,
    "saleStatusMessage": "On sale until Apr 17, 2026",
    "minQuantityPerOrder": 1,
    "maxQuantityPerOrder": 4,
    "maxQuantityPerUser": 4,
    "visibility": "VISIBLE",
    "visibilityStartDate": null,
    "visibilityEndDate": null,
    "isCurrentlyVisible": true,
    "attendanceMode": "IN_PERSON",
    "inclusiveItems": [
      "Backstage access",
      "Complimentary gift bag",
      "Priority seating"
    ],
    "status": "ACTIVE",
    "createdAt": "2025-02-18T10:30:45+03:00",
    "updatedAt": "2025-02-18T11:00:00+03:00",
    "createdBy": "john_organizer",
    "updatedBy": "john_organizer"
  }
}

Success Response Fields: Same as Create Ticket response fields.

Possible Error Responses:

Status Scenario
404 Event not found or ticket not found

5. Update Ticket Capacity

Purpose: Updates the total quantity (capacity) of a ticket. Allowed on both DRAFT and PUBLISHED events. The new capacity must be greater than or equal to the number of tickets already sold.

Endpoint: PATCH {base_url}/{eventId}/{ticketId}/capacity

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
ticketId UUID Yes The ticket to update capacity for Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
newTotalQuantity integer Yes The new total capacity Min: 1, Max: 1,000,000. Must be ≥ ticketsSold

Request JSON Sample:

{
  "newTotalQuantity": 300
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket capacity updated successfully",
  "action_time": "2025-02-18T12:00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "VIP Pass",
    "totalTickets": 300,
    "ticketsSold": 200,
    "ticketsRemaining": 100,
    "ticketsAvailable": 100,
    "isSoldOut": false,
    "status": "ACTIVE",
    "updatedAt": "2025-02-18T12:00:00+03:00",
    "updatedBy": "john_organizer"
  }
}

Note: If a ticket was previously SOLD_OUT and the new capacity exceeds tickets sold, the status is automatically reset to ACTIVE.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 New capacity is less than the number of tickets already sold
422 newTotalQuantity is missing or below minimum

Capacity Below Sold Count (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot reduce capacity to 100 because 200 tickets have already been sold",
  "action_time": "2025-02-18T12:00:00",
  "data": "Cannot reduce capacity to 100 because 200 tickets have already been sold"
}

6. Update Ticket Status

Purpose: Manually changes the status of a ticket type. Use this to pause sales (INACTIVE), permanently stop sales (CLOSED), or reactivate a paused ticket (ACTIVE). Works on both DRAFT and PUBLISHED events. The system automatically manages SOLD_OUT status — it cannot be set manually.

Endpoint: PATCH {base_url}/{eventId}/{ticketId}/status

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
ticketId UUID Yes The ticket to update status for Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
status string Yes The new status to set Enum: ACTIVE, INACTIVE, CLOSED. Cannot set SOLD_OUT or DELETED manually

Status Transition Rules:

Current Status Allowed Transitions Notes
ACTIVE INACTIVE, CLOSED Normal operations
INACTIVE ACTIVE, CLOSED Can be reactivated
SOLD_OUT ACTIVE, CLOSED ACTIVE only if capacity was increased first
CLOSED None Permanent. Cannot be changed
DELETED None Permanent. Cannot be changed

Request JSON Sample:

{
  "status": "INACTIVE"
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket status updated successfully",
  "action_time": "2025-02-18T13:00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "VIP Pass",
    "status": "INACTIVE",
    "isOnSale": false,
    "updatedAt": "2025-02-18T13:00:00+03:00",
    "updatedBy": "john_organizer"
  }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 Attempted to set SOLD_OUT or DELETED manually
400 Attempted to change status of a CLOSED or DELETED ticket
422 status field is missing

7. Delete Ticket

Purpose: Soft-deletes a ticket type. The ticket is marked as deleted and hidden from all listings. Deletion is only allowed if zero tickets have been sold. If tickets have already been sold, close the ticket using the status endpoint instead.

Endpoint: DELETE {base_url}/{eventId}/{ticketId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
ticketId UUID Yes The ticket to delete Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket deleted successfully",
  "action_time": "2025-02-18T14:00:00",
  "data": null
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 Ticket cannot be deleted because tickets have already been sold

Tickets Already Sold (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot delete ticket 'VIP Pass' because 45 tickets have been sold. You can close the ticket instead to stop sales.",
  "action_time": "2025-02-18T14:00:00",
  "data": "Cannot delete ticket 'VIP Pass' because 45 tickets have been sold. You can close the ticket instead to stop sales."
}

8. Update Sales Window

Purpose: Updates the sales start and/or end datetime of a ticket on a PUBLISHED event. Both fields are optional — omitting one preserves its current value. All existing sales period rules apply (30-minute minimum gap, must fall within registration window, etc.).

Endpoint: PATCH {base_url}/{ticketId}/sales-window

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
ticketId UUID Yes The ticket to update the sales window for Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
salesStartDateTime datetime No New sales open time ISO 8601 ZonedDateTime. Must be on or after registrationOpensAt. Cannot be after registrationClosesAt
salesEndDateTime datetime No New sales close time ISO 8601 ZonedDateTime. Must be after salesStartDateTime with at least 30 minutes gap. Cannot be after registrationClosesAt

At least one of salesStartDateTime or salesEndDateTime must be provided.

Request JSON Sample:

{
  "salesEndDateTime": "2026-04-25T23:59:00+03:00"
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket sales window updated successfully",
  "action_time": "2026-02-20T10:00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
    "name": "VIP Pass",
    "salesStartDateTime": "2026-03-18T05:00:00Z",
    "salesEndDateTime": "2026-04-25T20:59:00Z",
    "isOnSale": true,
    "status": "ACTIVE",
    "updatedAt": "2026-02-20T10:00:00+03:00",
    "updatedBy": "john_organizer"
  }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 Ticket is deleted or closed
422 Sales window violates registration window, gap less than 30 minutes, or no fields provided

Sales Window Outside Registration Window (422):

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Sales end date cannot be after registration closes (2026-04-17T20:59:00Z)",
  "action_time": "2026-02-20T10:00:00",
  "data": {
    "stage": "TICKETS",
    "message": "Sales end date cannot be after registration closes (2026-04-17T20:59:00Z)"
  }
}

9. Update Published Ticket

Purpose: Performs a limited update on a ticket belonging to a PUBLISHED event. Only three fields are allowed: visibility (and its schedule dates), status (ACTIVE, INACTIVE, or CLOSED), and inclusiveItems. All other fields must be updated while the event is still in DRAFT.

Endpoint: PATCH {base_url}/{ticketId}/published

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
ticketId UUID Yes The published ticket to update Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
visibility string No Updated visibility setting Enum: VISIBLE, HIDDEN, HIDDEN_WHEN_NOT_ON_SALE, CUSTOM_SCHEDULE
visibilityStartDate datetime No Start of custom visibility window Required if visibility is CUSTOM_SCHEDULE. ISO 8601 ZonedDateTime
visibilityEndDate datetime No End of custom visibility window Required if visibility is CUSTOM_SCHEDULE. Must be after visibilityStartDate. ISO 8601 ZonedDateTime
status string No Updated ticket status Enum: ACTIVE, INACTIVE, CLOSED. Cannot set SOLD_OUT or DELETED
inclusiveItems array of strings No Full replacement list of perks Max: 50 items. Each: max 200 characters, cannot be blank

This endpoint is specifically for PUBLISHED events. For DRAFT events, use the full PUT /{ticketId} endpoint instead.

Request JSON Sample:

{
  "visibility": "CUSTOM_SCHEDULE",
  "visibilityStartDate": "2026-03-01T00:00:00+03:00",
  "visibilityEndDate": "2026-04-17T23:59:00+03:00",
  "inclusiveItems": [
    "Backstage access",
    "Complimentary gift bag",
    "Priority seating",
    "Artist meet & greet",
    "Exclusive after-party entry"
  ]
}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Ticket updated successfully",
  "action_time": "2026-02-20T10:00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
    "name": "VIP Pass",
    "description": "Full weekend access with backstage entry and a complimentary gift bag.",
    "price": 150.00,
    "ticketPricingType": "PAID",
    "salesChannel": "EVERYWHERE",
    "totalTickets": 200,
    "ticketsSold": 87,
    "ticketsRemaining": 113,
    "ticketsAvailable": 113,
    "isSoldOut": false,
    "salesStartDateTime": "2026-03-18T05:00:00Z",
    "salesEndDateTime": "2026-04-17T20:59:00Z",
    "isOnSale": true,
    "minQuantityPerOrder": 1,
    "maxQuantityPerOrder": 4,
    "maxQuantityPerUser": 4,
    "visibility": "CUSTOM_SCHEDULE",
    "visibilityStartDate": "2026-03-01T00:00:00+03:00",
    "visibilityEndDate": "2026-04-17T23:59:00+03:00",
    "isCurrentlyVisible": false,
    "attendanceMode": "IN_PERSON",
    "inclusiveItems": [
      "Backstage access",
      "Complimentary gift bag",
      "Priority seating",
      "Artist meet & greet",
      "Exclusive after-party entry"
    ],
    "status": "ACTIVE",
    "createdAt": "2025-02-18T10:30:45+03:00",
    "updatedAt": "2026-02-20T10:00:00+03:00",
    "createdBy": "john_organizer",
    "updatedBy": "john_organizer"
  }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Ticket not found
400 Event is not in PUBLISHED status — use the draft update endpoint instead
400 Ticket is deleted
422 Invalid visibility schedule dates, attempted to set SOLD_OUT or DELETED, or CUSTOM_SCHEDULE missing required date fields

Event Not Published (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "This endpoint is only for published events. Use the draft ticket update endpoint instead.",
  "action_time": "2026-02-20T10:00:00",
  "data": "This endpoint is only for published events. Use the draft ticket update endpoint instead."
}

Quick Reference

Endpoint Summary

# Method Path Description Auth Event Status
1 POST /{eventId} Create a ticket type 🔒 Organizer DRAFT or PUBLISHED
2 PUT /{ticketId} Full update of ticket details 🔒 Organizer DRAFT only
3 GET /{eventId} Get all tickets for an event 🌐 Public Any
4 GET /{eventId}/{ticketId} Get a single ticket by ID 🌐 Public Any
5 PATCH /{eventId}/{ticketId}/capacity Update ticket capacity 🔒 Organizer DRAFT or PUBLISHED
6 PATCH /{eventId}/{ticketId}/status Update ticket status 🔒 Organizer DRAFT or PUBLISHED
7 DELETE /{eventId}/{ticketId} Delete a ticket 🔒 Organizer DRAFT or PUBLISHED (0 sold)
8 PATCH /{ticketId}/sales-window Update sales window dates 🔒 Organizer PUBLISHED
9 PATCH /{ticketId}/published Limited update (visibility, status, perks) 🔒 Organizer PUBLISHED only

Common HTTP Status Codes

Code Meaning
200 OK Successful GET, PATCH, PUT, or DELETE
201 Created Successful POST (ticket created)
400 Bad Request Business rule violated (wrong status, already sold, duplicate name)
401 Unauthorized Missing or invalid token
403 Forbidden Authenticated but not the event organizer
404 Not Found Event or ticket does not exist
422 Unprocessable Entity Validation errors on request fields

Business Rule Cheat Sheet

Rule Detail
Create ticket Allowed on DRAFT and PUBLISHED events
Full update (PUT) DRAFT events only
Published update (PATCH /published) PUBLISHED events only. Visibility, status, and inclusiveItems only
Sales window update PUBLISHED events. Validates against registration window
Capacity update DRAFT and PUBLISHED events
Status update DRAFT and PUBLISHED events
FREE ticket Price must be exactly 0.00
PAID ticket Price must be greater than 0.00
DONATION ticket ONLINE_ONLY channel. Max 1 per order and per user. No fixed price — buyer sets amount at checkout. price is null in response
Sales window Must fall within the event's registration window
Sales period gap Minimum 30 minutes between salesStartDateTime and salesEndDateTime
HYBRID event Must have ≥ 1 IN_PERSON ticket and ≥ 1 ONLINE ticket to publish
Delete Only allowed if ticketsSold = 0
SOLD_OUT System-managed. Cannot be set manually. Auto-cleared when capacity is increased
CLOSED Permanent. Cannot be reversed

Event Checkout & Payment API

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

Base URL: https://api.nextgate.co.tz/api/v1

Short Description: The NextGate Checkout API manages the complete ticket purchasing lifecycle on the NextGate event platform. It supports two distinct checkout flows: online checkout for registered attendees and at-door ticket sales for event organizers and scanner devices. This API should be used by frontend clients, mobile applications, and authorized scanner hardware integrations.

Hints:


Standard Response Format

All API responses follow a consistent structure using our Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {}
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}

Insufficient Balance Error Structure (422)

Returned when wallet balance is insufficient at session creation time for a PAID ticket. No session is created. The data field carries rich balance details so the frontend can guide the user directly to top up.

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Insufficient wallet balance to complete checkout",
  "action_time": "2025-09-23T10:30:45",
  "data": {
    "walletBalance": 50000.00,
    "sessionTotal": 150000.00,
    "shortfall": 100000.00,
    "hasSufficientBalance": false,
    "recommendedTopUp": 100000.00,
    "pspMinimum": 500.00,
    "currency": "TZS"
  }
}
Field Description
walletBalance Current wallet balance of the user in TZS
sessionTotal Total amount required for this checkout
shortfall Amount missing (sessionTotal - walletBalance)
hasSufficientBalance Always false when this error is returned
recommendedTopUp Suggested top-up amount — at minimum equals shortfall, rounded up to pspMinimum if needed
pspMinimum Minimum top-up amount accepted by the payment provider
currency Always TZS

Standard Response Fields

Field Type Description
success boolean Always true for successful operations, false for errors
httpStatus string HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.)
message string Human-readable message describing the operation result
action_time string ISO 8601 timestamp of when the response was generated
data object/string Response payload for success, error details for failures

HTTP Method Badge Standards


User Journey Flows

Flow A — Online Checkout (Registered Attendee)

  [ Attendee browses event & selects ticket ]
                      |
                      v
  [ POST /checkout  — Create checkout session ]
                      |
          ............|............
          .                       .
          v                       v
  [ FREE / DONATION ticket ]   [ PAID ticket ]
          |                       |
          |                       v
          |           [ Wallet balance check ]
          |           ....................
          |           . INSUFFICIENT     .
          |           ....................
          |                   |
          |           [ 422 response — no session created ]
          |           [ data: { shortfall, recommendedTopUp, ... } ]
          |
          |           ....................
          |           . SUFFICIENT       .
          |           ....................
          |                       |
          v                       v
  [ Auto-processed immediately ]  [ Ticket hold applied ]
          |                       [ Session = PENDING_PAYMENT ]
          |                       |
          |                       v
          |           [ POST /checkout/{sessionId}/payment ]
          |           [ — Deduct from wallet, create escrow ]
          |                       |
          `----------->-----------'
                      |
                      v
  [ Booking order created asynchronously ]
  [ QR codes generated & sent to attendees ]
                      |
                      v
  [ Session status = COMPLETED ]


  At any point before payment:
  [ POST /checkout/{sessionId}/cancel ]
  [ — Releases ticket hold            ]

Flow B — At-Door Sale via Scanner Device

  [ Customer arrives at event venue ]
                      |
                      v
  [ Scanner device sends sale request ]
  [ POST /checkout/sell-at-door-ticket/scanner ]
                      |
          ............|............
          .                       .
          v                       v
  [ Validate scanner ID ]   [ Validate device fingerprint ]
  [ & permissions       ]   [ & SELL_TICKETS permission   ]
          .                       .
          `..........v............'
                      |
                      v
  [ Validate ticket type belongs to event ]
  [ Check sales channel = AT_DOOR or BOTH ]
                      |
                      v
  [ Cash payment processed (no wallet deduction) ]
  [ Booking order created immediately            ]
                      |
                      v
  [ QR codes returned in response ]
  [ immediateCheckIn = true → ticket marked as checked-in ]

Flow C — At-Door Sale via Organizer

  [ Organizer is authenticated & accesses event ]
                      |
                      v
  [ POST /checkout/{eventId}/organizer ]
  [ — Organizer sells ticket at their counter ]
                      |
                      v
  [ System verifies organizer owns the event ]
                      |
                      v
  [ Validate ticket type & attendee count ]
                      |
                      v
  [ Cash payment recorded (NEUTRAL transaction) ]
  [ Booking order created                       ]
                      |
                      v
  [ QR codes returned in response ]
  [ immediateCheckIn flag respected ]

Endpoints


1. Create Checkout Session

Purpose: Creates a new online checkout session for a registered attendee purchasing event tickets, holding the requested quantity and initializing the payment intent.

Endpoint: POST /api/v1/e-events/checkout

Access Level: 🔒 Protected (Requires valid Bearer token — authenticated attendee)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token of the authenticated attendee
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
  "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
  "ticketsForMe": 2,
  "donationAmount": null,
  "otherAttendees": [
    {
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "phone": "+255712345678",
      "quantity": 1
    }
  ],
  "sendTicketsToAttendees": true,
  "paymentMethodId": null
}

Request Body Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The ID of the event being booked Must be a valid published event that has not yet started
ticketTypeId UUID Yes The ID of the ticket type being purchased Must belong to the specified event and be active and on sale
ticketsForMe integer Yes Number of tickets for the buyer themselves. Use 0 if the buyer is not attending Min: 0
donationAmount decimal No Donation amount in TZS. Only applicable for DONATION type tickets Only used when ticket pricing type is DONATION
otherAttendees array No List of other attendees to purchase tickets for Each attendee must have valid name, email, and Tanzanian phone number
otherAttendees[].name string Yes (if array provided) Full name of the attendee Min: 2, Max: 100 characters
otherAttendees[].email string Yes (if array provided) Email address of the attendee Valid email format; no duplicate emails in the array
otherAttendees[].phone string Yes (if array provided) Phone number of the attendee Must match Tanzania format: +255[67]XXXXXXXX
otherAttendees[].quantity integer Yes (if array provided) Number of tickets for this attendee Min: 1
sendTicketsToAttendees boolean No If true, QR tickets are sent to each attendee's email. If false, all QR codes are sent to the buyer only Default: true
paymentMethodId UUID No ID of a saved payment method. Defaults to wallet if not provided Optional

Business Rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Checkout session created successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {
    "sessionId": "a1b2c3d4-0000-0000-0000-000000000001",
    "status": "PENDING_PAYMENT",
    "customerId": "f1e2d3c4-0000-0000-0000-000000000010",
    "customerUserName": "john_doe",
    "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
    "eventTitle": "Kilimanjaro Jazz Night 2025",
    "ticketDetails": {
      "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
      "ticketTypeName": "VIP",
      "unitPrice": 50000.00,
      "ticketsForBuyer": 2,
      "otherAttendees": [
        {
          "name": "Jane Doe",
          "email": "jane.doe@example.com",
          "phone": "+255712345678",
          "quantity": 1
        }
      ],
      "sendTicketsToAttendees": true,
      "totalQuantity": 3,
      "subtotal": 150000.00
    },
    "pricing": {
      "subtotal": 150000.00,
      "total": 150000.00
    },
    "paymentIntent": {
      "provider": "WALLET",
      "clientSecret": null,
      "paymentMethods": ["WALLET"],
      "status": "PENDING"
    },
    "ticketsHeld": true,
    "ticketHoldExpiresAt": "2025-09-23T10:45:45",
    "expiresAt": "2025-09-23T10:45:45",
    "createdAt": "2025-09-23T10:30:45",
    "updatedAt": "2025-09-23T10:30:45",
    "completedAt": null,
    "createdBookingOrderId": null,
    "isExpired": false,
    "canRetryPayment": false
  }
}

Success Response Fields:

Field Description
sessionId Unique identifier for this checkout session. Use it for all subsequent actions
status Current session status. Values: PENDING_PAYMENT, PAYMENT_COMPLETED, COMPLETED, PAYMENT_FAILED, CANCELLED, EXPIRED
customerId Account ID of the buyer
customerUserName Username of the buyer
eventId ID of the event being booked
eventTitle Human-readable event title
ticketDetails.ticketTypeId ID of the chosen ticket type
ticketDetails.ticketTypeName Name of the ticket type (e.g., VIP, Regular)
ticketDetails.unitPrice Price per single ticket in TZS
ticketDetails.ticketsForBuyer Number of tickets allocated to the buyer
ticketDetails.otherAttendees List of other attendees and their ticket quantities
ticketDetails.sendTicketsToAttendees Whether QR codes will be emailed to each attendee
ticketDetails.totalQuantity Total tickets across buyer and all attendees
ticketDetails.subtotal Total price before any discounts (TZS)
pricing.subtotal Subtotal amount in TZS
pricing.total Final payable amount in TZS
paymentIntent.provider Payment provider (e.g., WALLET)
paymentIntent.paymentMethods Available payment methods for this session
paymentIntent.status Payment intent status (PENDING, COMPLETED)
ticketsHeld Whether the tickets are currently being held in reserve
ticketHoldExpiresAt Timestamp when the ticket hold expires
expiresAt Timestamp when the entire session expires
createdBookingOrderId Populated after payment is completed — the resulting booking order ID
isExpired Computed flag indicating whether the session has passed its expiry time
canRetryPayment Whether the session allows another payment attempt (true if status is PAYMENT_FAILED and attempts < 5)

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Please complete the event questionnaire before purchasing tickets",
  "action_time": "2025-09-23T10:30:45",
  "data": "Please complete the event questionnaire before purchasing tickets"
}

Possible Errors:

HTTP Status Scenario
400 BAD_REQUEST Event is not published, event has already started, ticket not on sale, AT_DOOR_ONLY ticket purchased online, DONATION rules violated, questionnaire not submitted
401 UNAUTHORIZED Missing, expired, or invalid Bearer token
404 NOT_FOUND Event or ticket type not found
422 UNPROCESSABLE_ENTITY Insufficient wallet balance (returns rich data with shortfall and recommendedTopUp); or validation failed on request fields
500 INTERNAL_SERVER_ERROR Unexpected server error

2. Get Checkout Session

Purpose: Retrieves the current state of an existing checkout session belonging to the authenticated user.

Endpoint: GET /api/v1/e-events/checkout/{sessionId}

Access Level: 🔒 Protected (Authenticated attendee — only the session owner can retrieve it)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token of the authenticated attendee

Path Parameters:

Parameter Type Required Description Validation
sessionId UUID Yes The ID of the checkout session to retrieve Must be a valid UUID belonging to the authenticated user

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Checkout session retrieved successfully",
  "action_time": "2025-09-23T10:35:00",
  "data": {
    "sessionId": "a1b2c3d4-0000-0000-0000-000000000001",
    "status": "PENDING_PAYMENT",
    "customerId": "f1e2d3c4-0000-0000-0000-000000000010",
    "customerUserName": "john_doe",
    "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
    "eventTitle": "Kilimanjaro Jazz Night 2025",
    "ticketDetails": {
      "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
      "ticketTypeName": "VIP",
      "unitPrice": 50000.00,
      "ticketsForBuyer": 2,
      "otherAttendees": [
        {
          "name": "Jane Doe",
          "email": "jane.doe@example.com",
          "phone": "+255712345678",
          "quantity": 1
        }
      ],
      "sendTicketsToAttendees": true,
      "totalQuantity": 3,
      "subtotal": 150000.00
    },
    "pricing": {
      "subtotal": 150000.00,
      "total": 150000.00
    },
    "paymentIntent": {
      "provider": "WALLET",
      "clientSecret": null,
      "paymentMethods": ["WALLET"],
      "status": "PENDING"
    },
    "paymentAttempts": [],
    "ticketsHeld": true,
    "ticketHoldExpiresAt": "2025-09-23T10:45:45",
    "expiresAt": "2025-09-23T10:45:45",
    "createdAt": "2025-09-23T10:30:45",
    "updatedAt": "2025-09-23T10:30:45",
    "completedAt": null,
    "createdBookingOrderId": null,
    "isExpired": false,
    "canRetryPayment": false
  }
}

Success Response Fields:

Field Description
sessionId Unique session identifier
status Current session status
paymentAttempts List of all payment attempts made on this session, including failures
paymentAttempts[].attemptNumber Sequential attempt number (1-indexed)
paymentAttempts[].paymentMethod Payment method used for this attempt
paymentAttempts[].status Result of the attempt: SUCCESS or FAILED
paymentAttempts[].errorMessage Failure reason if the attempt failed
paymentAttempts[].attemptedAt Timestamp of the attempt
paymentAttempts[].transactionId External or internal transaction reference
All other fields Same as Create Checkout Session response

Possible Errors:

HTTP Status Scenario
401 UNAUTHORIZED Missing, expired, or invalid Bearer token
404 NOT_FOUND Session not found or does not belong to the authenticated user
500 INTERNAL_SERVER_ERROR Unexpected server error

3. Process Payment

Purpose: Initiates wallet payment for a pending checkout session, creating an escrow account and triggering asynchronous booking order creation upon success.

Endpoint: POST /api/v1/e-events/checkout/{sessionId}/payment

Access Level: 🔒 Protected (Session owner only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token of the authenticated attendee

Path Parameters:

Parameter Type Required Description Validation
sessionId UUID Yes The ID of the checkout session to pay for Must be a valid UUID; session must be in PENDING_PAYMENT status and not expired

Business Rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Payment completed successfully. Your booking is being processed.",
  "action_time": "2025-09-23T10:38:00",
  "data": {
    "success": true,
    "status": "SUCCESS",
    "message": "Payment completed successfully. Your booking is being processed.",
    "checkoutSessionId": "a1b2c3d4-0000-0000-0000-000000000001",
    "escrowId": "e9f8a7b6-0000-0000-0000-000000000020",
    "escrowNumber": "ESC-2025-000001",
    "orderId": null,
    "orderNumber": null,
    "paymentMethod": "WALLET",
    "amountPaid": 150000.00,
    "platformFee": 7500.00,
    "sellerAmount": 142500.00,
    "currency": "TZS"
  }
}

Success Response Fields:

Field Description
status Payment result status: SUCCESS, FAILED, or PENDING
checkoutSessionId The checkout session this payment belongs to
escrowId ID of the escrow account holding the funds
escrowNumber Human-readable escrow reference number (format: ESC-YYYY-NNNNNN)
orderId Booking order ID — may be null immediately after payment as booking is created asynchronously
orderNumber Human-readable order reference — null until order is created
paymentMethod Payment method used: WALLET
amountPaid Total amount deducted from buyer's wallet in TZS
platformFee Platform fee amount (5% of total) in TZS
sellerAmount Amount that will be released to the event organizer in TZS
currency Currency code, always TZS

Possible Errors:

HTTP Status Scenario
400 BAD_REQUEST Session is not in PENDING_PAYMENT status, or session is expired
401 UNAUTHORIZED Missing, expired, or invalid Bearer token
404 NOT_FOUND Session not found or does not belong to the authenticated user
500 INTERNAL_SERVER_ERROR Unexpected payment processing or booking creation error

4. Cancel Checkout Session

Purpose: Cancels an active checkout session and releases any held tickets back to available inventory.

Endpoint: POST /api/v1/e-events/checkout/{sessionId}/cancel

Access Level: 🔒 Protected (Session owner only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token of the authenticated attendee

Path Parameters:

Parameter Type Required Description Validation
sessionId UUID Yes The ID of the checkout session to cancel Must be a valid UUID belonging to the authenticated user

Business Rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Checkout session cancelled successfully",
  "action_time": "2025-09-23T10:40:00",
  "data": null
}

Possible Errors:

HTTP Status Scenario
400 BAD_REQUEST Session is already completed or payment has been completed
401 UNAUTHORIZED Missing, expired, or invalid Bearer token
404 NOT_FOUND Session not found or does not belong to the authenticated user
500 INTERNAL_SERVER_ERROR Unexpected server error

5. Scanner — Sell Ticket at Door

Purpose: Allows an authorized scanner device to sell tickets at the venue entrance, processing a cash payment and optionally checking in the attendee immediately.

Endpoint: POST /api/v1/e-events/checkout/sell-at-door-ticket/scanner

Access Level: 🔒 Protected (Scanner device authentication via scannerId + deviceFingerprint)

Authentication: Bearer Token (of scanner's linked account) + Scanner credentials in body

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token linked to the scanner's registered account
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "scannerId": "SCN-2025-001",
  "deviceFingerprint": "a3f1b2c4d5e6f7890abc1234def56789",
  "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
  "quantity": 2,
  "attendees": [
    {
      "fullName": "John Mbeki",
      "email": "john.mbeki@example.com",
      "phoneNumber": "+255789123456"
    },
    {
      "fullName": "Amina Hassan",
      "email": "amina.hassan@example.com",
      "phoneNumber": "+255754321987"
    }
  ],
  "immediateCheckIn": true
}

Request Body Parameters:

Parameter Type Required Description Validation
scannerId string Yes Unique identifier of the registered scanner device Must match a registered, active scanner with SELL_TICKETS permission
deviceFingerprint string Yes Hardware fingerprint of the scanner device Must match the fingerprint registered for this scanner
ticketTypeId UUID Yes ID of the ticket type to sell Must belong to the event this scanner is assigned to; must not be ONLINE_ONLY; must be on sale
quantity integer Yes Total number of tickets to sell Min: 1; must equal the number of attendees in the attendees array
attendees array Yes List of attendee details, one entry per ticket Min: 1 entry; count must match quantity
attendees[].fullName string No Full name of the attendee Optional — if blank, a generated name like ATTENDEE-XXXX is assigned
attendees[].email string No Email address of the attendee Valid email format if provided
attendees[].phoneNumber string No Phone number of the attendee Optional
immediateCheckIn boolean Yes If true, the ticket is marked as checked-in immediately upon sale Required

Business Rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Tickets sold successfully at door",
  "action_time": "2025-09-23T18:00:00",
  "data": {
    "bookingId": "c9d8e7f6-0000-0000-0000-000000000030",
    "bookingReference": "BK-2025-000042",
    "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
    "eventName": "Kilimanjaro Jazz Night 2025",
    "tickets": [
      {
        "ticketInstanceId": "aa11bb22-0000-0000-0000-000000000050",
        "ticketSeries": "VIP-0042-A",
        "ticketTypeName": "VIP",
        "attendeeName": "John Mbeki",
        "attendeeEmail": "john.mbeki@example.com",
        "checkedIn": true,
        "checkInTime": "2025-09-23T18:00:05Z",
        "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
      },
      {
        "ticketInstanceId": "cc33dd44-0000-0000-0000-000000000051",
        "ticketSeries": "VIP-0042-B",
        "ticketTypeName": "VIP",
        "attendeeName": "Amina Hassan",
        "attendeeEmail": "amina.hassan@example.com",
        "checkedIn": true,
        "checkInTime": "2025-09-23T18:00:05Z",
        "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
      }
    ],
    "totalAmount": 100000.00,
    "currency": "TZS",
    "paymentMethod": "CASH",
    "soldBy": "Gate-A Scanner",
    "soldAt": "Main Entrance",
    "saleTime": "2025-09-23T18:00:05Z"
  }
}

Success Response Fields:

Field Description
bookingId UUID of the created booking order
bookingReference Human-readable booking reference number
eventId ID of the event
eventName Name of the event
tickets Array of issued ticket instances — one per attendee
tickets[].ticketInstanceId Unique ID of this specific ticket instance
tickets[].ticketSeries Ticket serial number (e.g., VIP-0042-A)
tickets[].ticketTypeName The type of the sold ticket
tickets[].attendeeName Name of the attendee this ticket is assigned to
tickets[].attendeeEmail Email of the attendee
tickets[].checkedIn Whether the attendee has been checked in
tickets[].checkInTime Timestamp of check-in if immediateCheckIn was true
tickets[].qrCode JWT-encoded QR code string for this ticket
totalAmount Total cash amount collected in TZS
currency Always TZS
paymentMethod Always CASH for at-door sales
soldBy Name of the scanner that processed the sale
soldAt Location label of the scanner (e.g., "Main Entrance")
saleTime ISO 8601 timestamp of when the sale occurred

Possible Errors:

HTTP Status Scenario
400 BAD_REQUEST Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Scanner does not have SELL_TICKETS permission, device fingerprint mismatch, scanner is inactive or expired
404 NOT_FOUND Scanner ID not found, ticket type not found
422 UNPROCESSABLE_ENTITY Validation errors on request fields
500 INTERNAL_SERVER_ERROR Payment processing or booking creation failure

6. Organizer — Sell Ticket at Door

Purpose: Allows the authenticated event organizer to sell tickets directly at their event counter, processing a cash payment and optionally checking in the attendee immediately.

Endpoint: POST /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer

Access Level: 🔒 Protected (Must be the organizer of the specified event)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token of the authenticated event organizer
Content-Type string Yes Must be application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The ID of the event to sell tickets for Must be an existing, non-deleted event; authenticated user must be the organizer

Request JSON Sample:

{
  "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
  "quantity": 2,
  "attendees": [
    {
      "fullName": "Peter Salim",
      "email": "peter.salim@example.com",
      "phoneNumber": "+255711223344"
    },
    {
      "fullName": "Grace Mwangi",
      "email": "grace.mwangi@example.com",
      "phoneNumber": null
    }
  ],
  "immediateCheckIn": false,
  "location": "VIP Gate"
}

Request Body Parameters:

Parameter Type Required Description Validation
ticketTypeId UUID Yes ID of the ticket type to sell Must belong to the event in the path; must not be ONLINE_ONLY; must be on sale
quantity integer Yes Total number of tickets to sell Min: 1; must equal the number of attendees in the attendees array
attendees array Yes List of attendee details — one entry per ticket Min: 1 entry; count must match quantity
attendees[].fullName string No Full name of the attendee Optional — auto-generated if blank
attendees[].email string No Email of the attendee Valid email format if provided
attendees[].phoneNumber string No Phone number of the attendee Optional
immediateCheckIn boolean Yes Whether to mark attendees as checked-in immediately Required
location string No Description of the sale point, e.g., "VIP Gate", "Main Counter" Max: 200 characters; defaults to "Organizer Counter" if not provided

Business Rules:

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Tickets sold successfully at door",
  "action_time": "2025-09-23T17:30:00",
  "data": {
    "bookingId": "d7e6f5a4-0000-0000-0000-000000000035",
    "bookingReference": "BK-2025-000043",
    "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
    "eventName": "Kilimanjaro Jazz Night 2025",
    "tickets": [
      {
        "ticketInstanceId": "ee55ff66-0000-0000-0000-000000000060",
        "ticketSeries": "VIP-0043-A",
        "ticketTypeName": "VIP",
        "attendeeName": "Peter Salim",
        "attendeeEmail": "peter.salim@example.com",
        "checkedIn": false,
        "checkInTime": null,
        "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
      },
      {
        "ticketInstanceId": "gg77hh88-0000-0000-0000-000000000061",
        "ticketSeries": "VIP-0043-B",
        "ticketTypeName": "VIP",
        "attendeeName": "Grace Mwangi",
        "attendeeEmail": "grace.mwangi@example.com",
        "checkedIn": false,
        "checkInTime": null,
        "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
      }
    ],
    "totalAmount": 100000.00,
    "currency": "TZS",
    "paymentMethod": "CASH",
    "soldBy": "organizer_username",
    "soldAt": "VIP Gate",
    "saleTime": "2025-09-23T17:30:05Z"
  }
}

Success Response Fields:

Field Description
bookingId UUID of the created booking order
bookingReference Human-readable booking reference number
eventId ID of the event
eventName Name of the event
tickets Array of issued ticket instances — one per attendee
tickets[].ticketInstanceId Unique ID of this specific ticket instance
tickets[].ticketSeries Ticket serial number
tickets[].ticketTypeName The type of ticket sold
tickets[].attendeeName Assigned attendee name
tickets[].attendeeEmail Attendee email
tickets[].checkedIn Whether immediately checked in
tickets[].checkInTime Check-in timestamp, null if not checked in
tickets[].qrCode JWT-encoded QR code string for this ticket
totalAmount Total cash amount in TZS
currency Always TZS
paymentMethod Always CASH
soldBy Username of the organizer who made the sale
soldAt Location label provided in the request
saleTime ISO 8601 timestamp of the sale

Possible Errors:

HTTP Status Scenario
400 BAD_REQUEST Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Authenticated user is not the organizer of the specified event
404 NOT_FOUND Event not found, ticket type not found
422 UNPROCESSABLE_ENTITY Validation errors on request fields
500 INTERNAL_SERVER_ERROR Payment processing or booking creation failure

Standard Error Response Examples

Bad Request — General (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Ticket is not currently on sale",
  "action_time": "2025-09-23T10:30:45",
  "data": "Ticket is not currently on sale"
}

Unauthorized — Token Issues (401):

{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2025-09-23T10:30:45",
  "data": "Token has expired"
}

Forbidden — Access Denied (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Only the event organizer can sell tickets at door",
  "action_time": "2025-09-23T10:30:45",
  "data": "Only the event organizer can sell tickets at door"
}

Not Found (404):

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Event not found",
  "action_time": "2025-09-23T10:30:45",
  "data": "Event not found"
}

Validation Error (422):

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2025-09-23T10:30:45",
  "data": {
    "ticketTypeId": "must not be null",
    "quantity": "must be greater than or equal to 1",
    "immediateCheckIn": "must not be null"
  }
}

Standard Error Types Reference

Application-Level Exceptions (400–499)

Server-Level Exceptions (500+)


Ticket Pricing Types — Detailed Behaviour

This section explains exactly how the system handles each pricing type end-to-end: from checkout creation through payment, booking order creation, and ticket serial assignment. Understanding this is critical for integrating correctly with the checkout API.


FREE Tickets

What they are: Tickets with a price of 0 TZS. No money changes hands.

Checkout flow:

  [ POST /checkout — session created ]
              |
              v
  [ System detects price = 0 TZS ]
              |
              v
  [ Payment auto-processed immediately ]
  [ No wallet deduction               ]
  [ No escrow created                 ]
              |
              v
  [ PaymentCompletedEvent published (escrow = null) ]
              |
              v
  [ Booking order created asynchronously ]
  [ Ticket serials assigned              ]
              |
              v
  [ Session status = COMPLETED ]
  [ Response returned to caller ]

Key rules:


PAID Tickets

What they are: Tickets with a price greater than 0 TZS. Wallet payment is required.

Checkout flow:

  [ POST /checkout — session created ]
              |
              v
  [ assertSufficientBalanceForCheckout(total) ]
  ............................................
  . INSUFFICIENT BALANCE                      .
  ............................................
          |
  [ 422 returned immediately ]
  [ No session created       ]
  [ data: {                  ]
  [   walletBalance,         ]
  [   sessionTotal,          ]
  [   shortfall,             ]
  [   recommendedTopUp       ]
  [ }                        ]

  ............................................
  . SUFFICIENT BALANCE                        .
  ............................................
          |
          v
  [ Ticket hold applied               ]
              |
              v
  [ Session status = PENDING_PAYMENT ]
  [ paymentIntent.provider = WALLET  ]
              |
              v
  [ POST /{sessionId}/payment called by client ]
              |
              v
  [ Wallet deducted via double-entry ledger ]
  [ Escrow account created (ESC-YYYY-NNNNNN)]
  [ platformFee = 5% of total              ]
  [ sellerAmount = total - platformFee     ]
              |
              v
  [ PaymentCompletedEvent published (escrow != null) ]
              |
              v
  [ Booking order created asynchronously ]
  [ Ticket serials assigned              ]
  [ QR codes generated                   ]
              |
              v
  [ Session status = COMPLETED ]
  [ Escrow status = HELD        ]
  [ (Released to organizer on event completion) ]

Key rules:


DONATION Tickets

What they are: Tickets where the attendee voluntarily chooses the amount they pay. A minimum may or may not be set by the organizer.

Checkout flow:

  [ POST /checkout — session created ]
  [ donationAmount provided in body  ]
              |
              v
  [ System validates DONATION rules  ]
  .....................................
  . totalQuantity must be exactly 1  .
  . otherAttendees must be empty     .
  . salesChannel must be ONLINE_ONLY .
  .....................................
              |
              v
  [ Treated as PAID internally                    ]
  [ donationAmount used as the ticket price       ]
  [ Wallet deducted for the donation amount       ]
  [ Escrow created for the donation amount        ]
              |
              v
  [ Booking order created asynchronously ]
  [ Ticket serial assigned               ]
  [ QR code generated                    ]
              |
              v
  [ Session status = COMPLETED ]

Key rules:


Ticket Serials — How They Are Assigned

Every ticket instance issued by the system receives a unique ticket serial (also called ticketSeries in the response). This serial is the human-readable identifier printed on physical tickets, displayed in QR codes, and used for manual verification at the door.

Serial Format

  [TICKET_TYPE_CODE]-[BOOKING_NUMBER]-[POSITION_LETTER]

  Examples:
    VIP-0042-A      ← First VIP ticket in booking #42
    VIP-0042-B      ← Second VIP ticket in booking #42
    REG-0199-A      ← First Regular ticket in booking #199
    GENERAL-0001-A  ← First General Admission ticket in booking #1

How Serials Are Generated

  [ Booking order created ]
              |
              v
  [ System reads totalQuantity from checkout session ]
              |
              v
  [ For each ticket in the order: ]
  .......................................
  .  ticketSeries = TYPE_CODE         .
  .               + "-"               .
  .               + BOOKING_NUMBER    .  ← zero-padded (e.g., 0042)
  .               + "-"               .
  .               + POSITION_LETTER   .  ← A, B, C, D ... per ticket
  .......................................
              |
              v
  [ Each serial stored on the TicketInstance entity ]
  [ JWT-encoded QR token generated per ticket       ]
  [ QR token embeds: ticketSeries + ticketInstanceId ]
              |
              v
  [ Serials returned in: ]
  .  POST /sell-at-door-ticket response (tickets[].ticketSeries) ]
  .  Booking order details endpoint (separate booking API)       ]

Serial Assignment per Pricing Type

Pricing Type When Serials Are Assigned Who Appears in attendeeName
FREE Asynchronously after PaymentCompletedEvent Buyer for ticketsForBuyer tickets; each named attendee for their tickets
PAID Asynchronously after payment escrow is created Buyer for ticketsForBuyer tickets; each named attendee for their tickets
DONATION Asynchronously after payment (always 1 ticket) Always the buyer only
At-Door (any type) Synchronously — returned immediately in the sale response Each attendee in the attendees array; auto-generated name if blank

Attendee-to-Serial Mapping

When a buyer purchases tickets for themselves and other attendees, each serial maps to exactly one person:

  Buyer purchases:
    ticketsForMe = 2
    otherAttendees = [ { name: "Jane", quantity: 1 } ]
    totalQuantity = 3

  Serials assigned:
    VIP-0042-A  →  Buyer (ticket 1 of 2 for buyer)
    VIP-0042-B  →  Buyer (ticket 2 of 2 for buyer)
    VIP-0042-C  →  Jane  (her 1 ticket)

QR Code & Serial Relationship

Each ticket's qrCode field in the response is a JWT token that encodes the ticket serial and instance ID. Scanners decode this JWT at check-in time to verify the ticket. The serial alone is readable by humans; the JWT is what the scanner hardware validates cryptographically.

  QR Code (JWT) decodes to:
  ...........................................
  .  ticketInstanceId  (UUID)             .
  .  ticketSeries      (e.g. VIP-0042-A)  .
  .  eventId           (UUID)             .
  .  issuedAt          (timestamp)        .
  ...........................................
              |
              v
  [ Scanner validates JWT signature ]
  [ Marks ticket as CHECKED_IN      ]
  [ Returns check-in confirmation   ]

Quick Reference

Endpoint Summary

# Method Path Description
1 POST /api/v1/e-events/checkout Create online checkout session
2 GET /api/v1/e-events/checkout/{sessionId} Get checkout session details
3 POST /api/v1/e-events/checkout/{sessionId}/payment Process wallet payment
4 POST /api/v1/e-events/checkout/{sessionId}/cancel Cancel checkout session
5 POST /api/v1/e-events/checkout/sell-at-door-ticket/scanner Scanner at-door sale
6 POST /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer Organizer at-door sale

Session Status Reference

Status Meaning
PENDING_PAYMENT Session created, awaiting payment
PAYMENT_PROCESSING External payment initiated, awaiting confirmation
PAYMENT_COMPLETED Payment succeeded, booking being created
PAYMENT_FAILED Payment attempt failed (retry may be possible)
COMPLETED Booking fully created and confirmed
CANCELLED Session cancelled by user
EXPIRED Session timed out before payment

Ticket Pricing Type Behaviour

Pricing Type Payment Required At-Door Allowed Notes
FREE No Depends on sales channel Auto-processed on session creation
PAID Yes (Wallet) Yes Escrow created on payment
DONATION Optional amount No (Online only) Max 1 ticket per order; no other attendees

Sales Channel Rules

Sales Channel Online Checkout At-Door (Scanner) At-Door (Organizer)
ONLINE_ONLY ✅ Allowed ❌ Blocked ❌ Blocked
AT_DOOR_ONLY ❌ Blocked ✅ Allowed ✅ Allowed
BOTH ✅ Allowed ✅ Allowed ✅ Allowed

Authentication Reference

Data Format Standards

Event Booking Orders API

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

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

Short Description: The Event Booking Orders API manages confirmed ticket purchases for events on the Nexgate platform. It allows customers to view their bookings with JWT-signed QR codes, track multi-day check-in history, download ticket PDFs, and enables event organizers to monitor all orders for their events with filtering and revenue stats.

Hints:


Standard Response Format

All JSON endpoints follow a consistent structure using the Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": {}
}

Error Response Structure

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Booking not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "Booking not found"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.)
message string Human-readable description of the result
action_time string ISO 8601 timestamp of when the response was generated
data object/string Response payload on success; error message string on failure

HTTP Method Badge Standards


Endpoints

1. Get Booking by ID

Purpose: Retrieve complete booking details including all tickets, check-in history, and event snapshot for a specific booking.

Endpoint: GET {base_url}/e-events/booking-orders/{bookingId}

Access Level: 🔒 Protected (Booking Owner, Event Organizer, or Admin)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token (format: Bearer <token>)

Path Parameters:

Parameter Type Required Description Validation
bookingId string (UUID) Yes Unique booking order ID Must be valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Booking retrieved successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "bookingId": "550e8400-e29b-41d4-a716-446655440000",
    "bookingReference": "EVT-A3F4B21C",
    "status": "CONFIRMED",
    "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "event": {
      "eventId": "770e8400-e29b-41d4-a716-446655440002",
      "title": "East African Tech Summit 2025",
      "startDateTime": "2025-12-15T09:00:00",
      "endDateTime": "2025-12-17T18:00:00",
      "timezone": "Africa/Nairobi",
      "location": "KICC Nairobi, Harambee Avenue, Nairobi",
      "format": "HYBRID",
      "hasApplicantForm": true,
      "virtualDetails": {
        "meetingLink": "https://zoom.us/j/123456789",
        "meetingId": "123 456 789",
        "passcode": "summit2025"
      }
    },
    "organizer": {
      "name": "TechEvents Kenya",
      "email": "organizer@techevents.ke",
      "phone": "+254712345678"
    },
    "customer": {
      "customerId": "660e8400-e29b-41d4-a716-446655440001",
      "name": "johndoe",
      "email": "john@example.com"
    },
    "tickets": [
      {
        "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440010",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "ticketTypeName": "VIP Pass",
        "ticketSeries": "VIP-0001",
        "ticketNumber": "VIP-0001",
        "price": 150.00,
        "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "attendanceMode": "IN_PERSON",
        "attendee": {
          "name": "John Doe",
          "email": "john@example.com",
          "phone": "+255712345678"
        },
        "buyer": {
          "name": "John Doe",
          "email": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "checkIns": [
          {
            "checkInTime": "2025-12-15T09:15:00+03:00",
            "checkInLocation": "Main Gate",
            "checkedInBy": "Scanner Operator 1",
            "dayName": "Day 1 - Opening Day",
            "scannerId": "SCANNER-001",
            "checkInMethod": "QR_SCAN"
          },
          {
            "checkInTime": "2025-12-16T08:45:00+03:00",
            "checkInLocation": "VIP Entrance",
            "checkedInBy": "Scanner Operator 2",
            "dayName": "Day 2 - Conference Day",
            "scannerId": "SCANNER-003",
            "checkInMethod": "QR_SCAN"
          }
        ],
        "hasBeenCheckedIn": true,
        "lastCheckedInAt": "2025-12-16T08:45:00+03:00",
        "lastCheckedInBy": "Scanner Operator 2",
        "lastCheckInLocation": "VIP Entrance",
        "lastCheckInDayName": "Day 2 - Conference Day",
        "status": "USED",
        "validFrom": "2025-12-15T09:00:00+03:00",
        "validUntil": "2025-12-17T18:00:00+03:00"
      },
      {
        "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440011",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "ticketTypeName": "General Admission",
        "ticketSeries": "GENER-0042",
        "ticketNumber": "GENER-0042",
        "price": 50.00,
        "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "attendanceMode": "IN_PERSON",
        "attendee": {
          "name": "Jane Smith",
          "email": "jane@example.com",
          "phone": "+255723456789"
        },
        "buyer": {
          "name": "John Doe",
          "email": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "checkIns": [],
        "hasBeenCheckedIn": false,
        "status": "ACTIVE",
        "validFrom": "2025-12-15T09:00:00+03:00",
        "validUntil": "2025-12-17T18:00:00+03:00"
      }
    ],
    "totalTickets": 2,
    "checkedInTicketsCount": 1,
    "subtotal": 200.00,
    "total": 200.00,
    "bookedAt": "2025-12-11T10:30:45",
    "cancelledAt": null
  }
}

Success Response Fields:

Field Description
bookingId Unique booking UUID
bookingReference Short readable code (e.g., EVT-A3F4B21C)
status Booking status: CONFIRMED, CANCELLED
formResponseId UUID of the buyer's applicant form response — null if the event had no form
event.eventId Event UUID
event.title Event title at time of booking (immutable snapshot)
event.startDateTime Event start — LocalDateTime (no timezone)
event.endDateTime Event end — LocalDateTime (no timezone)
event.timezone IANA timezone string (e.g., Africa/Nairobi)
event.location Full venue address snapshotted at booking
event.format IN_PERSON, ONLINE, or HYBRID
event.hasApplicantForm true if the event had an applicant form at time of booking
event.virtualDetails Virtual meeting info — only present for ONLINE/HYBRID events
event.virtualDetails.meetingLink Full meeting URL
event.virtualDetails.meetingId Meeting ID (optional)
event.virtualDetails.passcode Meeting passcode (optional)
organizer.name Organizer name snapshotted at booking
organizer.email Organizer email
organizer.phone Organizer phone
customer.customerId Customer account UUID
customer.name Customer username
customer.email Customer email
tickets Array of all booked ticket instances
tickets[].ticketInstanceId Unique ticket instance UUID
tickets[].formResponseId UUID of the applicant form response linked to this ticket — same as order-level formResponseId
tickets[].ticketTypeName Ticket type name (e.g., VIP Pass)
tickets[].ticketSeries Auto-generated series (e.g., VIP-0001)
tickets[].ticketNumber Same as ticketSeries — used for PDF labeling
tickets[].price Price paid for this ticket
tickets[].qrCode RSA-signed JWT — used for gate check-in scanning
tickets[].attendanceMode IN_PERSON or ONLINE (relevant for hybrid events)
tickets[].attendee Person this ticket is for
tickets[].attendee.name Attendee full name
tickets[].attendee.email Attendee email
tickets[].attendee.phone Attendee phone
tickets[].buyer Person who purchased this ticket
tickets[].buyer.name Buyer name
tickets[].buyer.email Buyer email (null for AT_DOOR purchases)
tickets[].buyer.buyerType SYSTEM_USER (online) or AT_DOOR (sold at gate)
tickets[].checkIns Full check-in history — one record per day attended
tickets[].checkIns[].checkInTime ZonedDateTime of check-in
tickets[].checkIns[].checkInLocation Where checked in (e.g., Main Gate, VIP Entrance)
tickets[].checkIns[].checkedInBy Staff/scanner operator name
tickets[].checkIns[].dayName Event day label (e.g., Day 1 - Opening Day)
tickets[].checkIns[].scannerId Scanner device ID
tickets[].checkIns[].checkInMethod QR_SCAN (default), MANUAL, or NFC
tickets[].hasBeenCheckedIn true if ticket has at least one check-in
tickets[].lastCheckedInAt Most recent check-in time (ZonedDateTime, null if none)
tickets[].lastCheckedInBy Who performed the most recent check-in (null if none)
tickets[].lastCheckInLocation Location of most recent check-in (null if none)
tickets[].lastCheckInDayName Day label of most recent check-in (null if none)
tickets[].status ACTIVE, USED, or CANCELLED
tickets[].validFrom Ticket validity start — ZonedDateTime
tickets[].validUntil Ticket validity end — ZonedDateTime
totalTickets Total ticket count in this booking
checkedInTicketsCount Count of tickets with at least one check-in
subtotal Subtotal before any deductions
total Total amount paid
bookedAt When the booking was created — LocalDateTime
cancelledAt When cancelled — LocalDateTime (null if still active)

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Booking not found: 550e8400-e29b-41d4-a716-446655440000",
  "action_time": "2026-05-23T10:30:45",
  "data": "Booking not found: 550e8400-e29b-41d4-a716-446655440000"
}

Standard Error Types:


2. Get My Bookings

Purpose: Retrieve a summary list of all bookings for the authenticated user, sorted newest first.

Endpoint: GET {base_url}/e-events/booking-orders/my-bookings

Access Level: 🔒 Protected (Authenticated Users)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token (format: Bearer <token>)

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Bookings retrieved successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": [
    {
      "bookingId": "550e8400-e29b-41d4-a716-446655440000",
      "bookingReference": "EVT-A3F4B21C",
      "status": "CONFIRMED",
      "eventTitle": "East African Tech Summit 2025",
      "eventStartDateTime": "2025-12-15T09:00:00",
      "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi",
      "totalTickets": 2,
      "checkedInTickets": 1,
      "total": 200.00,
      "bookedAt": "2025-12-11T10:30:45",
      "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
    {
      "bookingId": "550e8400-e29b-41d4-a716-446655440001",
      "bookingReference": "EVT-B5D2E12F",
      "status": "CONFIRMED",
      "eventTitle": "Dar es Salaam Food Festival",
      "eventStartDateTime": "2025-12-20T11:00:00",
      "eventLocation": "Mlimani City, Sam Nujoma Road, Dar es Salaam",
      "totalTickets": 4,
      "checkedInTickets": 0,
      "total": 150.00,
      "bookedAt": "2025-12-10T14:20:30",
      "formResponseId": null
    }
  ]
}

Success Response Fields:

Field Description
bookingId Unique booking UUID
bookingReference Short readable code (e.g., EVT-A3F4B21C)
status CONFIRMED or CANCELLED
eventTitle Event title snapshotted at booking time
eventStartDateTime Event start date/time — LocalDateTime (no timezone)
eventLocation Venue address snapshotted at booking time
totalTickets Total number of tickets in this booking
checkedInTickets Number of tickets with at least one check-in
total Total amount paid
bookedAt When booking was created — LocalDateTime
formResponseId UUID of the applicant form response — null if the event had no form

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "User not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "User not found"
}

Standard Error Types:


3. Download Ticket PDF

Purpose: Download or preview the PDF ticket for a specific ticket instance belonging to the authenticated user.

Endpoint: GET {base_url}/e-events/booking-orders/tickets/{ticketInstanceId}/pdf

Access Level: 🔒 Protected (Ticket Owner)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token (format: Bearer <token>)

Path Parameters:

Parameter Type Required Description Validation
ticketInstanceId string (UUID) Yes Unique ID of the ticket to download Must be valid UUID

Query Parameters:

Parameter Type Required Description Default
mode string No download prompts a save dialog; inline opens the PDF in the browser tab download

Success Response: Binary PDF file (not JSON)

Success Response Headers:

Header Value
Content-Type application/pdf
Content-Disposition attachment; filename="ticket-{series}.pdf" or inline; ... by mode

PDF Content Includes:

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "You don't have permission to access this ticket",
  "action_time": "2026-05-23T10:30:45",
  "data": "You don't have permission to access this ticket"
}

Standard Error Types:


4. Get Event Orders (Organizer)

Purpose: Retrieve a paginated list of all booking orders for a specific event with filtering by status, buyer type, and ticket type, plus aggregated revenue and order stats.

Endpoint: GET {base_url}/e-events/booking-orders/event/{eventId}

Access Level: 🔒 Protected (Event Organizer or Admin)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token (format: Bearer <token>)

Path Parameters:

Parameter Type Required Description Validation
eventId string (UUID) Yes Target event UUID Must be valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
status string (enum) No Filter by booking status CONFIRMED, CANCELLED
buyerType string (enum) No Filter by how the ticket was purchased SYSTEM_USER, AT_DOOR
ticketTypeId string (UUID) No Filter orders that include a specific ticket type Must be valid UUID
search string No Search by buyer name, email, or booking reference
page integer No Zero-based page index Min: 0 0
size integer No Number of orders per page Min: 1 20

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event orders retrieved",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "eventId": "770e8400-e29b-41d4-a716-446655440002",
    "eventTitle": "East African Tech Summit 2025",
    "appliedFilters": {
      "status": "CONFIRMED",
      "buyerType": null,
      "ticketTypeId": null,
      "ticketTypeName": null,
      "search": null
    },
    "stats": {
      "totalOrders": 142,
      "totalTicketsSold": 318,
      "totalRevenue": 47500.00,
      "confirmedOrders": 139,
      "cancelledOrders": 3,
      "refundedOrders": 0,
      "onlineOrders": 128,
      "atDoorOrders": 14
    },
    "orders": [
      {
        "bookingId": "550e8400-e29b-41d4-a716-446655440000",
        "bookingReference": "EVT-A3F4B21C",
        "status": "CONFIRMED",
        "bookedAt": "2025-12-11T10:30:45",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "buyer": {
          "buyerName": "John Doe",
          "buyerEmail": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "totalTickets": 2,
        "checkedInCount": 1,
        "total": 200.00,
        "ticketTypes": [
          { "ticketTypeName": "VIP Pass", "quantity": 1 },
          { "ticketTypeName": "General Admission", "quantity": 1 }
        ]
      },
      {
        "bookingId": "550e8400-e29b-41d4-a716-446655440099",
        "bookingReference": "EVT-C7E1F34A",
        "status": "CONFIRMED",
        "bookedAt": "2025-12-10T18:15:00",
        "formResponseId": null,
        "buyer": {
          "buyerName": "Grace Njeri",
          "buyerEmail": null,
          "buyerType": "AT_DOOR"
        },
        "totalTickets": 1,
        "checkedInCount": 1,
        "total": 50.00,
        "ticketTypes": [
          { "ticketTypeName": "General Admission", "quantity": 1 }
        ]
      }
    ],
    "pagination": {
      "currentPage": 0,
      "pageSize": 20,
      "totalPages": 8,
      "totalElements": 142,
      "hasNext": true,
      "hasPrevious": false
    }
  }
}

Success Response Fields:

Field Description
eventId UUID of the queried event
eventTitle Event title
appliedFilters Echo of the active filter values (null = not filtered)
appliedFilters.status Active booking status filter
appliedFilters.buyerType Active buyer type filter
appliedFilters.ticketTypeId Active ticket type UUID filter
appliedFilters.ticketTypeName Resolved name of the filtered ticket type
appliedFilters.search Active search string
stats Aggregated stats across ALL orders for the event (not just current page)
stats.totalOrders Total booking count
stats.totalTicketsSold Total individual tickets across all bookings
stats.totalRevenue Sum of all order totals
stats.confirmedOrders Orders with status CONFIRMED
stats.cancelledOrders Orders with status CANCELLED
stats.refundedOrders Orders with status REFUNDED
stats.onlineOrders Orders placed online (SYSTEM_USER buyer type)
stats.atDoorOrders Orders sold at the gate (AT_DOOR buyer type)
orders Paginated list of order rows
orders[].bookingId Booking UUID
orders[].bookingReference Short readable code (e.g., EVT-A3F4B21C)
orders[].status Booking status string
orders[].bookedAt When booking was created — LocalDateTime
orders[].formResponseId UUID of the buyer's applicant form response — null if no form or not yet submitted
orders[].buyer.buyerName Buyer's name
orders[].buyer.buyerEmail Buyer's email (null for AT_DOOR)
orders[].buyer.buyerType SYSTEM_USER or AT_DOOR
orders[].totalTickets Number of tickets in this order
orders[].checkedInCount Number of tickets checked in at least once
orders[].total Order total amount
orders[].ticketTypes Breakdown of ticket types in this order
orders[].ticketTypes[].ticketTypeName Name of the ticket type
orders[].ticketTypes[].quantity How many of this type are in the order
pagination.currentPage Zero-based current page index
pagination.pageSize Number of items per page
pagination.totalPages Total number of pages
pagination.totalElements Total matching orders
pagination.hasNext true if a next page exists
pagination.hasPrevious true if a previous page exists

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Access denied: you are not the organizer of this event",
  "action_time": "2026-05-23T10:30:45",
  "data": "Access denied: you are not the organizer of this event"
}

Standard Error Types:


Ticket Series Generation

Format: {TICKET_CODE}-{COUNTER}

The ticket code is the first 5 characters of the ticket type name (uppercased, spaces removed). The counter is a zero-padded incrementing integer per ticket type, managed with pessimistic locking to prevent duplicates.

Examples:

The counter never resets — ticket series are globally unique per type forever.


JWT-Signed QR Codes

Each ticket's qrCode field is a full RSA-signed JWT (2048-bit key pair generated when the event is published). The JWT payload contains:

[
  {
    "dayName": "Day 1 - Opening Day",
    "startDateTime": "2025-12-15T09:00:00+03:00",
    "endDateTime": "2025-12-15T18:00:00+03:00",
    "description": "Conference Opening & Keynotes"
  }
]

Why JWT? Scanners can verify the ticket offline using the public key without a database lookup. Any tampering breaks the signature. The embedded eventSchedules lets scanners enforce per-day check-in rules without an internet connection.


Multi-Day Check-Ins

For multi-day events, each ticket can be checked in once per day. The checkIns array stores the full history. The hasBeenCheckedIn, lastCheckedInAt, lastCheckedInBy, lastCheckInLocation, and lastCheckInDayName convenience fields reflect the most recent entry.


Event Snapshots

Event details (title, venue, times, organizer contacts) are captured into the booking record at the moment of purchase and never change. This means:


Booking Creation Flow

Bookings are created automatically by EventPaymentCompletedListener after a successful payment — never through a direct API call. The flow:

  1. Fetch checkout session, event, and ticket type from the database
  2. Generate a unique booking reference (EVT-{8-char-UUID})
  3. Create ticket instances (one per quantity unit, tagged with attendee and buyer info)
  4. Sign a JWT QR code for each ticket using the event's RSA private key
  5. Snapshot current event and organizer details into the booking record
  6. Save the booking and mark the checkout session as COMPLETED
  7. Publish a BookingCreatedEvent to trigger email notifications to buyer, attendees, and organizer

The entire flow runs in a single transaction — no partial bookings are created.


Access Control Rules

Actor Endpoint Allowed?
Booking owner (customer) GET /booking-orders/{id} Yes
Event organizer GET /booking-orders/{id} Yes
Admin (SUPER_ADMIN/STAFF_ADMIN) GET /booking-orders/{id} Yes
Other authenticated user GET /booking-orders/{id} No — 403
Any authenticated user GET /my-bookings Yes (own bookings only)
Ticket owner GET /tickets/{id}/pdf Yes
Other authenticated user GET /tickets/{id}/pdf No — 403
Event organizer GET /booking-orders/event/{id} Yes
Admin GET /booking-orders/event/{id} Yes
Non-organizer user GET /booking-orders/event/{id} No — 403

Booking and Ticket Status

Booking Status:

Status Description
CONFIRMED Active booking, tickets are valid
CANCELLED Booking cancelled (placeholder — cancellation logic not yet implemented)

Ticket Instance Status:

Status Description
ACTIVE Ticket ready for use; can be scanned
USED Ticket has been checked in; still scannable for additional days
CANCELLED Ticket cancelled; cannot be used for entry

Date/Time Formats

Type Format Example Used For
LocalDateTime YYYY-MM-DDTHH:mm:ss 2025-12-15T09:00:00 bookedAt, cancelledAt, event snapshots
ZonedDateTime YYYY-MM-DDTHH:mm:ss±HH:mm 2025-12-15T09:00:00+03:00 validFrom, validUntil, checkInTime

ZonedDateTime fields carry the event's local timezone offset and display correctly regardless of the viewer's locale.


Endpoint Summary

# Endpoint Access
1 GET /e-events/booking-orders/{bookingId} Owner / Organizer / Admin
2 GET /e-events/booking-orders/my-bookings Any authenticated user
3 GET /e-events/booking-orders/tickets/{ticketInstanceId}/pdf Ticket owner
4 GET /e-events/booking-orders/event/{eventId} Event organizer / Admin

Event Check-In System API

Author: Josh, Lead Backend Team
Last Updated: 2025-12-11
Version: v1.0

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

Short Description: The Event Check-In System API provides secure ticket validation and scanner management for event entry. This API enables organizers to generate registration tokens (like WhatsApp's "Link Device"), scanners to register for events, and perform real-time ticket validation with JWT signature verification. The system supports multi-day events with per-day check-in tracking, device fingerprinting for security, automatic scanner revocation, and comprehensive duplicate detection.

Hints:


API Overview

Registration Token Endpoints

  1. POST /check-in/tokens/generate - Generate registration token (organizer)
  2. GET /check-in/tokens/validate/{token} - Validate registration token

Scanner Management Endpoints

  1. POST /check-in/scanners/register - Register scanner device
  2. GET /check-in/scanners/event/{eventId} - Get all scanners for event
  3. GET /check-in/scanners/event/{eventId}/active - Get active scanners
  4. POST /check-in/scanners/{scannerId}/revoke - Revoke scanner

Ticket Validation Endpoint

  1. POST /check-in/validate - Validate ticket and check-in

Response Structures

RegistrationTokenResponse

{
  "tokenId": "uuid",
  "token": "REG-ABC12345-XYZ67890",
  "eventId": "uuid",
  "eventName": "East African Tech Summit 2025",
  "scannerName": "Gate A - Main Entrance",
  "expiresAt": "2025-12-11T10:35:00Z",
  "validityMinutes": 5,
  "remainingSeconds": 240,
  "qrCodeData": "scannerapp://register?token=REG-ABC12345-XYZ67890",
  "isValid": true,
  "used": false
}

ScannerResponse

{
  "scannerId": "uuid-string",
  "name": "Gate A - Main Entrance",
  "eventId": "uuid",
  "eventName": "East African Tech Summit 2025",
  "status": "ACTIVE",
  "deviceFingerprint": "abc123def456...",
  "createdAt": "2025-12-11T10:30:00Z",
  "credentials": "eyJhbGc...",
  "publicKey": "MIIBIjANBgkqhkiG9w0...",
  "revocationReason": null
}

ValidateTicketResponse

{
  "valid": true,
  "status": "VALID",
  "message": "✅ Entry granted for Day 1 - Opening Day. Welcome!",
  "ticketInstanceId": "uuid",
  "ticketTypeName": "VIP Pass",
  "ticketSeries": "VIP-0001",
  "attendeeName": "John Doe",
  "attendeeEmail": "john@example.com",
  "eventName": "East African Tech Summit 2025",
  "bookingReference": "EVT-A3F4B21C",
  "alreadyCheckedIn": false,
  "previousCheckInTime": null,
  "previousCheckInLocation": null,
  "currentCheckInTime": "2025-12-15T09:15:00+03:00",
  "validationMode": "ONLINE",
  "scannerName": "Gate A - Main Entrance",
  "dayName": "Day 1 - Opening Day"
}

Endpoints

1. Generate Registration Token

Endpoint: POST /check-in/tokens/generate

Access: 🔒 Event Organizer Only

Request:

{
  "eventId": "uuid",
  "scannerName": "Gate A - Main Entrance"
}

Success Response: Returns RegistrationTokenResponse

Behavior:

Errors:


2. Validate Registration Token

Endpoint: GET /check-in/tokens/validate/{token}

Access: 🔓 Public (for scanner apps)

Success Response: Returns RegistrationTokenResponse with validity status

Use Case: Scanner app validates token before registration


3. Register Scanner

Endpoint: POST /check-in/scanners/register

Access: 🔓 Public (uses registration token)

Request:

{
  "registrationToken": "REG-ABC12345-XYZ67890",
  "deviceFingerprint": "abc123def456hash",
  "scannerName": "Gate A - Main Entrance",
  "deviceInfo": "{\"model\":\"iPhone 13\",\"os\":\"iOS 15\"}"
}

Success Response: Returns ScannerResponse with credentials

Behavior:

  1. Validates device fingerprint (10-255 chars)
  2. Validates token (not used, not expired)
  3. Auto-revokes any ACTIVE scanner with same device fingerprint
  4. Generates scanner ID (UUID)
  5. Generates JWT credentials (1 year validity)
  6. Marks token as used (one-time use)

Key Rule: One device → One ACTIVE scanner (across all events)

Errors:


4. Get Scanners for Event

Endpoint: GET /check-in/scanners/event/{eventId}

Access: 🔒 Event Organizer Only

Success Response: Returns array of ScannerResponse

Includes: All scanners (ACTIVE + REVOKED)


5. Get Active Scanners

Endpoint: GET /check-in/scanners/event/{eventId}/active

Access: 🔒 Event Organizer Only

Success Response: Returns array of ScannerResponse (ACTIVE only)


6. Revoke Scanner

Endpoint: POST /check-in/scanners/{scannerId}/revoke?reason=Suspicious activity

Access: 🔒 Event Organizer Only

Success Response: 200 OK with confirmation message

Behavior:


7. Validate Ticket and Check-In

Endpoint: POST /check-in/validate

Access: 🔒 Scanner credentials required

Request:

{
  "jwtToken": "eyJhbGc...",
  "scannerId": "uuid-string",
  "deviceFingerprint": "abc123def456hash",
  "checkInLocation": "Gate A"
}

Success Response: Returns ValidateTicketResponse

Validation Flow:

  1. Validate Scanner: Active status, fingerprint match
  2. Verify JWT: RSA signature with event's public key
  3. Find Current Day: Match current time to event schedules
  4. Find Booking: Locate ticket in database
  5. Check Duplicate: Already checked in for this day?
  6. Create Check-In: Add check-in record for current day
  7. Update Stats: Increment scanner counters

Check-In Strategies:

Validation Statuses:

Response Examples:

Success (VALID):

{
  "success": true,
  "message": "✅ Entry granted for Day 1 - Opening Day. Welcome!",
  "data": {
    "valid": true,
    "status": "VALID",
    "attendeeName": "John Doe",
    "dayName": "Day 1 - Opening Day",
    "currentCheckInTime": "2025-12-15T09:15:00+03:00"
  }
}

Duplicate Check-In:

{
  "success": false,
  "message": "❌ Ticket already used for Day 1 - Opening Day. Entry denied.",
  "data": {
    "valid": false,
    "status": "DUPLICATE",
    "alreadyCheckedIn": true,
    "previousCheckInTime": "2025-12-15T09:00:00+03:00",
    "previousCheckInLocation": "Gate A"
  }
}

System Flows

Registration Flow

Step 1: Organizer Generates Token

Organizer → POST /tokens/generate
← Returns: Token + QR code data

Step 2: Scanner Scans QR Code

Scanner App → Scans QR code
Extracts: "scannerapp://register?token=REG-..."

Step 3: Scanner Validates Token (Optional)

Scanner App → GET /tokens/validate/{token}
← Confirms: Token valid, event details

Step 4: Scanner Registers

Scanner App → POST /scanners/register
Sends: Token, device fingerprint, name
← Receives: Scanner credentials (JWT)

Step 5: Scanner Stores Credentials

Scanner App → Saves: credentials, publicKey
Ready to validate tickets offline

Ticket Validation Flow

Step 1: Scanner Scans Ticket QR

Scanner → Reads JWT from QR code

Step 2: Offline Validation (Optional)

Scanner → Verifies JWT signature with public key
Checks: Expiry, event match

Step 3: Online Check-In

Scanner → POST /validate
Sends: JWT, scannerId, fingerprint

Step 4: System Validates

System → Validates scanner status
System → Verifies JWT signature
System → Finds current event day
System → Checks duplicate
System → Records check-in

Step 5: Response to Scanner

System → Returns validation result
Scanner → Shows success/error to staff

Multi-Day Event Support

How It Works

Single-Day Event:

Multi-Day Event (e.g., 3-day festival):

Example: 3-Day Festival

JWT contains schedules:

{
  "eventSchedules": [
    {
      "dayName": "Day 1 - Friday Night",
      "startDateTime": "2025-12-15T18:00:00+03:00",
      "endDateTime": "2025-12-15T23:59:00+03:00"
    },
    {
      "dayName": "Day 2 - Saturday",
      "startDateTime": "2025-12-16T10:00:00+03:00",
      "endDateTime": "2025-12-16T23:59:00+03:00"
    },
    {
      "dayName": "Day 3 - Sunday",
      "startDateTime": "2025-12-17T10:00:00+03:00",
      "endDateTime": "2025-12-17T20:00:00+03:00"
    }
  ]
}

Check-In Timeline:

Friday 18:30 → Check-in for "Day 1 - Friday Night" ✅
Friday 19:00 → Duplicate for "Day 1" ❌
Saturday 11:00 → Check-in for "Day 2 - Saturday" ✅
Saturday 15:00 → Duplicate for "Day 2" ❌
Sunday 12:00 → Check-in for "Day 3 - Sunday" ✅

Validation Logic:

  1. System finds current time: Saturday 11:00
  2. Matches to event schedule: "Day 2 - Saturday"
  3. Checks if ticket already checked in for "Day 2"
  4. If no → Allow check-in
  5. If yes → Reject as duplicate

Check-In Window Strategies

1. HOURS_BEFORE (Default)

Configuration:

Window: 2 hours before event until 30 minutes after event ends

Example:

Event: 09:00 - 18:00
Check-in allowed: 07:00 - 18:30

Use Case: Conferences, concerts

2. SPECIFIC_TIME

Configuration:

Window: Same time window each event day

Example:

3-day event (Dec 15-17)
Check-in allowed: 08:00-23:00 each day

Use Case: Multi-day festivals with consistent entry hours

3. ALL_DAY

Configuration: None needed

Window: Entire event date (00:00 - 23:59)

Example:

Event date: Dec 15
Check-in allowed: Dec 15 00:00 - Dec 15 23:59

Use Case: All-day events, exhibitions

4. EXACT_TIME

Configuration: None

Window: Only during event start-end times

Example:

Event: 14:00 - 17:00
Check-in allowed: 14:00 - 17:00 only

Use Case: Strict timing events

5. AS_DAY_START

Configuration:

Window: From start of event day (00:00) until event end + grace

Example:

Event: Dec 15 18:00 - 23:00
Check-in allowed: Dec 15 00:00 - 23:30

Use Case: Evening events with early arrival


Security Features

Device Fingerprinting

Purpose: Prevent credential theft

How It Works:

  1. Scanner generates fingerprint from device hardware
  2. Fingerprint sent with every request
  3. System validates fingerprint matches registered device
  4. Mismatch → Reject (credentials stolen)

Fingerprint Components (example):

const fingerprint = SHA256(
  deviceModel + 
  osVersion + 
  hardwareId + 
  appInstallId
);

JWT Credentials

Scanner Credentials (1 year validity):

{
  "scannerId": "uuid",
  "eventId": "uuid",
  "type": "scanner_credential",
  "iat": 1702300000,
  "exp": 1733836000
}

Signed with: Event's RSA private key

Used for: Scanner authentication to API

Ticket JWT Verification

Process:

  1. Scanner receives ticket JWT (QR code)
  2. Scanner verifies signature with event's public key
  3. Scanner can validate offline (no internet needed)
  4. Scanner sends to API for check-in recording

Benefits:


Auto-Revocation System

The Rule

One device → One ACTIVE scanner at a time (across ALL events)

Scenarios

Scenario 1: Device Registers for Different Event

Device ABC has ACTIVE scanner for Event 1
Device ABC registers for Event 2
→ System revokes Event 1 scanner automatically
→ Creates new scanner for Event 2

Scenario 2: Same Event Re-Registration

Device ABC has ACTIVE scanner for Event 1
Device ABC registers again for Event 1
→ System revokes old scanner
→ Creates new scanner (new credentials)

Revocation Message:

"Automatically revoked: Device registered as new scanner for event 'East African Tech Summit 2025'"

Why This Rule?

Prevents:

Allows:


Scanner Statistics

Auto-Tracked Metrics

Success Rate Calculation

successRate = (successfulScans / totalScans) * 100

Updated Automatically


Error Codes Summary

Registration Token Errors

Scanner Registration Errors

Ticket Validation Errors


Best Practices

For Organizers

✅ Generate tokens right before scanner setup
✅ Use descriptive scanner names (e.g., "Gate A - Main")
✅ Monitor scanner activity via dashboard
✅ Revoke suspicious scanners immediately
✅ Test scanner before event starts

For Scanner App Developers

✅ Generate stable device fingerprint
✅ Store credentials securely (encrypted)
✅ Implement offline validation first
✅ Sync check-ins when online
✅ Show clear success/error messages
✅ Handle device fingerprint mismatch gracefully

For Event Staff

✅ Keep scanners charged
✅ Verify scanner name matches gate
✅ Watch for duplicate warnings
✅ Report technical issues immediately
✅ Use backup manual verification if needed


Quick Reference

Token Lifetime

Device Fingerprint

Scanner Name

Status Flow

Registration Token: unused → used (one-time)
Scanner: ACTIVE → REVOKED (permanent)
Validation: VALID | DUPLICATE | INVALID_SIGNATURE | EXPIRED | NOT_FOUND | REVOKED

Integration Guide

Organizer Dashboard Integration

// 1. Generate token
POST /check-in/tokens/generate
{
  eventId: "uuid",
  scannerName: "Gate A"
}

// 2. Display QR code
<QRCode value={response.qrCodeData} />

// 3. Show token expiry countdown
remainingTime = response.remainingSeconds

// 4. List active scanners
GET /check-in/scanners/event/{eventId}/active

Scanner App Integration

// 1. Scan registration QR code
const token = extractTokenFromQR(qrData);

// 2. Register device
POST /check-in/scanners/register
{
  registrationToken: token,
  deviceFingerprint: generateFingerprint(),
  scannerName: "Gate A",
  deviceInfo: JSON.stringify(deviceDetails)
}

// 3. Store credentials
secureStorage.save('credentials', response.credentials);
secureStorage.save('publicKey', response.publicKey);
secureStorage.save('scannerId', response.scannerId);

// 4. Validate tickets
while (eventActive) {
  const ticketJWT = scanTicketQR();
  
  // Offline validation
  const offlineValid = verifyJWT(ticketJWT, publicKey);
  
  if (offlineValid) {
    // Online check-in
    POST /check-in/validate
    {
      jwtToken: ticketJWT,
      scannerId: scannerId,
      deviceFingerprint: generateFingerprint(),
      checkInLocation: "Gate A"
    }
  }
}

Conclusion

The Event Check-In System provides enterprise-grade security with:

WhatsApp-Style Registration: Scan QR to link device
Device Security: Fingerprint validation prevents theft
Offline Capable: JWT verification without internet
Multi-Day Support: Per-day check-in tracking
Auto-Revocation: Clean scanner sessions per event
Flexible Timing: 5 check-in window strategies
Duplicate Prevention: Same-day re-entry blocked
Real-Time Stats: Automatic success/fail tracking

Organizer Analytics API

Author: Josh, Lead Backend Team
Last Updated: 2025-12-11
Version: v1.0

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

Short Description: The Organizer Analytics API provides comprehensive financial and performance insights for event organizers. This API enables organizers to track total collections across all events, analyze revenue by event with filters, monitor individual event performance with detailed metrics, and visualize revenue trends over time with monthly/yearly breakdowns. The system calculates escrow holdings, released payments, ticket sales, attendance rates, and sell-out percentages automatically.

Hints:


Response Structures

CollectionSummaryResponse

{
  "eventMetrics": {
    "totalEvents": 15,
    "upcomingEvents": 5,
    "ongoingEvents": 1,
    "completedEvents": 8,
    "cancelledEvents": 1
  },
  "collectionMetrics": {
    "totalTicketsSold": 2500,
    "totalRevenue": 5000000.00,
    "inEscrow": 1200000.00,
    "released": 3800000.00,
    "refunded": 0.00,
    "pendingRefunds": 0.00
  },
  "topEvent": {
    "eventId": "uuid",
    "eventTitle": "East African Tech Summit 2025",
    "revenue": 1500000.00,
    "ticketsSold": 500,
    "attendanceRate": 92.5
  }
}

EventRevenueResponse

{
  "events": [
    {
      "eventId": "uuid",
      "eventTitle": "East African Tech Summit 2025",
      "eventDate": "2025-12-15T09:00:00",
      "status": "PUBLISHED",
      "ticketsSold": 500,
      "totalRevenue": 1500000.00,
      "inEscrow": 1500000.00,
      "released": 0.00,
      "refunded": 0.00,
      "attendanceRate": 0.0,
      "totalCapacity": 1000,
      "sellOutPercentage": 50.0
    }
  ],
  "pagination": {
    "currentPage": 0,
    "pageSize": 20,
    "totalPages": 3,
    "totalElements": 45,
    "hasNext": true,
    "hasPrevious": false
  }
}

EventPerformanceResponse

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2025",
  "eventDate": "2025-12-15T09:00:00",
  "status": "COMPLETED",
  "financials": {
    "totalRevenue": 1500000.00,
    "inEscrow": 0.00,
    "released": 1500000.00,
    "refunded": 0.00,
    "averageTicketPrice": 3000.00
  },
  "ticketMetrics": {
    "totalCapacity": 1000,
    "totalSold": 500,
    "totalRemaining": 500,
    "sellOutPercentage": 50.0
  },
  "attendanceMetrics": {
    "totalTickets": 500,
    "checkedIn": 462,
    "noShows": 38,
    "attendanceRate": 92.4
  },
  "timeline": {
    "createdAt": "2025-10-01T10:00:00",
    "publishedAt": "2025-10-05T14:30:00",
    "firstSaleAt": "2025-10-06T09:15:00",
    "eventDate": "2025-12-15T09:00:00",
    "completedAt": "2025-12-15T18:00:00"
  }
}

RevenueTrendResponse

{
  "period": "MONTHLY",
  "totalEvents": 12,
  "trends": [
    {
      "label": "JAN",
      "year": 2025,
      "month": 1,
      "eventsCount": 2,
      "ticketsSold": 350,
      "revenue": 875000.00,
      "inEscrow": 0.00,
      "released": 875000.00,
      "averageAttendanceRate": 88.5,
      "averageSellOutRate": 62.0
    }
  ]
}

Endpoints

1. Get Collection Summary

Endpoint: GET /analytics/collections/summary

Access: 🔒 Organizer Only

Success Response: Returns CollectionSummaryResponse

Success Response Message: "Collection summary retrieved"

Behavior:

Metrics Included:


2. Get Event Revenue

Endpoint: GET /analytics/collections/by-event?status=PUBLISHED&startDate=2025-01-01&endDate=2025-12-31&page=0&size=20

Access: 🔒 Organizer Only

Query Parameters:

Parameter Type Required Description
status string No Filter by event status (PUBLISHED, COMPLETED, etc.)
startDate date (ISO) No Filter events from this date (2025-01-01)
endDate date (ISO) No Filter events until this date (2025-12-31)
page integer No Page number (0-indexed, default: 0)
size integer No Items per page (default: 20)

Success Response: Returns EventRevenueResponse with pagination

Success Response Message: "Event revenue retrieved"

Behavior:


3. Get Event Performance

Endpoint: GET /analytics/performance/{eventId}

Access: 🔒 Event Organizer Only

Path Parameters:

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

Success Response: Returns EventPerformanceResponse

Success Response Message: "Event performance retrieved"

Behavior:

Metrics Sections:

Errors:


4. Get Revenue Trends

Endpoint: GET /analytics/trends?period=MONTHLY&year=2025

Access: 🔒 Organizer Only

Query Parameters:

Parameter Type Required Description
period string No MONTHLY or YEARLY (default: MONTHLY)
year integer No Year to analyze (default: current year)

Success Response: Returns RevenueTrendResponse

Success Response Message: "Revenue trends retrieved"

Behavior:

MONTHLY Period:

YEARLY Period:

Each Period Includes:


Key Calculations

Escrow vs Released

In Escrow (Not yet paid to organizer):

Released (Paid to organizer):

Example:

Event A: PUBLISHED, Revenue: 500,000 TZS → In Escrow
Event B: HAPPENING, Revenue: 300,000 TZS → In Escrow  
Event C: COMPLETED, Revenue: 1,200,000 TZS → Released

Total Revenue: 2,000,000 TZS
In Escrow: 800,000 TZS
Released: 1,200,000 TZS

Attendance Rate

Attendance Rate = (Checked-In Tickets / Total Tickets) × 100

Example:

Sell-Out Percentage

Sell-Out % = (Tickets Sold / Total Capacity) × 100

Example:

Average Ticket Price

Average Price = Total Revenue / Total Tickets

Example:


Event Status Flow

DRAFT → PUBLISHED → HAPPENING → COMPLETED
   ↓                               ↓
CANCELLED                     (Revenue Released)

Financial Impact by Status:


Top Performer Logic

Selection Criteria: Highest total revenue

Metrics Included:

Use Case: Dashboard highlight showing best-performing event


Use Cases

Dashboard Overview

GET /analytics/collections/summary

Shows:
- Total events across all statuses
- Total revenue (all time)
- Current escrow balance
- Released payments
- Top performing event

Event List with Filters

GET /analytics/collections/by-event?status=COMPLETED&page=0&size=20

Shows:
- All completed events
- Revenue details per event
- Attendance and sell-out rates
- Paginated for easy navigation

Deep Dive on Specific Event

GET /analytics/performance/550e8400-e29b-41d4-a716-446655440000

Shows:
- Complete financial breakdown
- Ticket sales metrics
- Attendance statistics
- Event timeline

Monthly Revenue Analysis

GET /analytics/trends?period=MONTHLY&year=2025

Shows:
- Revenue per month in 2025
- Event count trends
- Attendance patterns
- Sell-out trends

Year-Over-Year Comparison

GET /analytics/trends?period=YEARLY

Shows:
- Revenue by year
- Growth trends
- Performance evolution

Best Practices

For Organizers

✅ Check collection summary regularly
✅ Monitor escrow balance (upcoming payouts)
✅ Track attendance rates to improve future events
✅ Use trends to identify peak seasons
✅ Review individual event performance post-event

For Developers

✅ Cache collection summary (refresh hourly)
✅ Paginate event lists (default 20 items)
✅ Display financial amounts clearly (currency formatting)
✅ Show percentage metrics with 1 decimal (92.4%)
✅ Provide export functionality for trends


Quick Reference

HTTP Status Codes

Date Formats

Currency

Percentage Format

Pagination


Conclusion

The Organizer Analytics API provides comprehensive insights with:

Collection Summary: Total revenue, escrow, and top performers
Event Revenue: Filterable, paginated event list
Event Performance: Deep dive into individual event metrics
Revenue Trends: Monthly/yearly pattern analysis
Real-Time: Auto-calculated from latest bookings
Organizer-Focused: Restricted to event owners only

Events Applicant Form API

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2026-05-22 Version: v1.2

Base URL: https://your-api-domain.com/api/v1/e-events/applicant-form

Short Description: The Applicant Form API allows event organizers to attach an optional custom multi-page form to their event. When enabled, attendees are prompted to fill in the form as part of the registration flow. The form is fully scoped to the event — all endpoints use eventId as the primary key, so the organizer never needs to track a separate formId. The entire feature is optional; if never enabled, the event registration proceeds with no form step.

Hints:


Form Structure — Layers

┌─────────────────────────────────────────────────────────────────────────┐
│  FORM                                                                   │
│  title · description · settings · cover page                            │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  PAGE 1                                                           │  │
│  │  title · description · action button text                         │  │
│  │                                                                   │  │
│  │   ┌─────────────────────────────────────────────────────────┐     │  │
│  │   │ FIELD  (e.g. TEXT, EMAIL, DATE, FILE, RATING …)         │     │  │
│  │   │ label · placeholder · required · validation rules       │     │  │
│  │   └─────────────────────────────────────────────────────────┘     │  │
│  │                                                                   │  │
│  │   ┌─────────────────────────────────────────────────────────┐     │  │
│  │   │ FIELD  (DROPDOWN / RADIO / CHECKBOX)                    │     │  │
│  │   │ label · required                                        │     │  │
│  │   │   ┌──────────┐  ┌──────────┐  ┌──────────┐              │     │  │
│  │   │   │ Option 1 │  │ Option 2 │  │ Option 3 │  …           │     │  │
│  │   │   └──────────┘  └──────────┘  └──────────┘              │     │  │
│  │   └─────────────────────────────────────────────────────────┘     │  │
│  │                                                                   │  │
│  │   ┌─────────────────────────────────────────────────────────┐     │  │
│  │   │ FIELD  (HEADER — display only, no answer stored)        │     │  │
│  │   │ label (section divider text)                            │     │  │
│  │   └─────────────────────────────────────────────────────────┘     │  │
│  │                                                                   │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  PAGE 2                                                           │  │
│  │   ┌──────────────────────────┐  ┌──────────────────────────┐      │  │
│  │   │ FIELD                    │  │ FIELD                    │  …   │  │
│  │   └──────────────────────────┘  └──────────────────────────┘      │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  PAGE N  …                                                        │  │
│  └───────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────┘

Rules at a glance:


Organizer Workflow

  [Organizer]
       │
       │  POST /events/{eventId}/enable
       ▼
  ┌─────────────────────────────────────┐
  │   Form ENABLED (empty)              │
  │   - Form created internally         │
  │   - No pages yet (pages: [])        │
  │   - Config saved (displayTime etc.) │
  └──────────────┬──────────────────────┘
                 │
                 │  Load builder state (on re-open):
                 │  GET  /events/{eventId}/full-form
                 │
                 │  Build the form structure:
                 │
                 │  POST /events/{eventId}/pages
                 │  POST /events/{eventId}/pages/{pageId}/fields
                 │  POST /events/{eventId}/fields/{fieldId}/options
                 │         (DROPDOWN / RADIO / CHECKBOX only)
                 │
                 │  (Optional bulk / clone shortcuts)
                 │  POST /events/{eventId}/pages/bulk
                 │  POST /events/{eventId}/pages/{pageId}/fields/bulk
                 │  POST /events/{eventId}/pages/{pageId}/clone
                 │  POST /events/{eventId}/fields/{fieldId}/clone
                 │
                 │  Preview before publishing:
                 │  GET  /events/{eventId}/preview/metadata
                 │  GET  /events/{eventId}/preview/pages/{pageNumber}
                 │  POST /events/{eventId}/preview/pages/{pageNumber}/validate
                 ▼
  ┌─────────────────────────────────────┐
  │   Form ready — event can publish    │
  │   ⚠ Publish blocked if 0 pages     │
  └─────────────────────────────────────┘

Attendee Submission Journey

  [Attendee — event must be PUBLISHED and within registration window]
         │
         │  POST /events/{eventId}/start
         ▼
  ┌──────────────────────────────────────────────────┐
  │  Response created (DRAFT)                        │
  │  or existing draft returned (idempotent)         │
  │  status: DRAFT, completedPageIds: [], index: 0   │
  └──────────────┬───────────────────────────────────┘
                 │
                 │  ┌──────────────────────────────────────────────────────────┐
                 │  │  For each page (repeat until all pages completed):       │
                 │  │                                                          │
                 │  │  [Background auto-save while typing]                     │
                 │  │  PUT /pages/{pageId}/save?moveToNextPage=false           │
                 │  │  → No validation. Saves whatever is there. Always 200.  │
                 │  │  → completedPageIds unchanged.                           │
                 │  │                                                          │
                 │  │  [User clicks "Next"]                                    │
                 │  │  PUT /pages/{pageId}/save?moveToNextPage=true            │
                 │  │       ├─ Validation FAILS → returns errors, nothing      │
                 │  │       │  saved, page stays, success: false               │
                 │  │       └─ Validation PASSES → answers saved, page        │
                 │  │          added to completedPageIds, index advances       │
                 │  └──────────────────────────────────────────────────────────┘
                 │
                 │  POST /events/{eventId}/submit
                 │       ├─ Any page NOT in completedPageIds → 422 PAGE_INCOMPLETE
                 │       └─ All pages complete + required fields present → SUBMITTED
                 ▼
  ┌──────────────────────────────────────────────────┐
  │  Response SUBMITTED                              │
  │  status: SUBMITTED, completionTimeSeconds set    │
  └──────────────────────────────────────────────────┘

Response Status Flow

  DRAFT ──────────────────────────────► SUBMITTED
    │                                        │
    │  (withdraw — not in this API)          │  (organizer action — outside scope)
    ▼                                        ▼
  WITHDRAWN                           UNDER_REVIEW → APPROVED / REJECTED

Standard Response Format

All API responses follow a consistent structure using the Globe Response Builder pattern.

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-02-17T10:30:45",
  "data": "Error description"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, CREATED, BAD_REQUEST, etc.)
message string Human-readable description of the result
action_time string ISO 8601 timestamp of when the response was generated
data object / string Response payload on success; error detail on failure

HTTP Method Badge Standards


Shared Response Object Definitions

A. ApplicantFormSetupResponse

Returned only by the Enable Form endpoint (Endpoint 1).

Field Type Description
config object The saved EventApplicantFormEntity — see config fields below
config.id UUID Config record ID
config.formId UUID Internal Form Builder form ID (not needed for subsequent API calls)
config.displayTime string BEFORE_CHECKOUT or AFTER_CHECKOUT
config.isRequiredOnline boolean Whether online buyers must complete the form
config.applyToAtDoor boolean Whether walk-in attendees see the form
config.createdAt ZonedDateTime Config creation timestamp
config.updatedAt ZonedDateTime Last update timestamp
form object Full FormResponse — see FormResponse definition below

B. EventApplicantFormEntity (Config Object)

Returned by the Update Settings endpoint.

Field Type Description
id UUID Config record ID
formId UUID Internal Form Builder form ID
displayTime string BEFORE_CHECKOUT or AFTER_CHECKOUT
isRequiredOnline boolean Whether online buyers must complete the form
applyToAtDoor boolean Whether walk-in attendees see the form
createdAt ZonedDateTime ISO 8601 with offset
updatedAt ZonedDateTime ISO 8601 with offset

C. FormResponse (Full Form Object)

Field Type Description
formId UUID Form identifier (Form Builder internal ID)
title string Auto-set to "Attendee Questions - {event title}" on enable
description string Form description
settings object Form open/close settings (see FormSettings below)
settings.acceptResponses boolean Whether the form is accepting responses
settings.allowMultipleSubmissions boolean Whether a user can submit more than once
settings.responseStartTime Instant UTC datetime when responses open
settings.responseDeadline Instant UTC datetime when responses close
settings.allowSaveDraft boolean Whether respondents can save progress
coverPage object Optional cover page config
createdBy string Username of creator
createdAt LocalDateTime Creation timestamp
pages[] array Ordered list of PageResponse objects

D. PageResponse

Field Type Description
pageId UUID Page identifier
title string Page heading shown to attendee
description string Subheading or instruction text
displayOrder integer 1-based position within the form
actionButtonText string Label for the next/submit button
fields[] array Ordered list of FieldResponse objects (soft-deleted excluded)

E. FieldResponse

Field Type Description
fieldId UUID Field identifier
type string TEXT, TEXTAREA, EMAIL, PHONE, NUMBER, URL, DATE, TIME, DATETIME, DROPDOWN, RADIO, CHECKBOX, FILE, RATING, HEADER
label string Field label. For HEADER type this is the section title
description string Helper text shown below the field
placeholder string Input placeholder text
displayOrder integer 1-based order within the page
required boolean Whether the field must be answered. Always false for HEADER
validation object See FieldValidation below
options[] array OptionResponse entries — only for DROPDOWN, RADIO, CHECKBOX

FieldValidation Object

Field Applicable To Description
minLength / maxLength TEXT, TEXTAREA Character count constraints
pattern / patternMessage TEXT Regex pattern and custom error message
min / max NUMBER Numeric range
minDate / maxDate DATE Date range (YYYY-MM-DD)
minSelections / maxSelections CHECKBOX Selection count constraints
maxSizeMb / accept FILE File size limit and accepted MIME types

F. OptionResponse

Field Type Description
optionId UUID Option identifier
label string Display label shown to attendee
displayOrder integer 1-based order within the field

G. FormResponseObject (Attendee Submission)

Field Type Description
responseId UUID Unique response identifier
formId UUID Internal form ID
submittedBy string Username of the attendee
status string DRAFT, SUBMITTED, UNDER_REVIEW, APPROVED, REJECTED, WITHDRAWN
completedPageIds array UUIDs of pages marked complete
currentPageIndex integer 0-based index of current page
startedAt LocalDateTime When the attendee started the form
submittedAt LocalDateTime When the response was submitted
completionTimeSeconds integer Total seconds from start to submit
answers[] array See AnswerResponse below

AnswerResponse

Field Type Description
answerId UUID Answer identifier
fieldId UUID Field ID (null if field was hard-deleted)
fieldLabel string Label snapshot — preserved even if field is later deleted
fieldType string Type snapshot — preserved even if field is later deleted
fieldDeleted boolean true when the source field has been soft-deleted
value any Submitted value. String for text/single-choice, array of strings for checkbox, number for NUMBER/RATING
answeredAt LocalDateTime When this answer was saved
fileUrl / fileName / fileSize / fileType various File upload metadata (FILE fields only)

H. BulkOperationResult

Field Type Description
successCount integer Number of items successfully processed
failureCount integer Number of items that failed
errors[] array Error message per failed item
createdFields[] array FieldResponse objects (bulk field operations)
createdPages[] array PageResponse objects (bulk page operations)

I. BulkDeleteResult

Field Type Description
successCount integer Number of items successfully deleted
failureCount integer Number of items that failed
errors[] array Error message per failed item
deletedIds[] array UUIDs of successfully deleted items

J. SavePageResponse

Returned by the Save Page Answers endpoint (Endpoint 27). The shape of the response is the same whether the save succeeded or validation failed — the client always reads saved and isValid to decide what to do next.

Field Type Description
eventId UUID The event ID
saved boolean true if answers were actually written to the database. false if validation failed and nothing was saved
savedAt LocalDateTime Timestamp of the save. null when saved is false
isValid boolean true if all fields on the page passed validation. Mirrors saved
errors array / null null when isValid is true. Array of FieldValidationError when validation failed
errors[].fieldId UUID The field that failed validation
errors[].fieldLabel string Label of the failing field (e.g. "Email")
errors[].errorCode string Machine-readable error type: REQUIRED, INVALID_FORMAT, INVALID_TYPE, VALIDATION_FAILED
errors[].message string Human-readable error description (e.g. "must be valid email")

Note: totalFieldsOnPage, answeredFieldsOnPage, and overallProgress are reserved fields in the response object but are not currently populated — they will be null and omitted from the JSON (@JsonInclude(NON_NULL)).

K. FormAnalytics

Field Type Description
formId UUID Form identifier
formTitle string Form title
stats.totalStarted integer Total responses started (including drafts)
stats.totalDrafts integer Responses still in DRAFT
stats.totalSubmitted integer Responses with SUBMITTED status
stats.totalWithdrawn integer Withdrawn responses
stats.completionRate double Submitted / totalStarted × 100
stats.dropOffRate double Inverse of completion rate
stats.avgCompletionTimeSeconds double Mean time from start to submit
stats.fastestTimeSeconds integer Fastest submission time
stats.slowestTimeSeconds integer Slowest submission time
fieldAnalytics[] array Per-field breakdown
fieldAnalytics[].fieldId UUID Field ID
fieldAnalytics[].fieldLabel string Field label snapshot
fieldAnalytics[].fieldType string Field type snapshot
fieldAnalytics[].fieldDeleted boolean Whether field is soft-deleted
fieldAnalytics[].totalResponses integer Total answers for this field
fieldAnalytics[].choiceDistribution[] array { option, count, percentage } — choice fields
fieldAnalytics[].numericStats object { min, max, avg, median } — NUMBER/RATING fields
fieldAnalytics[].textResponses[] array Raw text answers — TEXT/TEXTAREA fields
dailySubmissions[] array { date, count } — submissions per day

Standard Error Types

Application-Level Exceptions (400–499)

Server-Level Exceptions (500+)


Shared Error Response Examples

401 — Unauthorized:

{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2025-02-17T10:30:45",
  "data": "Token has expired"
}

403 — Forbidden (not organizer):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Only organizer can manage form",
  "action_time": "2025-02-17T10:30:45",
  "data": "Only organizer can manage form"
}

403 — Forbidden (registration closed):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Registration closed",
  "action_time": "2025-02-17T10:30:45",
  "data": "Registration closed"
}

404 — Form not enabled:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Form not enabled",
  "action_time": "2025-02-17T10:30:45",
  "data": "Form not enabled"
}

Endpoints


Form Setup


1. Enable Applicant Form

Purpose: Enables the applicant form for an event. Internally creates a Form Builder form titled "Attendee Questions - {event title}" with no pages. Pages are added separately by the organizer. Only the event organizer can call this. Returns the empty form structure alongside the config.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/enable

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event to enable the form for Must be a valid UUID

Request JSON Sample:

{
  "displayTime": "BEFORE_CHECKOUT",
  "isRequiredOnline": true,
  "applyToAtDoor": false
}

Request Body Parameters:

Parameter Type Required Description Validation
displayTime string No When attendees see the form in the UI flow Enum: BEFORE_CHECKOUT, AFTER_CHECKOUT. Defaults to BEFORE_CHECKOUT
isRequiredOnline boolean No Whether online ticket buyers must complete the form before proceeding Defaults to false
applyToAtDoor boolean No Whether at-door / walk-in attendees also see the form Defaults to false

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Applicant form enabled",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "config": {
      "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "displayTime": "BEFORE_CHECKOUT",
      "isRequiredOnline": true,
      "applyToAtDoor": false,
      "createdAt": "2025-02-17T10:30:45+03:00",
      "updatedAt": null
    },
    "form": {
      "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
      "description": null,
      "settings": null,
      "coverPage": null,
      "createdBy": "amina.hassan",
      "createdAt": "2025-02-17T10:30:45",
      "pages": []
    }
  }
}

Success Response Fields: data is an ApplicantFormSetupResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer, or form is already enabled for this event
404 Event not found

2. Update Form Settings

Purpose: Updates the form configuration for the event — displayTime, isRequiredOnline, and applyToAtDoor. Does not affect the form structure (pages/fields). All fields are optional.

Endpoint: PUT /api/v1/e-events/applicant-form/events/{eventId}/settings

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Request JSON Sample:

{
  "displayTime": "AFTER_CHECKOUT",
  "isRequiredOnline": false,
  "applyToAtDoor": true
}

Request Body Parameters:

Parameter Type Required Description Validation
displayTime string No Updated display time Enum: BEFORE_CHECKOUT, AFTER_CHECKOUT
isRequiredOnline boolean No Updated required flag for online buyers
applyToAtDoor boolean No Updated at-door flag

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Form settings updated",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "displayTime": "AFTER_CHECKOUT",
    "isRequiredOnline": false,
    "applyToAtDoor": true,
    "createdAt": "2025-02-17T10:30:45+03:00",
    "updatedAt": "2025-02-17T11:00:00+03:00"
  }
}

Success Response Fields: data is an EventApplicantFormEntity config object.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

3. Disable Form

Purpose: Disables and permanently removes the applicant form from the event. The underlying Form Builder form is soft-deleted. Existing submitted responses are preserved. This action cannot be undone — to re-enable a form, call Endpoint 1 again which will create a fresh form.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/disable

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Applicant form disabled",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

4. Get Full Form

Purpose: Returns the complete form structure — all pages, fields, and options — in a single call. Intended for the form builder UI to load the current state when the organizer opens the builder. Only the event organizer can call this.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/full-form

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Form retrieved",
  "action_time": "2026-05-21T10:30:45",
  "data": {
    "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
    "description": null,
    "settings": null,
    "coverPage": null,
    "createdBy": "amina.hassan",
    "createdAt": "2026-05-21T10:30:45",
    "pages": [
      {
        "pageId": "1a2b3c4d-...",
        "title": "Personal Details",
        "description": null,
        "displayOrder": 1,
        "actionButtonText": "Next",
        "fields": [
          {
            "fieldId": "c3d4e5f6-...",
            "type": "TEXT",
            "label": "Full Name",
            "description": "As it appears on your ID.",
            "placeholder": "e.g. Amina Hassan",
            "displayOrder": 1,
            "required": true,
            "validation": { "minLength": 2, "maxLength": 100 },
            "options": []
          },
          {
            "fieldId": "d4e5f6a7-...",
            "type": "DROPDOWN",
            "label": "T-Shirt Size",
            "displayOrder": 2,
            "required": true,
            "validation": null,
            "options": [
              { "optionId": "a1b2c3d4-...", "label": "Small (S)", "displayOrder": 1 },
              { "optionId": "b2c3d4e5-...", "label": "Medium (M)", "displayOrder": 2 }
            ]
          }
        ]
      },
      {
        "pageId": "2b3c4d5e-...",
        "title": "Dietary Requirements",
        "displayOrder": 2,
        "actionButtonText": "Submit",
        "fields": []
      }
    ]
  }
}

Success Response Fields: data is a FormResponse with all pages, fields, and options fully nested. Soft-deleted items are excluded. Pages and fields are ordered by displayOrder.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

Page Management


4. Add Page

Purpose: Appends a new blank page to the form. displayOrder is auto-assigned as the next position. Fields are added to the page separately.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/pages

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Request JSON Sample:

{
  "title": "Emergency Contact",
  "description": "In case we need to reach someone on your behalf.",
  "actionButtonText": "Next"
}

Request Body Parameters:

Parameter Type Required Description Validation
title string Yes Page heading shown to attendee Max: 255 characters
description string No Subheading or instruction text Max: 500 characters
actionButtonText string No Label for the page's action button Max: 50 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Page added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "pageId": "2b3c4d5e-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
    "title": "Emergency Contact",
    "description": "In case we need to reach someone on your behalf.",
    "displayOrder": 2,
    "actionButtonText": "Next",
    "fields": []
  }
}

Success Response Fields: data is a PageResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled
422 title is blank

5. Update Page

Purpose: Updates the title, description, or action button label of an existing page.

Endpoint: PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page to update Must be a valid UUID

Request JSON Sample:

{
  "title": "Your Background",
  "actionButtonText": "Continue"
}

Request Body Parameters:

Parameter Type Required Description Validation
title string No Updated page title Max: 255 characters
description string No Updated description Max: 500 characters
actionButtonText string No Updated button label Max: 50 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "PageResponse object" }
}

Success Response Fields: data is a PageResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or page not found

6. Delete Page

Purpose: Deletes a page. Pass ?hard=false (default) for soft delete — the page is hidden but historical answer data is preserved. Pass ?hard=true to permanently remove the page and all its fields.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page to delete Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete, false = soft delete false

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}

When hard=true, message is "Page permanently deleted".

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or page not found

7. Bulk Add Pages (with Fields)

Purpose: Creates multiple pages in one call. Each page can optionally include an inline list of fields.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/pages/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Request JSON Sample:

{
  "pages": [
    {
      "title": "Personal Details",
      "actionButtonText": "Next",
      "fields": [
        { "type": "TEXT", "label": "Full Name", "required": true },
        { "type": "EMAIL", "label": "Email", "required": true }
      ]
    },
    {
      "title": "Dietary Requirements",
      "actionButtonText": "Submit",
      "fields": [
        { "type": "DROPDOWN", "label": "Dietary preference", "required": false }
      ]
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
pages array Yes Pages to create Min 1 item
pages[].title string Yes Page title Max: 255 characters
pages[].description string No Page description Max: 500 characters
pages[].actionButtonText string No Button label Max: 50 characters
pages[].fields array No Fields to create inline See CreateField below
pages[].fields[].type string Yes Field type See FieldType enum
pages[].fields[].label string Yes Field label Max: 255 characters
pages[].fields[].description string No Helper text Max: 500 characters
pages[].fields[].placeholder string No Placeholder text Max: 255 characters
pages[].fields[].required boolean No Required flag Defaults to false
pages[].fields[].validation object No Validation rules See FieldValidation

Note: For DROPDOWN, RADIO, and CHECKBOX fields created via bulk, options must still be added separately using the option endpoints after this call.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "2 pages added, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": [],
    "createdPages": [
      { "...": "PageResponse for page 1" },
      { "...": "PageResponse for page 2" }
    ]
  }
}

Success Response Fields: data is a BulkOperationResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

8. Bulk Delete Pages

Purpose: Deletes multiple pages in one call. Supports both soft delete (default) and hard delete via ?hard query param. Items are processed individually — a failure on one does not stop the rest.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete false

Request JSON Sample:

{
  "ids": ["pageId-1", "pageId-2"]
}

Request Body Parameters:

Parameter Type Required Description Validation
ids array Yes UUIDs of pages to delete Min 1 item

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "2 pages deleted, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": [],
    "deletedIds": ["pageId-1", "pageId-2"]
  }
}

Success Response Fields: data is a BulkDeleteResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found. Individual page errors reported in data.errors[]

9. Clone Page

Purpose: Creates an exact copy of an existing page including all its active fields and options. The clone is appended at the end of the form with "(Copy)" added to the title.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/clone

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page to clone Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Page cloned successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "PageResponse for the cloned page" }
}

Success Response Fields: data is a PageResponse for the new clone.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or page not found

Field Management


10. Add Field

Purpose: Adds a single field to a page. displayOrder is auto-assigned. For DROPDOWN, RADIO, and CHECKBOX types, options can be passed inline in the same request or added separately via the Add Option endpoint. Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields Access Level: 🔒 Protected (Event organizer only) Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page to add the field to Must be a valid UUID

Request Body Parameters:

Parameter Type Required Description Validation
type string Yes Field type Enum: TEXT, TEXTAREA, EMAIL, PHONE, NUMBER, URL, DATE, TIME, DATETIME, DROPDOWN, RADIO, CHECKBOX, FILE, RATING, HEADER
label string Yes Field label (or section title for HEADER) Max: 255 characters
description string No Helper text Max: 500 characters
placeholder string No Input placeholder Max: 255 characters
required boolean No Required flag. Forced false for HEADER Defaults to false
validation object No Validation rules See FieldValidation
options array No Inline options for DROPDOWN, RADIO, CHECKBOX. Ignored for other types. Blank labels are skipped
options[].label string Yes (if options provided) Option label Max: 255 characters

Example 1 — TEXT field with validation

Request:

{
  "type": "TEXT",
  "label": "Full Name",
  "description": "As it appears on your ID.",
  "placeholder": "e.g. Amina Hassan",
  "required": true,
  "validation": {
    "minLength": 2,
    "maxLength": 100
  }
}

Response:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
    "type": "TEXT",
    "label": "Full Name",
    "description": "As it appears on your ID.",
    "placeholder": "e.g. Amina Hassan",
    "displayOrder": 1,
    "required": true,
    "validation": { "minLength": 2, "maxLength": 100 },
    "options": []
  }
}

Example 2 — DROPDOWN field with inline options

Request:

{
  "type": "DROPDOWN",
  "label": "T-Shirt Size",
  "description": "Select your preferred size.",
  "placeholder": "Choose a size",
  "required": true,
  "options": [
    { "label": "Small (S)" },
    { "label": "Medium (M)" },
    { "label": "Large (L)" },
    { "label": "Extra Large (XL)" }
  ]
}

Response:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
    "type": "DROPDOWN",
    "label": "T-Shirt Size",
    "description": "Select your preferred size.",
    "placeholder": "Choose a size",
    "displayOrder": 2,
    "required": true,
    "validation": null,
    "options": [
      { "optionId": "a1b2c3d4-...", "label": "Small (S)", "displayOrder": 1 },
      { "optionId": "b2c3d4e5-...", "label": "Medium (M)", "displayOrder": 2 },
      { "optionId": "c3d4e5f6-...", "label": "Large (L)", "displayOrder": 3 },
      { "optionId": "d4e5f6a7-...", "label": "Extra Large (XL)", "displayOrder": 4 }
    ]
  }
}

Example 3 — RADIO field with inline options

Request:

{
  "type": "RADIO",
  "label": "What is your attendance mode?",
  "description": "Select how you will attend the event.",
  "required": true,
  "options": [
    { "label": "In Person" },
    { "label": "Online" }
  ]
}

Response:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
    "type": "RADIO",
    "label": "What is your attendance mode?",
    "description": "Select how you will attend the event.",
    "placeholder": null,
    "displayOrder": 3,
    "required": true,
    "validation": null,
    "options": [
      { "optionId": "e5f6a7b8-...", "label": "In Person", "displayOrder": 1 },
      { "optionId": "f6a7b8c9-...", "label": "Online", "displayOrder": 2 }
    ]
  }
}

Example 4 — CHECKBOX field with inline options and validation

Request:

{
  "type": "CHECKBOX",
  "label": "Which sessions will you attend?",
  "description": "Select all that apply. You may choose up to 3.",
  "required": false,
  "validation": {
    "minSelections": 1,
    "maxSelections": 3
  },
  "options": [
    { "label": "Morning Session (9am - 12pm)" },
    { "label": "Afternoon Session (1pm - 4pm)" },
    { "label": "Evening Session (6pm - 9pm)" }
  ]
}

Response:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c",
    "type": "CHECKBOX",
    "label": "Which sessions will you attend?",
    "description": "Select all that apply. You may choose up to 3.",
    "placeholder": null,
    "displayOrder": 4,
    "required": false,
    "validation": { "minSelections": 1, "maxSelections": 3 },
    "options": [
      { "optionId": "g7a8b9c0-...", "label": "Morning Session (9am - 12pm)", "displayOrder": 1 },
      { "optionId": "h8b9c0d1-...", "label": "Afternoon Session (1pm - 4pm)", "displayOrder": 2 },
      { "optionId": "i9c0d1e2-...", "label": "Evening Session (6pm - 9pm)", "displayOrder": 3 }
    ]
  }
}

Example 5 — HEADER field (section divider)

Request:

{
  "type": "HEADER",
  "label": "Emergency Contact Information"
}

Response:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
    "type": "HEADER",
    "label": "Emergency Contact Information",
    "description": null,
    "placeholder": null,
    "displayOrder": 5,
    "required": false,
    "validation": null,
    "options": []
  }
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or page not found
422 type is null, or label is blank

11. Update Field

Purpose: Updates the metadata of an existing field. All fields are optional. Field type cannot be changed — if a different type is needed, delete the field and create a new one. Options are managed via dedicated option endpoints. Endpoint: PUT /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId} Access Level: 🔒 Protected (Event organizer only) Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
fieldId UUID Yes The field to update Must be a valid UUID

Request JSON Sample:

{
  "label": "Legal Full Name",
  "required": true,
  "validation": {
    "minLength": 3,
    "maxLength": 150
  }
}

Request Body Parameters:

Parameter Type Required Description Validation
label string No Updated label Max: 255 characters
description string No Updated helper text Max: 500 characters
placeholder string No Updated placeholder Max: 255 characters
required boolean No Updated required flag. Forced false if type is HEADER
validation object No Updated validation rules (full replacement) See FieldValidation

⚠️ Note: type cannot be changed via this endpoint. Attempting to pass a different type will return a 400 error with the message: "Field type cannot be changed. Delete this field and create a new one with the correct type." To change the field type, delete this field and create a new one.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Field updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "FieldResponse object" }
}

Success Response Fields: data is a FieldResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or field not found
400 Attempting to change field type

12. Delete Field

Purpose: Deletes a field. Soft delete (default) preserves historical answer snapshots. Hard delete permanently removes the field record.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
fieldId UUID Yes The field to delete Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete false

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Field deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}

When hard=true, message is "Field permanently deleted".

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or field not found

13. Bulk Add Fields

Purpose: Adds multiple fields to a page in one call. Fields are appended after existing fields in array order. For DROPDOWN, RADIO, and CHECKBOX types, options can be passed inline per field. Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk Access Level: 🔒 Protected (Event organizer only) Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page to add fields to Must be a valid UUID

Request JSON Sample:

{
  "fields": [
    { "type": "EMAIL", "label": "Email Address", "required": true },
    { "type": "PHONE", "label": "Phone Number", "required": false },
    { "type": "HEADER", "label": "Travel Details" },
    {
      "type": "DROPDOWN",
      "label": "Arrival Method",
      "required": true,
      "options": [
        { "label": "Flight" },
        { "label": "Bus" },
        { "label": "Car" }
      ]
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
fields array Yes Fields to create Min 1 item
fields[].type string Yes Field type See FieldType enum
fields[].label string Yes Field label Max: 255 characters
fields[].description string No Helper text Max: 500 characters
fields[].placeholder string No Placeholder text Max: 255 characters
fields[].required boolean No Required flag Defaults to false
fields[].validation object No Validation rules See FieldValidation
fields[].options array No Inline options for DROPDOWN, RADIO, CHECKBOX. Ignored for other types. Blank labels are skipped
fields[].options[].label string Yes (if options provided) Option label Max: 255 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "4 fields added, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 4,
    "failureCount": 0,
    "errors": [],
    "createdFields": [ "[ FieldResponse objects ]" ]
  }
}

Success Response Fields: data is a BulkOperationResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or page not found

14. Bulk Delete Fields

Purpose: Deletes multiple fields from a page in one call. Supports soft and hard delete via ?hard.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page containing the fields Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete false

Request JSON Sample:

{
  "ids": ["fieldId-1", "fieldId-2"]
}

Request Body Parameters:

Parameter Type Required Description Validation
ids array Yes UUIDs of fields to delete Min 1 item

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "2 fields deleted, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": [],
    "deletedIds": ["fieldId-1", "fieldId-2"]
  }
}

Success Response Fields: data is a BulkDeleteResult.

Possible Error Responses: Same as Endpoint 8 (401, 403, 404).


15. Bulk Update Fields

Purpose: Updates required flag and/or validation rules across multiple fields at once.

Endpoint: PATCH /api/v1/e-events/applicant-form/events/{eventId}/fields/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Request JSON Sample:

{
  "fieldIds": ["fieldId-1", "fieldId-2"],
  "required": true,
  "validation": { "maxLength": 200 }
}

Request Body Parameters:

Parameter Type Required Description Validation
fieldIds array Yes UUIDs of fields to update Min 1 item
required boolean No New required value for all listed fields
validation object No New validation rules for all listed fields (full replacement) See FieldValidation

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "2 fields updated, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": [],
    "createdFields": [ "[ Updated FieldResponse objects ]" ]
  }
}

Success Response Fields: data is a BulkOperationResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 One or more field IDs not found — reported in data.errors[]

16. Clone Field

Purpose: Creates a copy of a field (with its options) and appends it to a target page. Label gets "(Copy)" appended.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/clone

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
fieldId UUID Yes The field to clone Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
targetPageId UUID Yes The page to clone the field into Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field cloned successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "FieldResponse for the cloned field" }
}

Success Response Fields: data is a FieldResponse for the new clone.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, field not found, or target page not found

Option Management


17. Add Option

Purpose: Adds a choice option to a DROPDOWN, RADIO, or CHECKBOX field. Calling this on any other field type returns 400.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
fieldId UUID Yes The field to add an option to Must be DROPDOWN, RADIO, or CHECKBOX type

Request JSON Sample:

{
  "label": "By Car"
}

Request Body Parameters:

Parameter Type Required Description Validation
label string Yes Display label for this option Max: 255 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Option added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "optionId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
    "label": "By Car",
    "displayOrder": 1
  }
}

Success Response Fields: data is an OptionResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
400 Field type does not support options
403 Not the event organizer
404 Event not found or field not found
422 label is blank

18. Update Option

Purpose: Updates the label of an existing option.

Endpoint: PUT /api/v1/e-events/applicant-form/events/{eventId}/options/{optionId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
optionId UUID Yes The option to update Must be a valid UUID

Request JSON Sample:

{
  "label": "Public Transport"
}

Request Body Parameters:

Parameter Type Required Description Validation
label string No Updated label Max: 255 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Option updated",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "optionId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
    "label": "Public Transport",
    "displayOrder": 1
  }
}

Success Response Fields: data is an OptionResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or option not found

19. Delete Option

Purpose: Deletes an option. Soft delete (default) preserves previously selected answer values. Hard delete permanently removes the option record.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/options/{optionId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
optionId UUID Yes The option to delete Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete false

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Option deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}

When hard=true, message is "Option permanently deleted".

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or option not found

20. Bulk Delete Options

Purpose: Deletes multiple options from a field in one call. Supports soft and hard delete via ?hard.

Endpoint: DELETE /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
fieldId UUID Yes The field containing the options Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
hard boolean No true = permanent delete false

Request JSON Sample:

{
  "ids": ["optionId-1", "optionId-2"]
}

Request Body Parameters:

Parameter Type Required Description Validation
ids array Yes UUIDs of options to delete Min 1 item

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "2 options deleted, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": [],
    "deletedIds": ["optionId-1", "optionId-2"]
  }
}

Success Response Fields: data is a BulkDeleteResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or field not found. Individual errors in data.errors[]

21. Bulk Update Options

Purpose: Updates the labels of multiple options in one call.

Endpoint: PATCH /api/v1/e-events/applicant-form/events/{eventId}/options/bulk

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Request JSON Sample:

{
  "options": [
    { "optionId": "optionId-1", "label": "Train" },
    { "optionId": "optionId-2", "label": "Bus" }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
options array Yes List of option updates Min 1 item
options[].optionId UUID Yes The option to update Must be a valid UUID
options[].label string No New label Max: 255 characters

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "2 options updated, 0 failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "successCount": 2,
    "failureCount": 0,
    "errors": []
  }
}

Success Response Fields: data is a BulkOperationResult.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 An option ID not found — reported in data.errors[]

Preview


23. Get Preview Metadata

Purpose: Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used by the attendee to render a progress indicator before filling. Also returns the attendee's current response state (responseId, currentPageIndex, completedPageIds) if they have an existing draft.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/preview/metadata

Access Level: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Metadata retrieved",
  "data": {
    "formId": "uuid",
    "formTitle": "Attendee Questions - Tech Summit 2025",
    "totalPages": 3,
    "pageNumbers": [
      { "pageNumber": 1, "pageTitle": "Personal Info" },
      { "pageNumber": 2, "pageTitle": "Experience" },
      { "pageNumber": 3, "pageTitle": "Preferences" }
    ],
    "responseId": "uuid",
    "responseStatus": "DRAFT",
    "currentPageIndex": 1,
    "completedPageIds": ["page-uuid-1"]
  }
}

Success Response Fields:

Field Description
formId Internal form ID
formTitle Form title
totalPages Total active pages
pageNumbers[].pageNumber 1-based page number
pageNumbers[].pageTitle Page heading

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

24. Preview Page by Number

Purpose: Returns a specific page by its 1-based position number, including all its active fields and options. Used by attendees to load each page as they fill the form. Also accessible to the organizer for previewing before publishing.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}

Access Level: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageNumber integer Yes 1-based page position Must be ≥ 1 and ≤ total active pages

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Preview page retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "PageResponse object" }
}

Success Response Fields: data is a PageResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, form not enabled, or pageNumber out of range

25. Validate Preview Page Answers

Purpose: Runs validation on a set of answers for a specific page number without creating or saving any data. Lets the organizer test field validation rules before the form goes live.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}/validate

Access Level: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageNumber integer Yes 1-based page number to validate against Must be ≥ 1 and ≤ total active pages

Request JSON Sample:

{
  "fieldId-fullname": { "value": "" },
  "fieldId-email":    { "value": "not-an-email" }
}

Request Body: A flat map of fieldId (UUID) → AnswerValue. AnswerValue has value (any), plus optional fileUrl, fileName, fileSize, fileType for FILE fields.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Validation complete",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "valid": false,
    "errors": [
      {
        "pageId": "1a2b3c4d-...",
        "pageTitle": "Attendee Information",
        "fieldId": "fieldId-fullname",
        "fieldLabel": "Full Name",
        "errorMessage": "Full Name is required",
        "errorType": "REQUIRED"
      },
      {
        "pageId": "1a2b3c4d-...",
        "pageTitle": "Attendee Information",
        "fieldId": "fieldId-email",
        "fieldLabel": "Email",
        "errorMessage": "Email must be valid email",
        "errorType": "INVALID_FORMAT"
      }
    ]
  }
}

Success Response Fields:

Field Description
data.valid true if all fields passed validation
data.errors[].pageId Page UUID
data.errors[].pageTitle Page title
data.errors[].fieldId Field UUID
data.errors[].fieldLabel Field label
data.errors[].errorMessage Human-readable error
data.errors[].errorType REQUIRED, INVALID_FORMAT, INVALID_TYPE, VALIDATION_FAILED

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found, form not enabled, or pageNumber out of range

Attendee Submission


26. Start Submission

Purpose: Initiates the attendee's form response session. Creates a new DRAFT response tied to this event's form. If the attendee already has a draft or submitted response for this event's form, the existing response is returned — this endpoint is idempotent. Requires the event to be PUBLISHED and within its registration window.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/start

Access Level: 🔒 Protected (Any authenticated attendee)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The published event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Form submission started",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "responseId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
    "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "submittedBy": "john.doe",
    "status": "DRAFT",
    "completedPageIds": [],
    "currentPageIndex": 0,
    "startedAt": "2025-02-17T10:30:45",
    "submittedAt": null,
    "completionTimeSeconds": null,
    "answers": []
  }
}

Success Response Fields: data is a FormResponseObject.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Event not published, registration not yet open, or registration already closed
404 Event not found or form not enabled

27. Save Page Answers

Purpose: Saves the attendee's answers for a specific page. Behaviour is controlled by the ?moveToNextPage query parameter which maps directly to two distinct client actions — background auto-save and the "Next" button.

Endpoint: PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/save

Access Level: 🔒 Protected (Attendee — response owner)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
pageId UUID Yes The page being answered Must be a valid UUID belonging to this event's form

Query Parameters:

Parameter Type Required Description Default
moveToNextPage boolean No Controls validation and page advancement. See behaviour table below false

moveToNextPage Behaviour

Value Intended Use Validation What Happens on Success What Happens on Failure
false Background auto-save while the user is typing None — answers are stored as-is regardless of format or required state Answers written. completedPageIds unchanged. Always success: true N/A — cannot fail
true User clicks "Next" to advance to the next page Full — all fields on the page are validated (required + format + type) Answers written, page added to completedPageIds, currentPageIndex advances. success: true Nothing saved, nothing advanced. success: false with errors[] in data

Key rule: A page only enters completedPageIds through a successful ?moveToNextPage=true call. submit will reject the form if any page is missing from completedPageIds.

Request Body: A flat Map<UUID, AnswerValue> where each key is a fieldId.

Key Type Description
{fieldId} UUID (map key) The field being answered
.value any Answer value. See answer value types below
.fileUrl string FILE fields — uploaded file URL
.fileName string FILE fields — original file name
.fileSize long FILE fields — file size in bytes
.fileType string FILE fields — MIME type

Answer Value Types

Field Type Expected Value
TEXT, TEXTAREA, EMAIL, PHONE, URL String
NUMBER Number (integer or decimal)
DATE String — YYYY-MM-DD
TIME String — HH:mm
DATETIME String — ISO 8601 datetime
DROPDOWN, RADIO String — UUID of selected option
CHECKBOX Array of strings — UUIDs of selected options
RATING Integer — 1 to 5
FILE null — metadata goes in fileUrl, fileName, etc.
HEADER Omit entirely — no answer needed

Example 1 — Auto-save (moveToNextPage=false)

Request:

PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false
{
  "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
  "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" }
}

Response — always 200 OK, always success: true, no validation applied:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page saved",
  "action_time": "2026-05-22T14:30:00",
  "data": {
    "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
    "saved": true,
    "savedAt": "2026-05-22T14:30:00",
    "isValid": true,
    "errors": null
  }
}

Example 2 — User clicks "Next", validation fails (moveToNextPage=true)

Request:

PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true
{
  "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
  "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" },
  "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "" },
  "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" }
}

Response200 OK but success: false. Nothing saved. Client shows inline errors and keeps user on the same page:

{
  "success": false,
  "httpStatus": "OK",
  "message": "Validation failed",
  "action_time": "2026-05-22T14:31:00",
  "data": {
    "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
    "saved": false,
    "savedAt": null,
    "isValid": false,
    "errors": [
      {
        "fieldId": "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e",
        "fieldLabel": "Email",
        "errorCode": "INVALID_FORMAT",
        "message": "must be valid email"
      },
      {
        "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42",
        "fieldLabel": "Phone Number",
        "errorCode": "REQUIRED",
        "message": "Phone Number is required"
      }
    ]
  }
}

Example 3 — User clicks "Next", validation passes (moveToNextPage=true)

Request:

PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true
{
  "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
  "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "graphereddy@gmail.com" },
  "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "0627489964" },
  "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" }
}

Response200 OK, success: true. Page written, added to completedPageIds, index advances:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page saved",
  "action_time": "2026-05-22T14:32:00",
  "data": {
    "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
    "saved": true,
    "savedAt": "2026-05-22T14:32:00",
    "isValid": true,
    "errors": null
  }
}

Error Codes in errors[].errorCode

errorCode Meaning
REQUIRED Field is marked required and no value was provided
INVALID_FORMAT Value format is wrong (e.g. bad email, invalid phone pattern)
INVALID_TYPE Value is the wrong data type for the field (e.g. passing a string to a DROPDOWN expecting a UUID)
VALIDATION_FAILED Value fails a custom rule (minLength, maxLength, min, max, minSelections, etc.)

Success Response Fields: data is a SavePageResponse.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Registration window closed or event not published
404 Event not found, form not enabled, or no active draft found — call /start first

28. Get My Response

Purpose: Returns the authenticated attendee's current response for the event's form — whether still in draft or already submitted.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/my-response

Access Level: 🔒 Protected (Any authenticated attendee)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Response retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject" }
}

Success Response Fields: data is a FormResponseObject.

Possible Error Responses:

Status Scenario
401 No or expired token
404 No response found for this attendee on this event's form

29. Submit

Purpose: Finalises and submits the attendee's response. The server performs a two-stage validation before accepting: first it checks that every page is in completedPageIds (meaning the attendee clicked "Next" and passed validation for each page), then it checks that all required fields still have answers. If either check fails the response is rejected with 422 and a list of field-level errors in data. On success, status transitions to SUBMITTED and completionTimeSeconds is recorded.

Endpoint: POST /api/v1/e-events/applicant-form/events/{eventId}/submit

Access Level: 🔒 Protected (Attendee — response owner)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Form submitted successfully",
  "action_time": "2026-05-22T14:35:00",
  "data": {
    "responseId": "8a067ebe-5eff-496c-a7eb-e3f8ad853b9f",
    "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "submittedBy": "john.doe",
    "status": "SUBMITTED",
    "completedPageIds": ["a64a2f29-428e-41fc-8d14-043841d857a2"],
    "currentPageIndex": 1,
    "startedAt": "2026-05-22T14:30:00",
    "submittedAt": "2026-05-22T14:35:00",
    "completionTimeSeconds": 312,
    "answers": [ "..." ]
  }
}

Success Response Fields: data is a FormResponseObject. data.status will be "SUBMITTED".

Error Response — page not completed via "Next" (422):

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Please complete all required pages",
  "action_time": "2026-05-22T14:34:00",
  "data": [
    {
      "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2",
      "pageTitle": "Personal Info",
      "fieldId": null,
      "fieldLabel": null,
      "errorType": "PAGE_INCOMPLETE",
      "errorMessage": "Page not completed — please review all fields and click Next"
    }
  ]
}

Error Response — required field still unanswered (422):

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Please complete all required pages",
  "action_time": "2026-05-22T14:34:00",
  "data": [
    {
      "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2",
      "pageTitle": "Personal Info",
      "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42",
      "fieldLabel": "Phone Number",
      "errorType": "REQUIRED",
      "errorMessage": "This field is required"
    }
  ]
}

Possible Error Responses:

Status Scenario
401 No or expired token
403 Already submitted, or registration window closed
404 Event not found, form not enabled, or no draft response found
422 One or more pages not in completedPageIds (PAGE_INCOMPLETE); or a required field has no answer (REQUIRED). Error list is in data[]

Organizer Response Management


30. Get Response by ID

Purpose: Retrieves a specific attendee response by ID. Available to the event organizer only.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/responses/{responseId}

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID
responseId UUID Yes The response to retrieve Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Response retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject" }
}

Success Response Fields: data is a FormResponseObject.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or response not found

31. Get All Responses

Purpose: Returns a paginated list of all attendee responses for the event's form. For organizer review and export.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/responses

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Query Parameters:

Parameter Type Required Description Validation Default
page integer No Page number (0-based) Min: 0 0
size integer No Items per page Min: 1 20

Note: This endpoint uses 0-based pagination (page=0 is the first page), matching the Spring Pageable default passed from the controller.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Responses retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ FormResponseObject entries ]" ],
    "totalElements": 48,
    "totalPages": 3,
    "first": true,
    "last": false,
    "empty": false
  }
}

Success Response Fields: data.content[] contains FormResponseObject entries.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

32. Get Analytics

Purpose: Returns full submission analytics for the event's form — stats overview, per-field distributions, and daily submission trends.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/analytics

Access Level: 🔒 Protected (Event organizer only)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes The event Must be a valid UUID

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Analytics retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "formTitle": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
    "stats": {
      "totalStarted": 120,
      "totalDrafts": 18,
      "totalSubmitted": 95,
      "totalWithdrawn": 7,
      "completionRate": 79.2,
      "dropOffRate": 20.8,
      "avgCompletionTimeSeconds": 312.0,
      "fastestTimeSeconds": 45,
      "slowestTimeSeconds": 1890
    },
    "fieldAnalytics": [
      {
        "fieldId": "c3d4e5f6-...",
        "fieldLabel": "Full Name",
        "fieldType": "TEXT",
        "fieldDeleted": false,
        "totalResponses": 95,
        "uniqueResponses": 95,
        "textResponses": ["Amina Hassan", "John Doe", "..."]
      },
      {
        "fieldId": "e5f6a7b8-...",
        "fieldLabel": "Arrival method",
        "fieldType": "DROPDOWN",
        "fieldDeleted": false,
        "totalResponses": 95,
        "choiceDistribution": [
          { "option": "By Car", "count": 42, "percentage": 44.2 },
          { "option": "Public Transport", "count": 35, "percentage": 36.8 },
          { "option": "On Foot", "count": 18, "percentage": 18.9 }
        ]
      }
    ],
    "dailySubmissions": [
      { "date": "2025-07-15", "count": 12 },
      { "date": "2025-07-16", "count": 28 }
    ]
  }
}

Success Response Fields: data is a FormAnalytics.

Possible Error Responses:

Status Scenario
401 No or expired token
403 Not the event organizer
404 Event not found or form not enabled

Quick Reference — Endpoint Summary

# Method Path Auth Who Description
Form Setup
1 POST /events/{eventId}/enable 🔒 Organizer Enable form (empty — no pages)
2 PUT /events/{eventId}/settings 🔒 Organizer Update form display settings
3 DELETE /events/{eventId}/disable 🔒 Organizer Disable and remove form
4 GET /events/{eventId}/full-form 🔒 Organizer Load full form (all pages + fields) for builder
Page Management
5 POST /events/{eventId}/pages 🔒 Organizer Add a page
6 PUT /events/{eventId}/pages/{pageId} 🔒 Organizer Update a page
7 DELETE /events/{eventId}/pages/{pageId}?hard=false 🔒 Organizer Delete a page (soft or hard)
8 POST /events/{eventId}/pages/bulk 🔒 Organizer Bulk add pages with fields
9 DELETE /events/{eventId}/pages/bulk?hard=false 🔒 Organizer Bulk delete pages
10 POST /events/{eventId}/pages/{pageId}/clone 🔒 Organizer Clone a page
Field Management
11 POST /events/{eventId}/pages/{pageId}/fields 🔒 Organizer Add a field to a page
12 PUT /events/{eventId}/fields/{fieldId} 🔒 Organizer Update a field
13 DELETE /events/{eventId}/fields/{fieldId}?hard=false 🔒 Organizer Delete a field (soft or hard)
14 POST /events/{eventId}/pages/{pageId}/fields/bulk 🔒 Organizer Bulk add fields
15 DELETE /events/{eventId}/pages/{pageId}/fields/bulk?hard=false 🔒 Organizer Bulk delete fields
16 PATCH /events/{eventId}/fields/bulk 🔒 Organizer Bulk update fields
17 POST /events/{eventId}/fields/{fieldId}/clone 🔒 Organizer Clone a field
Option Management
18 POST /events/{eventId}/fields/{fieldId}/options 🔒 Organizer Add option to a field
19 PUT /events/{eventId}/options/{optionId} 🔒 Organizer Update an option
20 DELETE /events/{eventId}/options/{optionId}?hard=false 🔒 Organizer Delete an option (soft or hard)
21 DELETE /events/{eventId}/fields/{fieldId}/options/bulk?hard=false 🔒 Organizer Bulk delete options
22 PATCH /events/{eventId}/options/bulk 🔒 Organizer Bulk update option labels
Preview
23 GET /events/{eventId}/preview/metadata 🔒 Organizer + Attendee* Form page metadata + attendee progress
24 GET /events/{eventId}/preview/pages/{pageNumber} 🔒 Organizer + Attendee* Load a page by number (fields + options)
25 POST /events/{eventId}/preview/pages/{pageNumber}/validate 🔒 Organizer + Attendee* Validate answers without saving
Attendee Submission
26 POST /events/{eventId}/start 🔒 Attendee Start form / get existing draft
27 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false 🔒 Attendee Auto-save answers (no validation)
27 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true 🔒 Attendee "Next" — validate page and advance if clean
28 GET /events/{eventId}/my-response 🔒 Attendee Get my response
29 POST /events/{eventId}/submit 🔒 Attendee Submit form (requires all pages completed via "Next")
Organizer — Responses & Analytics
30 GET /events/{eventId}/responses/{responseId} 🔒 Organizer Get response by ID
31 GET /events/{eventId}/responses 🔒 Organizer Get all responses (paginated)
32 GET /events/{eventId}/analytics 🔒 Organizer Get form analytics

* Attendee access to preview endpoints requires the event to be PUBLISHED.

All paths are prefixed with /api/v1/e-events/applicant-form.


Data Format Standards

Concern Standard
Timestamps ZonedDateTime — ISO 8601 with offset: 2025-02-17T10:30:45+03:00
Local timestamps LocalDateTime — ISO 8601 no offset: 2025-02-17T10:30:45
Dates YYYY-MM-DD2025-07-18
Times HH:mm18:00
IDs UUID v4 — 3fa85f64-5717-4562-b3fc-2c963f66afa6
Pagination (responses) 0-based page param, Spring Page wrapper
Enums Uppercase: BEFORE_CHECKOUT, SUBMITTED, DROPDOWN, REQUIRED

Attendance Analytics API

Author: Josh S. Sakweli, Backend Lead 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 both single-day and multi-day events.

Hints:


Standard Response Format

All API responses follow a consistent structure using our Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": {
  }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2026-05-23T10:30:45",
  "data": "Error description"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.)
message string Human-readable message describing the result
action_time string ISO 8601 timestamp of when the response was generated
data object Response payload for success, error string for failures

HTTP Method Badge Standards


Endpoints

1. Get Attendance Summary

Purpose: Returns overall attendance statistics for an event including per-day and per-ticket-type breakdowns. Always covers the full event — no filtering applied.

Endpoint: GET /e-events/attendance/{eventId}/summary

Access Level: 🔒 Protected — Event Organizer Only

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes Event identifier Must be a valid UUID of an existing event owned by the authenticated organizer

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Attendance summary retrieved",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "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": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
        "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
          }
        ]
      }
    ]
  }
}

Success Response Fields:

Field Description
data.eventId Event UUID
data.eventTitle Event name
data.totalDays Number of event days
data.eventSchedule List of days with dayNumber, dayName, date
data.overallStats.totalTickets Total tickets sold
data.overallStats.present Tickets checked in for all days
data.overallStats.partiallyAttended Tickets checked in for some days (multi-day only)
data.overallStats.absent Tickets never checked in
data.overallStats.attendanceRate ((present + partiallyAttended) / total) × 100, 2 dp
data.overallStats.byDay[].status COMPLETED, ONGOING, or UPCOMING
data.byTicketType Same stats broken down per ticket type

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Event not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "Event not found"
}

Standard Error Types:


2. Get Attendance List

Purpose: Returns a paginated, filterable list of all tickets for an event. Each row is one ticket. Present and absent tickets are unified in one list — use the status filter to narrow down.

Endpoint: GET /e-events/attendance/{eventId}/list

Access Level: 🔒 Protected — Event Organizer Only

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes Event identifier Must be a valid UUID of an existing event owned by the authenticated organizer

Query Parameters:

Parameter Type Required Description Validation Default
status enum No Filter by attendance status PRESENT, ABSENT, PARTIALLY_ATTENDED All
dayNumber integer No Filter for a specific event day Must be between 1 and totalDays None (overall)
ticketTypeId UUID No Filter by ticket type Must belong to this event All types
buyerType enum No Filter by how the ticket was purchased SYSTEM_USER, AT_DOOR All
search string No Search by attendee name, email, or phone Partial match, case-insensitive None
page integer No Page number (0-indexed) Min: 0 0
size integer No Items per page Min: 1 20

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Attendance list retrieved",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "eventTitle": "East African Tech Summit 2026",
    "appliedFilters": {
      "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
      "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": "3fa85f64-5717-4562-b3fc-2c963f66afa8",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "attendeeNumber": "EVT-A3F4B21C-VIP-0042",
        "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
        "ticketType": "VIP Pass",
        "ticketSeries": "VIP-0042",
        "attendeeName": "John Doe",
        "attendeeEmail": "john@example.com",
        "attendeePhone": "+255712345678",
        "buyer": {
          "buyerName": "Jane Doe",
          "buyerEmail": "jane@example.com",
          "buyerId": "3fa85f64-5717-4562-b3fc-2c963f66afa9",
          "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": "3fa85f64-5717-4562-b3fc-2c963f66afb0"
      },
      {
        "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afb1",
        "formResponseId": null,
        "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
    }
  }
}

Success Response Fields:

Field Description
data.appliedFilters Echo of all active filters so the client knows what was applied
data.summary.totalTickets Total tickets matching the ticket type filter (unaffected by status/search)
data.summary.present Tickets with PRESENT status under current filters
data.summary.absent Tickets with ABSENT status under current filters
data.summary.partiallyAttended null when dayNumber is active; count when viewing overall multi-day
data.summary.attendanceRate ((present + partiallyAttended) / total) × 100, 2 dp
data.attendees[].ticketInstanceId Unique ticket instance UUID
data.attendees[].formResponseId UUID of the buyer's applicant form response — null if the event had no form
data.attendees[].attendeeNumber Unique readable ID = bookingReference + "-" + ticketSeries
data.attendees[].attendanceStatus PRESENT, ABSENT, or PARTIALLY_ATTENDED
data.attendees[].buyer.buyerType SYSTEM_USER (online) or AT_DOOR (walk-in)
data.attendees[].buyer.buyerId Populated for SYSTEM_USER, null for AT_DOOR
data.attendees[].checkInTime null when attendanceStatus is ABSENT
data.pagination Standard pagination envelope

Notes:

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Invalid day 5. Valid: 1-3",
  "action_time": "2026-05-23T10:30:45",
  "data": "Invalid day 5. Valid: 1-3"
}

Standard Error Types:


3. Get Attendee Detail

Purpose: Returns the full check-in history for a single ticket across all event days. Shows per-day status including upcoming days.

Endpoint: GET /e-events/attendance/{eventId}/attendees/{ticketInstanceId}

Access Level: 🔒 Protected — Event Organizer Only

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
eventId UUID Yes Event identifier Must be owned by the authenticated organizer
ticketInstanceId UUID Yes Individual ticket instance identifier Must belong to a booking under this event

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Attendee detail retrieved",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa8",
    "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "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": "3fa85f64-5717-4562-b3fc-2c963f66afb0"
      },
      {
        "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
      }
    ]
  }
}

Success Response Fields:

Field Description
data.ticketInstanceId Unique ticket instance UUID
data.formResponseId UUID of the buyer's applicant form response — null if the event had no form
data.attendeeNumber Unique readable ID = bookingReference + "-" + ticketSeries
data.overallStatus PRESENT (all days), PARTIALLY_ATTENDED (some), ABSENT (none)
data.daysAttended Count of days this ticket was checked in
data.daysTotal Total number of event days
data.checkInsByDay[].status CHECKED_IN, NOT_CHECKED_IN, or UPCOMING
data.checkInsByDay[].checkInTime null when not checked in or day is upcoming
data.checkInsByDay[].checkedInBy Name of the scanner operator who processed the check-in

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Ticket not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "Ticket not found"
}

Standard Error Types:


Quick Reference Guide

Attendance Status Values

Value Meaning Available when
PRESENT Checked in (all days if no day filter) Always
ABSENT Never checked in Always
PARTIALLY_ATTENDED Checked in some days but not all No dayNumber filter, multi-day events only

Day Status Values

Value Meaning
CHECKED_IN Ticket was scanned on that day
NOT_CHECKED_IN Day passed, ticket not scanned
UPCOMING Day has not started yet

Buyer Type Values

Value buyerEmail buyerId Meaning
SYSTEM_USER Populated Populated Bought online, has platform account
AT_DOOR null null Walk-in sale, name only captured at door

Common HTTP Status Codes

Event Feedback API

Author: Josh, Lead Backend Team
Last Updated: 2025-12-11
Version: v1.0

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

Short Description: The Event Feedback API enables attendees to rate and review events after attendance. This API provides a simple 5-star rating system with optional comments, prevents duplicate feedback, ensures only attendees (non-organizers) can submit reviews, and allows anyone to view event feedback with pagination. The system supports escrow release decisions based on feedback quality and helps organizers improve future events.

Hints:


Response Structure

EventFeedbackResponse

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "eventId": "770e8400-e29b-41d4-a716-446655440002",
  "eventTitle": "East African Tech Summit 2025",
  "userId": "660e8400-e29b-41d4-a716-446655440001",
  "userName": "johndoe",
  "rating": 5,
  "comment": "Amazing event! The speakers were excellent and the venue was perfect. Definitely attending next year!",
  "createdAt": "2025-12-18T10:30:45Z"
}

Paginated Response

{
  "success": true,
  "httpStatus": "OK",
  "message": "Feedbacks retrieved successfully",
  "action_time": "2025-12-11T10:30:45",
  "data": {
    "content": [
      {
        "id": "uuid",
        "eventId": "uuid",
        "eventTitle": "East African Tech Summit 2025",
        "userId": "uuid",
        "userName": "johndoe",
        "rating": 5,
        "comment": "Great event!",
        "createdAt": "2025-12-18T10:30:45Z"
      }
    ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 20,
      "offset": 0
    },
    "totalElements": 145,
    "totalPages": 8,
    "last": false,
    "first": true,
    "numberOfElements": 20,
    "size": 20,
    "number": 0,
    "empty": false
  }
}

Endpoints

1. Create Feedback

Endpoint: POST /feedbacks/event/{eventId}

Access: 🔒 Authenticated Users (Non-Organizers)

Path Parameters:

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

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token
Content-Type string Yes application/json

Request Body:

{
  "rating": 5,
  "comment": "Amazing event! The speakers were excellent and the venue was perfect. Definitely attending next year!"
}

Request Parameters:

Parameter Type Required Description Validation
rating integer Yes Star rating Min: 1, Max: 5
comment string No Text review Max: 1000 characters

Success Response: Returns EventFeedbackResponse

Success Response Message: "Feedback submitted successfully"

HTTP Status Code: 201 CREATED

Behavior:

  1. Validates user is authenticated
  2. Validates event exists
  3. Checks user is NOT the event organizer
  4. Checks user hasn't already submitted feedback
  5. Creates feedback record
  6. Returns created feedback

Validation Rules:

Standard Error Types:

Error Response Examples:

Invalid Rating (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Rating must be at least 1",
  "action_time": "2025-12-11T10:30:45",
  "data": "Rating must be at least 1"
}

Organizer Cannot Review (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Event organizers cannot submit feedback for their own events.",
  "action_time": "2025-12-11T10:30:45",
  "data": "Event organizers cannot submit feedback for their own events."
}

Already Submitted (409):

{
  "success": false,
  "httpStatus": "CONFLICT",
  "message": "You have already provided feedback for this event",
  "action_time": "2025-12-11T10:30:45",
  "data": "You have already provided feedback for this event"
}

2. Get Event Feedbacks

Endpoint: GET /feedbacks/event/{eventId}?page=0&size=20

Access: 🔓 Public (No Authentication Required)

Path Parameters:

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

Query Parameters:

Parameter Type Required Description
page integer No Page number (0-indexed, default: 0)
size integer No Items per page (default: 20)

Success Response: Returns Spring Page object with EventFeedbackResponse items

Success Response Message: "Feedbacks retrieved successfully"

HTTP Status Code: 200 OK

Behavior:

Pagination Details:

Standard Error Types:


Rating System

Star Ratings (1-5)

Rating Meaning Emoji Description
5 ⭐⭐⭐⭐⭐ Excellent 😍 Outstanding event, exceeded expectations
4 ⭐⭐⭐⭐ Very Good 😊 Great event, minor improvements possible
3 ⭐⭐⭐ Good 🙂 Satisfactory event, met expectations
2 ⭐⭐ Fair 😐 Below expectations, needs improvement
1 ⭐ Poor 😞 Disappointing event, significant issues

Average Rating Calculation

Average = Sum of all ratings / Number of feedbacks

Example:

Ratings: 5, 5, 4, 5, 3, 4, 5, 5, 4, 5
Total: 45
Count: 10
Average: 4.5 stars

Display Format: 4.5 ⭐ (10 reviews)


Comment Guidelines

Good Comments Include:

Example Good Comments:

"Amazing event! The keynote speakers were excellent and provided valuable insights. 
The venue was perfect and well-organized. Only suggestion would be to have longer 
lunch breaks. Will definitely attend next year!"

"Great networking opportunities and diverse range of topics. Sound system could 
be improved in the main hall."

"Well organized but sessions felt rushed. Would prefer fewer sessions with more 
time for Q&A."

Avoid:

Character Limit


Access Control

Who Can Submit Feedback?

Allowed:

Not Allowed:

Future Restriction (Recommended):

Who Can View Feedback?

Anyone (Public access):


Use Cases

Submit Feedback After Event

POST /feedbacks/event/{eventId}
{
  "rating": 5,
  "comment": "Great event, highly recommend!"
}

User provides honest review after attending

View Event Reviews Before Booking

GET /feedbacks/event/{eventId}?page=0&size=20

Potential attendee checks reviews before purchasing tickets
Sees average rating and recent comments

Organizer Checks Feedback

GET /feedbacks/event/{eventId}?page=0&size=20

Organizer views all feedback for their event
Identifies areas for improvement
Plans better future events

Platform Quality Monitoring

GET /feedbacks/event/{eventId}?page=0&size=20

Platform admin reviews feedback
Identifies low-rated events
May influence escrow release decisions

Future Enhancements

Planned Features

1. Verified Attendee Badge

2. Average Rating Endpoint

GET /feedbacks/event/{eventId}/summary
{
  "averageRating": 4.5,
  "totalReviews": 145,
  "ratingDistribution": {
    "5": 90,
    "4": 35,
    "3": 15,
    "2": 3,
    "1": 2
  }
}

3. Helpful Votes

4. Organizer Response

5. Escrow Integration


Escrow Release Logic (Future)

How Feedback May Affect Escrow

High Ratings (4-5 stars average):

Medium Ratings (3-3.9 stars average):

Low Ratings (<3 stars average):

Example:

Event with 4.5 average rating (90% 5-star):
→ Escrow automatically released 24 hours after event

Event with 2.8 average rating (50% 1-2 star):
→ Escrow held, manual review, contact organizer
→ Possible partial refunds if issues confirmed

Current Status: Not implemented (all events release automatically)


Best Practices

For Attendees

✅ Submit honest, constructive feedback
✅ Mention specific positives and negatives
✅ Wait until after event to review
✅ Be respectful in comments
✅ Update review if organizer addresses issues (future)

For Organizers

✅ Read all feedback carefully
✅ Identify patterns in complaints
✅ Thank reviewers for positive feedback (future)
✅ Address concerns in organizer response (future)
✅ Use feedback to improve future events

For Platform

✅ Monitor feedback quality
✅ Flag suspicious reviews
✅ Use ratings in escrow decisions (future)
✅ Display average ratings prominently
✅ Encourage verified attendee reviews (future)


Quick Reference

HTTP Status Codes

Rating Range

Comment Length

Pagination

Data Formats


Integration Examples

Submit Feedback Form

const submitFeedback = async (eventId, rating, comment) => {
  const response = await fetch(`/api/v1/e-events/feedbacks/event/${eventId}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ rating, comment })
  });
  
  if (response.status === 201) {
    alert('Thank you for your feedback!');
  } else if (response.status === 409) {
    alert('You have already reviewed this event');
  } else if (response.status === 403) {
    alert('Organizers cannot review their own events');
  }
};

Display Feedback List

const loadFeedback = async (eventId, page = 0) => {
  const response = await fetch(
    `/api/v1/e-events/feedbacks/event/${eventId}?page=${page}&size=20`
  );
  const data = await response.json();
  
  const feedbacks = data.data.content;
  const averageRating = calculateAverage(feedbacks);
  
  displayFeedbacks(feedbacks, averageRating);
};

const calculateAverage = (feedbacks) => {
  if (feedbacks.length === 0) return 0;
  const sum = feedbacks.reduce((acc, f) => acc + f.rating, 0);
  return (sum / feedbacks.length).toFixed(1);
};

Star Rating Component

const StarRating = ({ rating, onChange }) => {
  const stars = [1, 2, 3, 4, 5];
  
  return (
    <div className="star-rating">
      {stars.map(star => (
        <span
          key={star}
          className={star <= rating ? 'star filled' : 'star'}
          onClick={() => onChange(star)}
        >
          ⭐
        </span>
      ))}
    </div>
  );
};

Conclusion

The Event Feedback API provides simple yet effective review system with:

5-Star Ratings: Simple and universally understood
Optional Comments: Detailed written feedback
One Per User: Prevents spam and duplicate reviews
Organizer Protection: Can't review own events
Public Access: Anyone can view to inform decisions
Pagination: Handles large numbers of reviews
Future Integration: Ready for escrow release logic

Event Fund Claims

Author: Josh S. Sakweli, Backend Lead — QBIT SPARK CO LIMITED
Version: v1.0
Base URL: /api/v1/e-events/claims


Overview

When an event organizer sells tickets, revenue is held in escrow — one escrow entry per buyer checkout session. A Fund Claim is a formal request to release all currently HELD escrows for an event into the organizer's wallet. Every claim goes through a PENDING → APPROVED / REJECTED lifecycle, and funds only move on explicit admin approval.

Claim approval is an all-or-nothing escrow operation — it releases every HELD escrow for that event at the moment of approval. Partial escrow release is not supported by design (see Financial Safety). What the organizer does with the wallet balance after release is handled by the separate DisbursementService.

Authentication: All endpoints require Authorization: Bearer <jwt_token>


Business Rules

Rule Detail
One pending per event At most ONE PENDING claim per event at any time
Claimable amount formula totalRevenue − totalRefunded − totalClaimed − totalPendingClaims
Admin-only for active events If event has not ended, only ADMIN can initiate a claim
Refund deadline 3 days before event start. Past this → no refunds, organizer may claim early
Escrow release Approval releases ALL HELD escrows for the event atomically → organizer wallet. No partial release
No partial amounts Escrows are atomic per buyer. "80% release" has no business meaning at the escrow layer — staging happens via DisbursementService after funds land in wallet
Admin claims require note adminNote is mandatory on admin-initiated claims
Refund safety Refunds and approvals use pessimistic write lock on EscrowAccount to prevent race conditions

Claim Status Lifecycle

Status Meaning
PENDING Submitted, awaiting admin review. Escrow NOT yet released
APPROVED Admin approved. Escrow atomically released to organizer wallet
REJECTED Admin rejected. No funds moved. Organizer may submit a new claim
CANCELLED Organizer cancelled before admin action. No funds moved

Standard Response Format

Success

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claim submitted successfully",
  "action_time": "2026-04-27T10:30:45",
  "data": { }
}

Error

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "A pending claim already exists for this event",
  "action_time": "2026-04-27T10:30:45",
  "data": "A pending claim already exists for this event"
}

Endpoints


1. List All Claims (Admin)

Endpoint: GET /api/v1/e-events/claims
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Returns all fund claims across all events. Optionally filter by status.

Query Parameters:

Parameter Type Required Description Default
status ClaimStatus enum No Filter by: PENDING, APPROVED, REJECTED, CANCELLED — (all returned)

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claims retrieved",
  "data": [
    {
      "claimId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "claimNumber": "EFC-2026-000001",
      "eventId": "uuid",
      "eventTitle": "Dar Jazz Night",
      "eventStatus": "PUBLISHED",
      "organizerId": "uuid",
      "organizerName": "Amina Hassan",
      "status": "PENDING",
      "claimedAmount": 50000.00,
      "totalRevenueSnapshot": 80000.00,
      "totalRefundedSnapshot": 5000.00,
      "totalPreviouslyClaimedSnapshot": 0.00,
      "totalPendingAtSubmission": 0.00,
      "currency": "TZS",
      "adminInitiated": false,
      "adminId": null,
      "adminNote": null,
      "organizerNote": "Requesting first partial claim",
      "reviewedById": null,
      "reviewerName": null,
      "reviewNote": null,
      "reviewedAt": null,
      "escrowsReleasedCount": 0,
      "escrowsSkippedCount": 0,
      "actualReleasedAmount": null,
      "initiatedAt": "2026-04-20T08:00:00",
      "updatedAt": "2026-04-20T08:00:00"
    }
  ]
}

Response Fields:

Field Type Description
claimId UUID Unique claim identifier
claimNumber string Human-readable reference, e.g. EFC-2026-000001
eventId UUID The event this claim belongs to
eventTitle string Display name of the event
eventStatus EventStatus Current event status
organizerId UUID Organizer's account ID
organizerName string Organizer's display name
status ClaimStatus Current claim status
claimedAmount BigDecimal Amount the organizer requested
totalRevenueSnapshot BigDecimal Organizer's net revenue at time of submission
totalRefundedSnapshot BigDecimal Total refunds issued at time of submission
totalPreviouslyClaimedSnapshot BigDecimal Already released to wallet before this claim
totalPendingAtSubmission BigDecimal Other pending claim amounts at submission time
currency string Currency code, e.g. TZS
adminInitiated boolean true if an admin submitted on behalf of organizer
adminId UUID Admin's account ID (null if organizer-initiated)
adminNote string Admin's reason for initiating (null if organizer-initiated)
organizerNote string Optional note from organizer
reviewedById UUID ID of admin who reviewed (null if pending)
reviewerName string Name of reviewing admin (null if pending)
reviewNote string Admin's review note (null if pending)
reviewedAt LocalDateTime Timestamp of review (null if pending)
escrowsReleasedCount Integer Number of escrow entries released on approval
escrowsSkippedCount Integer Escrow entries skipped (e.g. already refunded)
actualReleasedAmount BigDecimal Actual amount credited to wallet on approval
initiatedAt LocalDateTime When the claim was submitted
updatedAt LocalDateTime Last updated timestamp

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Caller does not have admin role

2. Get My Claims (Organizer)

Endpoint: GET /api/v1/e-events/claims/my-claims
Access: 🔒 Authenticated organizer (any role)
Purpose: Returns all fund claims submitted by the currently authenticated organizer, across all their events.

No query parameters.

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "My fund claims retrieved",
  "data": [ /* array of EventFundClaimResponse — same structure as endpoint 1 */ ]
}

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT

3. Get Claim by ID

Endpoint: GET /api/v1/e-events/claims/{claimId}
Access: 🔒 Authenticated — admin sees any claim; organizer sees only their own
Purpose: Returns full details of a single fund claim.

Path Parameters:

Parameter Type Required Description
claimId UUID Yes The claim's unique identifier

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Claim retrieved",
  "data": { /* EventFundClaimResponse */ }
}

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Organizer attempting to view another organizer's claim
404 NOT_FOUND Claim with given ID does not exist

4. Get Event Claims

Endpoint: GET /api/v1/e-events/claims/event/{eventId}
Access: 🔒 Authenticated — organizer (own events) or admin (any event)
Purpose: Returns all claims ever submitted for a specific event.

Path Parameters:

Parameter Type Required Description
eventId UUID Yes The event's unique identifier

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event claims retrieved",
  "data": [ /* array of EventFundClaimResponse */ ]
}

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Organizer does not own this event
404 NOT_FOUND Event not found

5. Get Claimable Amount

Endpoint: GET /api/v1/e-events/claims/event/{eventId}/claimable-amount
Access: 🔒 Authenticated — organizer (own events) or admin
Purpose: Returns the current claimable amount breakdown for an event, including eligibility status and the active pending claim if one exists.

Path Parameters:

Parameter Type Required Description
eventId UUID Yes The event's unique identifier

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Claimable amount retrieved",
  "data": {
    "eventId": "uuid",
    "eventTitle": "Dar Jazz Night",
    "totalRevenue": 80000.00,
    "totalRefunded": 5000.00,
    "totalClaimed": 0.00,
    "totalPendingClaims": 0.00,
    "claimableAmount": 60000.00,
    "currency": "TZS",
    "eligible": true,
    "ineligibilityReason": null,
    "activePendingClaimId": null,
    "refundDeadline": "2026-05-10T00:00:00+03:00",
    "pastRefundDeadline": false
  }
}

Note on claimable amount: claimableAmount = totalRevenue − totalRefunded − totalClaimed − totalPendingClaims. This is a snapshot estimate used to indicate eligibility — the actualReleasedAmount on approval is the real number, as it reflects the live HELD escrow balance at the moment of approval.

Response Fields:

Field Type Description
totalRevenue BigDecimal Net organizer share of all non-refunded tickets
totalRefunded BigDecimal Gross amount returned to buyers
totalClaimed BigDecimal Already released to organizer wallet
totalPendingClaims BigDecimal Locked by current PENDING claims
claimableAmount BigDecimal What the organizer can claim right now
eligible boolean Whether a new claim can be submitted
ineligibilityReason string Human-readable reason if eligible = false
activePendingClaimId UUID ID of the existing pending claim, or null
refundDeadline ZonedDateTime eventStartDate − 3 days
pastRefundDeadline boolean true = refund window closed, full amount claimable

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Organizer does not own this event
404 NOT_FOUND Event not found

6. Get Revenue Summary (Admin)

Endpoint: GET /api/v1/e-events/claims/event/{eventId}/revenue-summary
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Returns a full financial summary of an event including gross sales, platform fees, refunds, claimed amounts, and escrow balance. Used for admin audit and oversight.

Path Parameters:

Parameter Type Required Description
eventId UUID Yes The event's unique identifier

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Event revenue summary retrieved",
  "data": {
    "eventId": "uuid",
    "eventTitle": "Dar Jazz Night",
    "grossRevenue": 100000.00,
    "totalRefunded": 5000.00,
    "platformFees": 10000.00,
    "netOrganizerRevenue": 85000.00,
    "totalClaimed": 0.00,
    "totalPendingClaims": 0.00,
    "escrowBalance": 85000.00,
    "currency": "TZS"
  }
}

Possible Errors:

HTTP Status Description
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Caller does not have admin role
404 NOT_FOUND Event not found

7. Submit Claim (Organizer)

Endpoint: POST /api/v1/e-events/claims/event/{eventId}
Access: 🔒 Authenticated organizer — must own the event
Purpose: Organizer submits a fund claim for the full current claimable amount. The claim amount is computed server-side — organizer cannot override it.

Path Parameters:

Parameter Type Required Description
eventId UUID Yes The event to submit a claim for

Request Body (optional):

{
  "organizerNote": "Requesting first partial claim before event ends"
}

Request Body Fields:

Field Type Required Description Validation
organizerNote string No Optional note from the organizer Max 1000 characters

Guard Rules Applied Server-Side:

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claim submitted successfully",
  "data": { /* EventFundClaimResponse with status: PENDING */ }
}

Possible Errors:

HTTP Status Description
400 BAD_REQUEST A pending claim already exists for this event
400 BAD_REQUEST Claimable amount is zero — nothing to claim
400 BAD_REQUEST Event has not ended and refund deadline has not passed — only admin can claim
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Authenticated user does not own this event
404 NOT_FOUND Event not found

8. Admin-Initiate Claim

Endpoint: POST /api/v1/e-events/claims/event/{eventId}/admin-initiate
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin submits a fund claim on behalf of the organizer. Used when the event is still active (ticket sales ongoing). The claim is flagged adminInitiated = true with a mandatory audit note.

Path Parameters:

Parameter Type Required Description
eventId UUID Yes The event to claim funds for

Request Body:

{
  "adminNote": "Organizer requested early release. Event ending tomorrow, no pending refunds."
}

Request Body Fields:

Field Type Required Description Validation
adminNote string Yes Mandatory reason for admin-initiated claim Not blank

Guard Rules Applied Server-Side:

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Admin-initiated claim created",
  "data": {
    "claimId": "uuid",
    "claimNumber": "EFC-2026-000003",
    "status": "PENDING",
    "adminInitiated": true,
    "adminId": "uuid-of-admin",
    "adminNote": "Organizer requested early release. Event ending tomorrow, no pending refunds.",
    "claimedAmount": 64000.00,
    "currency": "TZS"
  }
}

Possible Errors:

HTTP Status Description
400 BAD_REQUEST A pending claim already exists for this event
400 BAD_REQUEST Claimable amount is zero — nothing to claim
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Caller does not have admin role
404 NOT_FOUND Event not found
422 UNPROCESSABLE_ENTITY adminNote is blank

9. Approve Claim (Admin)

Endpoint: POST /api/v1/e-events/claims/{claimId}/approve
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin approves a pending claim. This atomically acquires a pessimistic write lock on the EscrowAccount, fetches all escrows currently in HELD status for the event, releases every one of them, and credits the full released amount to the organizer's wallet.

Design note: Approval releases ALL HELD escrows — not just the claimedAmount subset. Each escrow is atomic (one buyer's payment for one checkout session) so partial release has no valid business meaning. The claimedAmount on the claim is a snapshot estimate taken at submission time. The actualReleasedAmount stored on approval is the authoritative number — it reflects the live HELD balance at the moment the admin approves, and may differ if refunds were processed in between.

Path Parameters:

Parameter Type Required Description
claimId UUID Yes The claim to approve

Request Body (optional):

{
  "reviewNote": "Verified escrow balance. Approved for release."
}

Request Body Fields:

Field Type Required Description
reviewNote string No Optional note from reviewing admin

Critical: Approval acquires a PESSIMISTIC_WRITE lock on the EscrowAccount row. Concurrent refund operations for the same event will block until the approval transaction completes, preventing escrow from going negative.

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claim approved and funds released",
  "data": {
    "claimId": "uuid",
    "claimNumber": "EFC-2026-000001",
    "status": "APPROVED",
    "claimedAmount": 50000.00,
    "actualReleasedAmount": 48000.00,
    "escrowsReleasedCount": 12,
    "escrowsSkippedCount": 1,
    "reviewedById": "admin-uuid",
    "reviewerName": "Admin John",
    "reviewNote": "Verified escrow balance. Approved for release.",
    "reviewedAt": "2026-04-27T14:30:00"
  }
}

Note on actualReleasedAmount: May be less than claimedAmount if one or more escrow entries were refunded between claim submission and admin approval. The escrowsSkippedCount indicates how many entries were skipped for this reason.

Possible Errors:

HTTP Status Description
400 BAD_REQUEST Claim is not in PENDING status
400 BAD_REQUEST Escrow balance insufficient to cover claim amount
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Caller does not have admin role
404 NOT_FOUND Claim not found

10. Reject Claim (Admin)

Endpoint: POST /api/v1/e-events/claims/{claimId}/reject
Access: 🔒 ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN
Purpose: Admin rejects a pending claim. No funds are moved. The organizer may submit a new claim after rejection.

Path Parameters:

Parameter Type Required Description
claimId UUID Yes The claim to reject

Request Body (optional):

{
  "reviewNote": "Pending dispute investigation. Please resubmit after resolution."
}

Request Body Fields:

Field Type Required Description
reviewNote string No Reason for rejection (shown to organizer)

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claim rejected",
  "data": {
    "claimId": "uuid",
    "claimNumber": "EFC-2026-000001",
    "status": "REJECTED",
    "reviewedById": "admin-uuid",
    "reviewerName": "Admin John",
    "reviewNote": "Pending dispute investigation. Please resubmit after resolution.",
    "reviewedAt": "2026-04-27T14:35:00"
  }
}

Possible Errors:

HTTP Status Description
400 BAD_REQUEST Claim is not in PENDING status
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Caller does not have admin role
404 NOT_FOUND Claim not found

11. Cancel Claim (Organizer)

Endpoint: DELETE /api/v1/e-events/claims/{claimId}
Access: 🔒 Authenticated organizer — must be the claim owner
Purpose: Organizer cancels their own pending claim before any admin action. This frees up the pending amount, allowing a new claim to be submitted.

Path Parameters:

Parameter Type Required Description
claimId UUID Yes The claim to cancel

No request body.

Success Response:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Fund claim cancelled",
  "data": null
}

Possible Errors:

HTTP Status Description
400 BAD_REQUEST Claim is not in PENDING status (already reviewed)
401 UNAUTHORIZED Invalid or expired JWT
403 FORBIDDEN Organizer does not own this claim
404 NOT_FOUND Claim not found

Financial Safety: Refund & Claim Concurrency

This section explains how the system prevents money from being over-released when a refund and a claim approval happen simultaneously, and why partial escrow release is not supported.

Why Partial Release Is Not Supported

Each escrow is atomic — it represents one buyer's payment for one checkout session. To release "80% of claimable funds" the service would have to arbitrarily pick specific escrow entries to release and leave others HELD. There is no business logic that justifies which individual buyer's payment stays locked — the organizer delivered the event to all of them equally.

10 escrows × 950 TZS = 9,500 TZS claimable
"Release 80%" = 7,600 TZS = release 8 escrows, hold 2
→ WHY are those 2 buyers' payments still locked?
→ No valid answer exists at the escrow layer.

The correct model is:

Claim approval → ALL HELD escrows released → Organizer wallet (full amount lands)
                                                      ↓
                                              Organizer controls staged
                                              payouts via DisbursementService

The organizer's wallet is the right layer for staged payouts. The claim domain's job is simply: verify eligibility, get admin approval, atomically convert all HELD escrows into wallet balance.

The Race Condition Risk

Revenue = 10,000 TZS in HELD escrows
PENDING claim submitted
→ Refund of 2,000 processed concurrently
→ Claim approved → releases all HELD (now 8,000)
→ Refund deducts 2,000 from already-decremented escrow
→ Escrow goes negative 💀

Protections in Place

Layer Mechanism
Refund deadline No refunds allowed within 3 days of event. Eliminates the race entirely post-deadline.
Pessimistic DB lock Both approveClaim() and processRefund() acquire PESSIMISTIC_WRITE lock on the EscrowAccount row — serializing the two operations.
Balance check Before any deduction, service verifies escrow.balance >= amount. Throws InsufficientEscrowException if not.
Snapshot vs actual claimedAmount is a snapshot estimate at submission time. actualReleasedAmount is the real released amount at approval time — reflects live HELD balance after any intervening refunds.
escrowsSkippedCount Tracks how many escrow entries were already REFUNDED or DISPUTED at approval time and therefore skipped.

Claimable Amount Formula

claimableAmount = totalRevenue − totalRefunded − totalClaimed − totalPendingClaims

This formula gives an estimate of what's claimable. The actual amount released on approval equals the live sum of all HELD escrow sellerAmount values at that moment.


Endpoint Quick Reference

# Method Path Access Description
1 GET /claims Admin List all claims (filter by status)
2 GET /claims/my-claims Organizer Get my own claims
3 GET /claims/{claimId} Organizer / Admin Get single claim by ID
4 GET /claims/event/{eventId} Organizer / Admin Get all claims for an event
5 GET /claims/event/{eventId}/claimable-amount Organizer / Admin Get claimable amount breakdown
6 GET /claims/event/{eventId}/revenue-summary Admin Full event revenue summary
7 POST /claims/event/{eventId} Organizer Submit a new claim
8 POST /claims/event/{eventId}/admin-initiate Admin Admin-initiate claim for active event
9 POST /claims/{claimId}/approve Admin Approve claim + release escrow
10 POST /claims/{claimId}/reject Admin Reject claim
11 DELETE /claims/{claimId} Organizer Cancel pending claim

Standard Error Reference

HTTP Status Scenario
400 BAD_REQUEST Business rule violation (duplicate pending, zero claimable, wrong status)
401 UNAUTHORIZED Missing, expired, or malformed JWT
403 FORBIDDEN Insufficient role or ownership violation
404 NOT_FOUND Event or claim does not exist
422 UNPROCESSABLE_ENTITY Validation failure (e.g. blank adminNote)
500 INTERNAL_SERVER_ERROR Unexpected server error