Events API


Events Management API

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

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, media
  └──────┬──────┘
         │  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)
         │
         │  PATCH /drafts/{id}/registration
         ▼
  ┌───────────────────────┐
  │  REGISTRATION SETUPS  │  registrationOpensAt, registrationClosesAt
  └──────┬────────────────┘
         │
         │  (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, 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)
virtualDetails.meetingId string Platform meeting ID
virtualDetails.passcode string Meeting passcode
media.banner string Banner image URL
media.thumbnail string Thumbnail image URL
media.gallery[] array List of gallery image URLs
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.organizerId UUID Organizer account ID
organizer.organizerName string Organizer full name
organizer.organizerUsername string Organizer system username
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
image string Profile/headshot image URL
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
presenterImage string Headshot image URL

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)
totalTickets integer Total slots allocated
ticketsSold integer Number sold
ticketsAvailable integer Remaining slots
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 on sale

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")
thumbnail string Thumbnail URL
pricing.minPrice BigDecimal Lowest available ticket price
pricing.maxPrice BigDecimal Highest available ticket price
pricing.isFree boolean True when all tickets are free
pricing.hasPaidTickets boolean True when at least one paid ticket exists
organizerId UUID Organizer ID
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.


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.",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
    "gallery": []
  }
}

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
media object No Media URLs for the event See MediaRequest below
media.banner string No Banner image URL Max: 500 characters
media.thumbnail string No Thumbnail image URL Max: 500 characters
media.gallery array No List of gallery image URLs

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": 20,
    "canPublish": false,
    "schedule": null,
    "venue": null,
    "virtualDetails": null,
    "media": {
      "banner": "https://cdn.example.com/banners/jazz-2025.jpg",
      "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
      "gallery": []
    },
    "highlights": null,
    "faqs": null,
    "lineup": null,
    "agenda": null,
    "linkedProducts": [],
    "linkedShops": [],
    "tickets": [],
    "organizer": {
      "organizerId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "organizerName": "Amina Hassan",
      "organizerUsername": "amina.hassan"
    },
    "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,
        "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
        "pricing": { "isFree": true, "hasPaidTickets": false },
        "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",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025-v2.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v2.jpg",
    "gallery": ["https://cdn.example.com/gallery/img1.jpg"]
  }
}

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
media object No Updated media See media fields in endpoint 1

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.

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.

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 — Registration Config

Purpose: Sets the registration window (open and close dates) for the event. The window must be valid relative to itself and cannot close after the event ends. Advances currentStage to TICKETS.

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

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:

{
  "registrationOpensAt": "2025-05-01T08:00:00+03:00",
  "registrationClosesAt": "2025-07-17T23:59:00+03:00",
  "ctaLabel":"Get ticket"
}

Request Body Parameters:

Parameter Type Required Description Validation
registrationOpensAt ZonedDateTime Yes When registration opens ISO 8601 with timezone offset; must be before registrationClosesAt
registrationClosesAt ZonedDateTime Yes When registration closes ISO 8601 with timezone offset; must not be after the event's endDateTime

Success Response JSON Sample:

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

Success Response Fields: data is a full EventResponse. completedStages will include "REGISTRATION_SETUPS" 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 registrationOpensAt is after registrationClosesAt, or registrationClosesAt is after event end

9. 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

10. 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 9 (401, 403, 404).


11. 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.",
      "image": "https://cdn.example.com/artists/dj-afrobeat.jpg",
      "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[].image string No Headshot URL 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 image from the user's profile. These are also refreshed on every GET of the event.

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

12. 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
sessions[].presenterImage string No Image URL Auto-enriched for platform users

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

13. Attach Product to Event

Endpoint: POST /api/v1/e-events/draft/{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

14. 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/draft/{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

15. Attach Shop to Event

Endpoint: POST /api/v1/e-events/draft/{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

16. 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/draft/{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 14 (401, 403, 404).


17. 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, and generates an RSA key pair used for secure ticket QR code signing.

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": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.status will be "PUBLISHED".

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 date/registration validation fails
500 RSA key generation failed

18. 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": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.status will be "DRAFT".

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

19. 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": { "...": "Full EventResponse object" }
}

Success Response Fields: data is a full EventResponse. data.status will be "CANCELLED".

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

20. Update Published Event — Basic Info

Purpose: Updates the description and/or media of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly.

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.",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025-v3.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v3.jpg",
    "gallery": ["https://cdn.example.com/gallery/img1.jpg", "https://cdn.example.com/gallery/img2.jpg"]
  }
}

Request Body Parameters:

Parameter Type Required Description Validation
description string No Updated event description Min: 15, Max: 5000 characters
media object No Updated media URLs See media fields in Endpoint 1
media.banner string No Banner image URL Max: 500 characters
media.thumbnail string No Thumbnail image URL Max: 500 characters
media.gallery array No Gallery image URLs

Blocked fields: title, categoryId, eventFormat, and eventVisibility cannot be changed on a published event. Providing them will have no effect.

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Published event updated successfully",
  "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

21. Update Published Event — Registration Window

Purpose: Extends the registration close date for a published event. The new registrationClosesAt must be later than the current value — shortening the registration window is not allowed.

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

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:

{
  "registrationClosesAt": "2025-07-20T23:59:00+03:00"
}

Request Body Parameters:

Parameter Type Required Description Validation
registrationClosesAt ZonedDateTime Yes New registration close date Must be after the current registrationClosesAt; must not be after event end

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Registration window extended 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
422 New close date is not after current close date, or is after event end

22. 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 9 — 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

23. 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 10 — 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 22 (401, 403, 404, 400).


24. 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 11 — 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

25. 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 12 — 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

26. 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

27. 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.

Possible Error Responses:

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

28. 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.


29. 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 28.

Possible Error Responses:

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

30. 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).


31. 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).


32. 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

33. 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)

34. 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

Quick Reference — Endpoint Summary

# Method Path Auth Description
1 POST /e-events/drafts 🔒 Create event draft
2 GET /e-events/drafts 🔒 List my drafts (paginated)
3 GET /e-events/drafts/{draftId} 🔒 Get draft by ID
4 DELETE /e-events/drafts/{draftId} 🔒 Discard a draft
5 PATCH /e-events/drafts/{draftId}/basic-info 🔒 Update basic info
6 PATCH /e-events/drafts/{draftId}/schedule 🔒 Update schedule / days
7 PATCH /e-events/drafts/{draftId}/location 🔒 Update venue / virtual details
8 PATCH /e-events/drafts/{draftId}/registration 🔒 Update registration window
9 PATCH /e-events/drafts/{draftId}/highlights 🔒 Update highlights
10 PATCH /e-events/drafts/{draftId}/faqs 🔒 Update FAQs
11 PATCH /e-events/drafts/{draftId}/lineup 🔒 Update lineup
12 PATCH /e-events/drafts/{draftId}/agenda 🔒 Update agenda
13 POST /e-events/draft/{eventId}/products/{productId} 🔒 Attach product (DRAFT or PUBLISHED)
14 DELETE /e-events/draft/{eventId}/products/{productId} 🔒 Remove product (DRAFT or PUBLISHED)
15 POST /e-events/draft/{eventId}/shops/{shopId} 🔒 Attach shop (DRAFT or PUBLISHED)
16 DELETE /e-events/draft/{eventId}/shops/{shopId} 🔒 Remove shop (DRAFT or PUBLISHED)
17 PATCH /e-events/{eventId}/publish 🔒 Publish event
18 PATCH /e-events/{eventId}/unpublish 🔒 Unpublish event (0 sales only)
19 PATCH /e-events/{eventId}/cancel 🔒 Cancel event (terminal)
20 PATCH /e-events/{eventId}/published/basic-info 🔒 Update description/media on published event
21 PATCH /e-events/{eventId}/published/registration 🔒 Extend registration close date
22 PATCH /e-events/{eventId}/published/highlights 🔒 Update highlights on published event
23 PATCH /e-events/{eventId}/published/faqs 🔒 Update FAQs on published event
24 PATCH /e-events/{eventId}/published/lineup 🔒 Update lineup on published event
25 PATCH /e-events/{eventId}/published/agenda 🔒 Update agenda on published event
26 PATCH /e-events/{eventId}/published/reveal-location 🔒 Reveal TBA location
27 GET /e-events/{eventId} 🌐/🔒 Get event by ID
28 GET /e-events/my-events 🔒 List my events
29 GET /e-events/my-events/status/{status} 🔒 My events by status
30 GET /e-events/events-feed 🌐 Public events feed
31 GET /e-events/search 🌐 Search events by keyword
32 GET /e-events/filter/date 🌐 Filter by date range
33 GET /e-events/filter 🌐 Combined search + filter
34 GET /e-events/my-events/search 🔒 Search within my events

Note on path inconsistency: Endpoints 5–12 use the path prefix /drafts/{id}/... (plural), while endpoints 6, 7, 13–16 use /draft/{id}/... (singular). This reflects the actual controller routing in the current codebase — use the exact paths shown in the table above.


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: 2025-02-19 Version: v1.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"
}

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                       |
  [ Auto-processed immediately ]  |
          |                       v
          |           [ GET /checkout/{sessionId} ]
          |           [ — Review session details  ]
          |                       |
          |                       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, insufficient wallet balance, 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 Validation failed on request fields (missing eventId, invalid email format, invalid phone format, etc.)
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, session is expired, or attempting payment on a FREE ticket
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 Payment processing error, insufficient balance at time of processing

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 ]
  [ Wallet balance validated upfront  ]
  [ 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, Lead Backend Team
Last Updated: 2025-12-11
Version: v1.0

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

Short Description: The Event Booking Orders API provides comprehensive booking management functionality for confirmed event ticket purchases in the Nexgate platform. This API enables users to view their booking details including JWT-signed QR codes for secure event entry, track multi-day event check-ins, retrieve complete booking history, and access event snapshots at time of booking. The system supports both single-day and multi-day events with per-day check-in tracking, automated ticket series generation, and comprehensive booking references.

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-12-11T10:30:45",
  "data": {
    // Actual response data goes here
  }
}

Error Response Structure

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

HTTP Method Badge Standards


BookingOrderResponse Structure

This is the comprehensive response structure returned by booking endpoints:

{
  "bookingId": "550e8400-e29b-41d4-a716-446655440000",
  "bookingReference": "EVT-A3F4B21C",
  "status": "CONFIRMED",
  "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",
    "virtualDetails": {
      "platform": "ZOOM",
      "meetingUrl": "https://zoom.us/j/123456789",
      "meetingId": "123 456 789",
      "passcode": "summit2025",
      "additionalInstructions": "Join 5 minutes early for networking"
    }
  },
  "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",
      "ticketTypeName": "VIP Pass",
      "ticketSeries": "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"
      },
      "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",
      "ticketTypeName": "General Admission",
      "ticketSeries": "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"
      },
      "checkIns": [],
      "hasBeenCheckedIn": false,
      "lastCheckedInAt": null,
      "lastCheckedInBy": null,
      "lastCheckInLocation": null,
      "lastCheckInDayName": null,
      "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
}

Response Field Descriptions

Root Level Fields

Field Type Description
bookingId string (UUID) Unique booking identifier
bookingReference string Short readable code (e.g., "EVT-A3F4B21C")
status enum Booking status: CONFIRMED, CANCELLED
event object Event snapshot at time of booking
organizer object Organizer details snapshot
customer object Customer (buyer) information
tickets array List of all booked tickets with check-in history
totalTickets integer Total number of tickets in booking
checkedInTicketsCount integer Count of tickets with at least one check-in
subtotal decimal Subtotal amount
total decimal Total amount paid
bookedAt string (LocalDateTime) When booking was created
cancelledAt string (LocalDateTime) When booking was cancelled (null if active)

Event Snapshot Object

Field Type Description
eventId string (UUID) Event identifier
title string Event title at time of booking
startDateTime string (LocalDateTime) Event start date/time
endDateTime string (LocalDateTime) Event end date/time
timezone string IANA timezone (e.g., "Africa/Nairobi")
location string Full venue location (name + address)
format string Event format: IN_PERSON, ONLINE, HYBRID
virtualDetails object Virtual meeting details (for ONLINE/HYBRID)

Virtual Details Object

Field Type Description
platform string Platform: ZOOM, GOOGLE_MEET, MS_TEAMS, CUSTOM
meetingUrl string Full meeting URL
meetingId string Meeting ID (optional)
passcode string Meeting passcode (optional)
additionalInstructions string Extra instructions (optional)

Organizer Snapshot Object

Field Type Description
name string Organizer name at time of booking
email string Organizer email
phone string Organizer phone

Customer Info Object

Field Type Description
customerId string (UUID) Customer account ID
name string Customer username
email string Customer email

Booked Ticket Response Object

Field Type Description
ticketInstanceId string (UUID) Unique ticket instance ID
ticketTypeName string Ticket type name (e.g., "VIP Pass")
ticketSeries string Auto-generated series (e.g., "VIP-0001")
price decimal Ticket price
qrCode string JWT-signed QR code (very long string)
attendanceMode string IN_PERSON or ONLINE (for hybrid events)
attendee object Attendee information
buyer object Buyer information (who paid)
checkIns array Complete check-in history (all days)
hasBeenCheckedIn boolean True if checked in at least once
lastCheckedInAt string (ZonedDateTime) Most recent check-in time
lastCheckedInBy string Who performed last check-in
lastCheckInLocation string Location of last check-in
lastCheckInDayName string Day name of last check-in
status enum ACTIVE, USED, CANCELLED
validFrom string (ZonedDateTime) Ticket valid from
validUntil string (ZonedDateTime) Ticket valid until

Attendee/Buyer Info Object

Field Type Description
name string Person's name
email string Person's email
phone string Person's phone (attendee only)

Check-In Record Object

Field Type Description
checkInTime string (ZonedDateTime) When check-in occurred
checkInLocation string Where (e.g., "Main Gate", "VIP Entrance")
checkedInBy string Staff/scanner operator name
dayName string Event day (e.g., "Day 1", "Day 2 - Saturday")
scannerId string Scanner device ID
checkInMethod string Method: QR_SCAN (default), MANUAL, NFC

BookingOrderSummaryResponse Structure

Lightweight response for listing bookings:

{
  "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"
}

Endpoints

1. Get Booking by ID

Purpose: Retrieve complete booking details including all tickets and check-in history

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
bookingId string (UUID) Yes Booking order ID

Success Response: Returns complete BookingOrderResponse structure

Success Response Message: "Booking retrieved successfully"

HTTP Status Code: 200 OK

Access Rules:

Behavior:

Use Cases:

Standard Error Types:

Error Response Examples:

Booking Not Found (404):

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

Access Denied (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "You don't have permission to view this booking",
  "action_time": "2025-12-11T10:30:45",
  "data": "You don't have permission to view this booking"
}

2. Get My Bookings

Purpose: Retrieve all bookings for the authenticated user

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: Returns array of BookingOrderSummaryResponse

Success Response Message: "Bookings retrieved successfully"

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Bookings retrieved successfully",
  "action_time": "2025-12-11T10: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"
    },
    {
      "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"
    }
  ]
}

HTTP Status Code: 200 OK

Sorting: Bookings sorted by bookedAt descending (newest first)

Behavior:

Use Cases:

Standard Error Types:


3. Download Ticket PDF

Purpose: Download the ticket PDF for a specific ticket belonging to the authenticated user

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

Access Level: 🔒 Protected (Authenticated Users)

Authentication: Bearer Token


Request Headers:

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

Path Parameters:

Parameter Type Required Description
ticketId UUID Yes The unique ID of the ticket to download

Query Parameters:

Parameter Type Required Default Description
mode string No download Controls how the PDF is served. download forces a file save dialog. inline opens the PDF directly in the browser.

Mode Values:

Value Content-Disposition Behaviour
download attachment; filename="ticket-{series}.pdf" Browser prompts user to save the file
inline inline; filename="ticket-{series}.pdf" PDF opens directly in the browser tab

Success Response: Returns a binary PDF file

Success Response Headers:

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

HTTP Status Code: 200 OK


Behavior:

Use Cases:


Standard Error Types:

Code Type Description
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Ticket does not belong to the authenticated user
404 NOT_FOUND Ticket with the given ID not found
500 INTERNAL_SERVER_ERROR PDF generation failed

Booking Status Lifecycle

Status Descriptions

Status Description Can Cancel? Tickets Valid?
CONFIRMED Booking active and tickets valid Future (not implemented) Yes
CANCELLED Booking cancelled (placeholder) N/A No

Current Implementation:


Ticket Instance Status

Status Lifecycle

Status Description Can Check In?
ACTIVE Ticket ready for use Yes
USED Ticket checked in Yes (for multi-day)
CANCELLED Ticket cancelled No

Multi-Day Events:


Ticket Series Generation

How Ticket Series Work

Format: {TICKET_CODE}-{COUNTER}

Examples:

Ticket Code Extraction:

  1. Take first 5 characters of ticket type name
  2. Remove spaces
  3. Convert to uppercase
  4. Fallback to "TICK" if name empty

Counter System:

Example Flow:

Ticket Type: "VIP Pass"
Counter: 0

First Booking (3 tickets):
- VIP-0001 (counter now 1)
- VIP-0002 (counter now 2)
- VIP-0003 (counter now 3)

Second Booking (2 tickets):
- VIP-0004 (counter now 4)
- VIP-0005 (counter now 5)

Why Series Numbers?


JWT-Signed QR Codes

Security Architecture

What is the QR Code?

JWT Structure:

Header.Payload.Signature

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ0aWNrZXRJbnN0YW5jZUlkIjoiODgw...
[RSA signature]

JWT Payload Contains:

Multi-Day Event Schedules:

"eventSchedules": [
  {
    "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"
  },
  {
    "dayName": "Day 2 - Conference Day",
    "startDateTime": "2025-12-16T09:00:00+03:00",
    "endDateTime": "2025-12-16T18:00:00+03:00",
    "description": "Technical Sessions & Workshops"
  },
  {
    "dayName": "Day 3 - Closing Day",
    "startDateTime": "2025-12-17T09:00:00+03:00",
    "endDateTime": "2025-12-17T18:00:00+03:00",
    "description": "Finals & Networking"
  }
]

RSA Key Pair:

Verification Process (Scanner App):

  1. Scanner reads QR code
  2. Extracts JWT token
  3. Fetches event's public key
  4. Verifies JWT signature
  5. Checks expiry and validity
  6. Checks if already checked in for this day
  7. Records check-in if valid

Why JWT over Simple Codes?


Multi-Day Event Check-Ins

How Multi-Day Check-Ins Work

Single-Day Event:

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

Check-In Record Fields:

Example: 3-Day Festival

Day 1 (Friday):

{
  "checkInTime": "2025-12-15T18:30:00+03:00",
  "checkInLocation": "Main Gate",
  "checkedInBy": "Staff Member 1",
  "dayName": "Day 1 - Friday Night",
  "scannerId": "SCANNER-001",
  "checkInMethod": "QR_SCAN"
}

Day 2 (Saturday):

{
  "checkInTime": "2025-12-16T14:15:00+03:00",
  "checkInLocation": "VIP Entrance",
  "checkedInBy": "Staff Member 2",
  "dayName": "Day 2 - Saturday",
  "scannerId": "SCANNER-003",
  "checkInMethod": "QR_SCAN"
}

Day 3 (Sunday):

{
  "checkInTime": "2025-12-17T12:00:00+03:00",
  "checkInLocation": "Main Gate",
  "checkedInBy": "Staff Member 1",
  "dayName": "Day 3 - Sunday",
  "scannerId": "SCANNER-001",
  "checkInMethod": "QR_SCAN"
}

Validation Rules:

Benefits:


Event Snapshots

Why Snapshots?

Problem: Event details can change after booking

Solution: Capture event state at booking time

Snapshot Fields (Immutable after booking):

Benefits:

Example Scenario:

  1. User books ticket for "Tech Summit" at KICC
  2. Snapshot created: "Tech Summit", "KICC Nairobi"
  3. Organizer renames event to "African Tech Summit"
  4. Organizer moves venue to "Safari Park Hotel"
  5. User's booking still shows original: "Tech Summit at KICC"
  6. This is what they paid for (legal protection)

Booking Creation Flow

Automatic Creation After Payment

Trigger: EventPaymentCompletedListener fires after successful payment

Steps:

  1. Fetch Entities:

    • Get EventCheckoutSession
    • Get Event details
    • Get TicketType details
  2. Generate Booking Reference:

    • Format: EVT-{8-char-UUID}
    • Example: EVT-A3F4B21C
    • Unique and readable
  3. Create Ticket Instances:

    • For buyer's tickets (ticketsForMe count)
    • For each other attendee (their quantity)
    • Each ticket gets unique series number
    • Each ticket assigned to correct attendee
  4. Generate JWT QR Codes:

    • Create JWT payload with ticket data
    • Include event schedules for multi-day
    • Sign with event's RSA private key
    • Set as ticket's qrCode field
  5. Snapshot Event Details:

    • Capture current event title, times, location
    • Capture organizer details
    • Store in booking (immutable)
  6. Save Booking:

    • Create EventBookingOrderEntity
    • Save to database with all tickets
    • Link to checkout session
  7. Update Checkout Session:

    • Set createdBookingOrderId
    • Mark as COMPLETED
  8. Publish Event:

    • Fire BookingCreatedEvent
    • Triggers notifications
  9. Send Notifications:

    • Email buyer confirmation with tickets
    • Optionally email other attendees their tickets
    • Notify event organizer of new booking

Transaction Safety:

Timing:


Notification System Integration

Who Gets Notified?

1. Buyer (ALWAYS):

2. Other Attendees (CONDITIONAL):

3. Event Organizer (ALWAYS):

Notification Content

Buyer Confirmation:

Attendee Ticket Email:

Organizer Alert:

QR Code Delivery

sendTicketsToAttendees = false:

Buyer receives:
- Ticket 1 QR (for self)
- Ticket 2 QR (for self)
- Ticket 3 QR (for Jane)
- Ticket 4 QR (for Jane)
- Ticket 5 QR (for Bob)

Buyer must forward to Jane and Bob manually

sendTicketsToAttendees = true:

Buyer receives:
- Ticket 1 QR (for self)
- Ticket 2 QR (for self)

Jane receives:
- Ticket 3 QR (for Jane)
- Ticket 4 QR (for Jane)

Bob receives:
- Ticket 5 QR (for Bob)

Everyone gets their own tickets directly

Access Control Rules

Who Can View Bookings?

1. Booking Owner (Customer):

2. Event Organizer:

3. Platform Admins:

4. Others:


Virtual Event Support

Virtual Details Structure

Included for:

Fields:

Example (Zoom):

{
  "platform": "ZOOM",
  "meetingUrl": "https://zoom.us/j/123456789?pwd=abc123",
  "meetingId": "123 456 789",
  "passcode": "summit2025",
  "additionalInstructions": "Please join 5 minutes early for a smooth start"
}

Example (Custom):

{
  "platform": "CUSTOM",
  "meetingUrl": "https://customplatform.com/event/12345",
  "meetingId": null,
  "passcode": null,
  "additionalInstructions": "Use your booking email to log in"
}

Delivery:

Hybrid Events:


Date/Time Formats

DateTime Standards

LocalDateTime (No timezone):

ZonedDateTime (With timezone):

Why Different Formats?

LocalDateTime:

ZonedDateTime:

Example:

Event in Nairobi (UTC+3):
startDateTime: 2025-12-15T09:00:00+03:00

Ticket validFrom: 2025-12-15T09:00:00+03:00
User in London sees: 2025-12-15T06:00:00+00:00 ✓
User in New York sees: 2025-12-15T01:00:00-05:00 ✓

Validation Rules Summary

Booking Retrieval Validation

Access Validation:

Data Validation:


Quick Reference Guide

Common HTTP Status Codes

Booking Reference Format

Ticket Series Format

QR Code Format

Data Format Standards

Booking Flow Checklist

  1. ✅ User completes checkout payment
  2. ✅ Payment listener triggers
  3. ✅ System generates booking reference
  4. ✅ System creates ticket instances
  5. ✅ System generates JWT QR codes
  6. ✅ System snapshots event details
  7. ✅ System saves booking order
  8. ✅ System publishes booking event
  9. ✅ System sends email notifications
  10. ✅ User receives confirmation + QR codes

Best Practices

For Users:

For Developers:

For Organizers:

Error Handling Tips

Common Mistakes to Avoid

❌ Showing other users' bookings
❌ Not displaying virtual details for ONLINE events
❌ Forgetting to show multi-day check-in history
❌ Not caching QR codes for offline use
❌ Displaying mutable event data instead of snapshot
❌ Not grouping attendees' tickets properly
❌ Ignoring timezone in validity checks


Additional Notes

Future Enhancements (Not Yet Implemented)

Booking Cancellation:

Ticket Transfers:

Check-In API:

Booking Modifications:

Integration Points

Payment System:

Notification System:

Escrow System:

Scanner Apps:


Conclusion

The Event Booking Orders API provides comprehensive booking management with:

Security: JWT-signed QR codes prevent fraud
Flexibility: Multi-day events with per-day tracking
Reliability: Event snapshots preserve booking details
Scalability: Unique ticket series with DB counters
User Experience: Clear references and notifications
Multi-Attendee: Support for group bookings
Virtual Events: Full online/hybrid event support
Access Control: Proper permissions for all parties

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: 2025-02-17 Version: v1.0

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                      │
  │   - Form auto-created internally    │
  │   - Default page auto-created       │
  │   - Config saved (displayTime etc.) │
  └──────────────┬──────────────────────┘
                 │
                 │  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    │
  └─────────────────────────────────────┘

Attendee Submission Journey

  [Attendee — event must be PUBLISHED and within registration window]
         │
         │  POST /events/{eventId}/start
         ▼
  ┌──────────────────────────────────┐
  │  Response created (DRAFT)        │  Returns existing draft if one
  │  or existing draft returned      │  already exists (idempotent)
  └──────────────┬───────────────────┘
                 │
                 │  For each page:
                 │  PUT /events/{eventId}/pages/{pageId}/save
                 │  Body: { fieldId: { value, fileUrl, ... }, ... }
                 ▼
  ┌──────────────────────────────────┐
  │  Page answers saved              │  Save is in-place, does NOT
  │  (repeat per page)               │  advance page index automatically
  └──────────────┬───────────────────┘
                 │
                 │  POST /events/{eventId}/submit
                 ▼
  ┌──────────────────────────────────┐
  │  Response SUBMITTED              │  completionTimeSeconds recorded
  └──────────────────────────────────┘

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. 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}" and an initial default page titled "Attendee Information". Only the event organizer can call this. Returns the full 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 with default page",
  "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": [
        {
          "pageId": "1a2b3c4d-...",
          "title": "Attendee Information",
          "description": "Please provide your details",
          "displayOrder": 1,
          "actionButtonText": "Next",
          "fields": []
        }
      ]
    }
  }
}

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

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


22. Get Preview Metadata

Purpose: Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used to render a progress indicator before the attendee starts filling.

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

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": "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

23. Preview Page by Number

Purpose: Returns a specific page by its 1-based position number, including all its active fields and options. Allows the organizer to preview what attendees will see before publishing.

Endpoint: GET /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}

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
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

24. 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 (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
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


25. 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

26. Save Page Answers

Purpose: Saves the attendee's answers for a specific page. Can be called multiple times — each call replaces the previous answers for that page. This is a save-in-place operation; it does not advance the page index. Call submit (Endpoint 28) when all pages are complete.

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

Request JSON Sample:

{
  "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f": {
    "value": "Amina Hassan"
  },
  "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a": {
    "value": "amina@example.com"
  },
  "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b": {
    "value": "optionId-bycar"
  },
  "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c": {
    "value": null,
    "fileUrl": "https://cdn.example.com/uploads/id.pdf",
    "fileName": "national-id.pdf",
    "fileSize": 204800,
    "fileType": "application/pdf"
  }
}

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

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page saved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject with updated answers" }
}

Success Response Fields: data is a FormResponseObject reflecting the updated answer state.

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
422 A required field has no value

27. 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

28. Submit

Purpose: Finalises and submits the attendee's response. The system validates all pages before accepting. Sets status to SUBMITTED and records completionTimeSeconds.

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": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject with status SUBMITTED" }
}

Success Response Fields: data is a FormResponseObject. data.status will be "SUBMITTED".

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 Required fields on one or more pages are unanswered

Organizer Response Management


29. 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

30. 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

31. 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 + create default page
2 PUT /events/{eventId}/settings 🔒 Organizer Update form display settings
3 DELETE /events/{eventId}/disable 🔒 Organizer Disable and remove form
Page Management
4 POST /events/{eventId}/pages 🔒 Organizer Add a page
5 PUT /events/{eventId}/pages/{pageId} 🔒 Organizer Update a page
6 DELETE /events/{eventId}/pages/{pageId}?hard=false 🔒 Organizer Delete a page (soft or hard)
7 POST /events/{eventId}/pages/bulk 🔒 Organizer Bulk add pages with fields
8 DELETE /events/{eventId}/pages/bulk?hard=false 🔒 Organizer Bulk delete pages
9 POST /events/{eventId}/pages/{pageId}/clone 🔒 Organizer Clone a page
Field Management
10 POST /events/{eventId}/pages/{pageId}/fields 🔒 Organizer Add a field to a page
11 PUT /events/{eventId}/fields/{fieldId} 🔒 Organizer Update a field
12 DELETE /events/{eventId}/fields/{fieldId}?hard=false 🔒 Organizer Delete a field (soft or hard)
13 POST /events/{eventId}/pages/{pageId}/fields/bulk 🔒 Organizer Bulk add fields
14 DELETE /events/{eventId}/pages/{pageId}/fields/bulk?hard=false 🔒 Organizer Bulk delete fields
15 PATCH /events/{eventId}/fields/bulk 🔒 Organizer Bulk update fields
16 POST /events/{eventId}/fields/{fieldId}/clone 🔒 Organizer Clone a field
Option Management
17 POST /events/{eventId}/fields/{fieldId}/options 🔒 Organizer Add option to a field
18 PUT /events/{eventId}/options/{optionId} 🔒 Organizer Update an option
19 DELETE /events/{eventId}/options/{optionId}?hard=false 🔒 Organizer Delete an option (soft or hard)
20 DELETE /events/{eventId}/fields/{fieldId}/options/bulk?hard=false 🔒 Organizer Bulk delete options
21 PATCH /events/{eventId}/options/bulk 🔒 Organizer Bulk update option labels
Preview
22 GET /events/{eventId}/preview/metadata 🔒 Organizer Get form page metadata
23 GET /events/{eventId}/preview/pages/{pageNumber} 🔒 Organizer Preview a page by number
24 POST /events/{eventId}/preview/pages/{pageNumber}/validate 🔒 Organizer Validate answers without saving
Attendee Submission
25 POST /events/{eventId}/start 🔒 Attendee Start form / get existing draft
26 PUT /events/{eventId}/pages/{pageId}/save 🔒 Attendee Save page answers
27 GET /events/{eventId}/my-response 🔒 Attendee Get my response
28 POST /events/{eventId}/submit 🔒 Attendee Submit form
Organizer — Responses & Analytics
29 GET /events/{eventId}/responses/{responseId} 🔒 Organizer Get response by ID
30 GET /events/{eventId}/responses 🔒 Organizer Get all responses (paginated)
31 GET /events/{eventId}/analytics 🔒 Organizer Get form analytics

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, Lead Backend Team
Last Updated: 2025-12-11
Version: v1.0

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

Short Description: The Attendance Analytics API provides comprehensive attendee tracking and analytics for event organizers. This API enables organizers to view attendance statistics with per-day breakdowns, list all checked-in attendees with filters, track absentees by category (full no-shows vs specific-day absences), and view detailed check-in history for individual attendees. The system supports multi-day events with separate tracking per day and provides real-time attendance metrics.

Hints:


Response Structures

AttendanceStatsResponse

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2025",
  "totalDays": 3,
  "eventSchedule": [
    {"dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2025-12-15"},
    {"dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2025-12-16"},
    {"dayNumber": 3, "dayName": "Day 3 - Closing", "date": "2025-12-17"}
  ],
  "overallStats": {
    "totalTickets": 500,
    "totalCheckedIn": 462,
    "totalAbsent": 38,
    "attendanceRate": 92.4,
    "byDay": [
      {
        "dayNumber": 1,
        "dayName": "Day 1 - Opening",
        "date": "2025-12-15",
        "totalTickets": 500,
        "checkedIn": 475,
        "absent": 25,
        "attendanceRate": 95.0,
        "status": "COMPLETED"
      }
    ]
  },
  "byTicketType": [
    {
      "ticketTypeId": "uuid",
      "ticketTypeName": "VIP Pass",
      "totalSold": 100,
      "totalCheckedIn": 98,
      "totalAbsent": 2,
      "attendanceRate": 98.0,
      "byDay": [
        {
          "dayNumber": 1,
          "dayName": "Day 1 - Opening",
          "checkedIn": 99,
          "absent": 1,
          "attendanceRate": 99.0
        }
      ]
    }
  ]
}

AttendeeListResponse

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2025",
  "dayNumber": 1,
  "dayName": "Day 1 - Opening",
  "dayDate": "2025-12-15",
  "ticketTypeId": "uuid",
  "ticketTypeName": "VIP Pass",
  "summary": {
    "totalTicketsForType": 100,
    "checkedInThisDay": 99
  },
  "attendees": [
    {
      "ticketInstanceId": "uuid",
      "attendeeName": "John Doe",
      "attendeeEmail": "john@example.com",
      "attendeePhone": "+255712345678",
      "ticketType": "VIP Pass",
      "ticketSeries": "VIP-0001",
      "bookingReference": "EVT-A3F4B21C",
      "pricePaid": 150.00,
      "checkInTime": "2025-12-15T09:15:00+03:00",
      "checkInLocation": "Main Gate",
      "checkedInBy": "Scanner Operator 1",
      "scannerId": "uuid-string"
    }
  ],
  "pagination": {
    "currentPage": 0,
    "pageSize": 50,
    "totalPages": 2,
    "totalElements": 99,
    "hasNext": true,
    "hasPrevious": false
  }
}

AbsenteeListResponse

{
  "eventId": "uuid",
  "eventTitle": "East African Tech Summit 2025",
  "dayNumber": 1,
  "dayName": "Day 1 - Opening",
  "dayDate": "2025-12-15",
  "ticketTypeId": null,
  "ticketTypeName": null,
  "summary": {
    "totalTicketsForType": 500,
    "absentThisDay": 25,
    "absenteeRate": 5.0,
    "breakdown": {
      "fullNoShow": 10,
      "specificDayOnly": 15
    }
  },
  "absentees": [
    {
      "ticketInstanceId": "uuid",
      "attendeeName": "Jane Smith",
      "attendeeEmail": "jane@example.com",
      "attendeePhone": "+255723456789",
      "ticketType": "General Admission",
      "ticketSeries": "GENER-0042",
      "bookingReference": "EVT-B5D2E12F",
      "pricePaid": 50.00,
      "statusForThisDay": "NOT_CHECKED_IN",
      "attendancePattern": {
        "totalEventDays": 3,
        "daysAttended": 2,
        "daysAbsent": 1,
        "attendedDayNumbers": [2, 3],
        "absentDayNumbers": [1],
        "category": "SPECIFIC_DAY_ONLY"
      }
    }
  ],
  "pagination": {
    "currentPage": 0,
    "pageSize": 50,
    "totalPages": 1,
    "totalElements": 25,
    "hasNext": false,
    "hasPrevious": false
  }
}

AttendeeDetailResponse

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

Endpoints

1. Get Attendance Stats

Endpoint: GET /attendance/{eventId}/stats

Access: 🔒 Event Organizer Only

Path Parameters:

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

Success Response: Returns AttendanceStatsResponse

Success Response Message: "Attendance stats retrieved"

Behavior:

Errors:


2. Get Attendees

Endpoint: GET /attendance/{eventId}/attendees?dayNumber=1&ticketTypeId=uuid&search=john&page=0&size=50

Access: 🔒 Event Organizer Only

Path Parameters:

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

Query Parameters:

Parameter Type Required Description
dayNumber integer Multi-day: Yes Day number (1, 2, 3...)
ticketTypeId string (UUID) No Filter by ticket type
search string No Search by name/email
page integer No Page number (0-indexed, default: 0)
size integer No Items per page (default: 50)

Success Response: Returns AttendeeListResponse with pagination

Success Response Message: "Attendees retrieved"

Behavior:

Validation:


3. Get Absentees

Endpoint: GET /attendance/{eventId}/absentees?dayNumber=1&ticketTypeId=uuid&category=FULL_NO_SHOW&search=jane&page=0&size=50

Access: 🔒 Event Organizer Only

Path Parameters:

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

Query Parameters:

Parameter Type Required Description
dayNumber integer Multi-day: Yes Day number (1, 2, 3...)
ticketTypeId string (UUID) No Filter by ticket type
category enum No ALL, FULL_NO_SHOW, SPECIFIC_DAY_ONLY (default: ALL)
search string No Search by name/email
page integer No Page number (0-indexed, default: 0)
size integer No Items per page (default: 50)

Success Response: Returns AbsenteeListResponse with pagination

Success Response Message: "Absentees retrieved"

Behavior:

Absentee Breakdown:


4. Get Attendee Detail

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

Access: 🔒 Event Organizer Only

Path Parameters:

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

Success Response: Returns AttendeeDetailResponse

Success Response Message: "Attendee detail retrieved"

Behavior:

Overall Status:


Absentee Categories Explained

ALL (Default)

Who: Everyone who didn't check in for the specified day

Includes:

Use Case: Complete list of who's missing today

FULL_NO_SHOW

Who: Tickets never checked-in for ANY event day

Characteristics:

Use Case: Identify completely unused tickets for follow-up

SPECIFIC_DAY_ONLY

Who: Attended some days but NOT this specific day

Characteristics:

Use Case: Identify who skipped specific days (e.g., missed keynote)

Example:

3-Day Festival:
- Ticket A: Attended Day 1, 2, 3 → FULLY_ATTENDED
- Ticket B: Attended Day 1, 3 (missed Day 2) → SPECIFIC_DAY_ONLY for Day 2
- Ticket C: Never attended → FULL_NO_SHOW for all days

Multi-Day Event Logic

Day Number Requirements

Single-Day Events:

Multi-Day Events:

Validation:

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

Per-Day Tracking

Statistics:

Day Status:

Example:

3-Day Event (Dec 15-17), Today: Dec 16

Day 1: status=COMPLETED, checkedIn=475
Day 2: status=ONGOING, checkedIn=450
Day 3: status=UPCOMING, checkedIn=0

Attendance Rate Calculations

Overall Attendance Rate

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

Example:

Per-Day Attendance Rate

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

Example:

Per-Ticket-Type Rate

Rate = (Checked-In for Type / Total Sold for Type) × 100

Example:


Search Functionality

Searches:

Match: Partial match (contains)

Examples:


Use Cases

Dashboard Overview

GET /attendance/{eventId}/stats

Shows:
- Overall attendance rate
- Per-day breakdown
- Per-ticket-type breakdown
- Real-time metrics

Check Who Attended Today

GET /attendance/{eventId}/attendees?dayNumber=1

Shows:
- All checked-in attendees for Day 1
- Check-in times and locations
- Searchable and filterable

Find No-Shows

GET /attendance/{eventId}/absentees?dayNumber=1&category=FULL_NO_SHOW

Shows:
- Tickets that were never used
- For follow-up or refund processing

Track Partial Attendance

GET /attendance/{eventId}/absentees?dayNumber=2&category=SPECIFIC_DAY_ONLY

Shows:
- Who attended Day 1 but missed Day 2
- Useful for understanding engagement patterns

Individual Ticket History

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

Shows:
- Complete check-in history
- Which days attended/missed
- Check-in details per day

Best Practices

For Organizers

✅ Monitor attendance in real-time during event
✅ Follow up with full no-shows post-event
✅ Track partial attendance patterns
✅ Export attendee lists for post-event communications
✅ Use absentee data to improve future events

For Developers

✅ Require dayNumber for multi-day events
✅ Show attendance rate with 1 decimal (92.4%)
✅ Implement export to CSV functionality
✅ Cache stats (refresh every 5 minutes)
✅ Display check-in times in event timezone


Quick Reference

HTTP Status Codes

Attendance Status

Day Status

Absentee Categories


Conclusion

The Attendance Analytics API provides comprehensive tracking with:

Real-Time Stats: Instant attendance metrics
Multi-Day Support: Per-day breakdowns
Absentee Tracking: Full no-shows vs partial attendance
Search & Filter: By ticket type, day, name/email
Individual History: Complete check-in records
Attendance Patterns: Understand engagement

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