E_events-nexgate-service(9)

Events Guides & Philosophy

Nexgate-Event-mng-Requrements
1. PLATFORM PHILOSOPHY (the 7 unbreakable laws) 
 
 One person = One lifelong QR forever (physical / online / hybrid / exclusive) 
 Host gets paid 48 hours after each session ends – fastest in the industry 
 Money held in escrow until the session actually happens 
 100 % offline + cryptographically signed QR tokens – zero fraud possible 
 Zero queues – unlimited scanners + self-service kiosks 
 True recurring series that feel exactly like Google Calendar 
 One toggle controls everything: Public ↔ Private ↔ Application-only 
 
 2. ALL EVENT TYPES & COMBINATIONS SUPPORTED 
 Visibility → Public | Private (link) | Application-only
Location → In-person | Online | Hybrid → every combination allowed
Time → One-off | Multi-day | Recurring | Free → every combination allowed
 
 3. MONEY & PAYOUT RULES (applies to EVERYTHING above) 
 
 
 
 Situation 
 Attendee pays 
 Host receives money (bank) 
 
 
 
 
 Public / Private 
 Instantly 
 48 h after session ends 
 
 
 Application-only 
 Only after host approves 
 48 h after session ends 
 
 
 Online / Hybrid 
 Same rules 
 48 h after scheduled end time 
 
 
 Recurring pay-per-session 
 Per session 
 48 h after each class 
 
 
 Monthly unlimited 
 1st of month 
 3–5th of next month 
 
 
 Class packs (10-class etc.) 
 Upfront or per use 
 Per use (or full after 1st class if trusted host) 
 
 
 
 4. FULL PROFESSIONAL FORMS – DOTTED LINES (2025 standard) 
 FORM 1 ─ CREATE NEW EVENT – STEP 1
┌────────────────────────────────────────────────────────────────────┐
│ ← All events New event │
├────────────────────────────────────────────────────────────────────┤
│ Event title │
│ Weekend Yoga & Sound Healing Retreat │ │
│ │
│ Tagline (shown under title) │
│ 2-day oceanfront immersion · March 15–16 │ │
│ │
│ Cover photo │
│ [ Drag & drop or click to upload ] │
│ │
│ Category │
│ Retreat & Wellness ▼ │
│ Tags │
│ #yoga #soundhealing #miami #oceanview #retreat │
│ │
│ Visibility & Access │
│ ○ Public – anyone can find & book │
│ ○ Private – only people with the direct link │
│ ● Application required – you approve every attendee │
│ │
│ Location type │
│ ○ In-person only ○ Online only ● Hybrid (both) │
│ │
│ [Continue →]│
└────────────────────────────────────────────────────────────────────┘

FORM 2 ─ RECURRING SERIES SETUP (the legendary screen)
┌────────────────────────────────────────────────────────────────────┐
│ Event type │
│ ○ One-time event ● Recurring series │
│ │
│ Repeats │
│ ● Every week ○ Every 2 weeks ○ Every month │
│ ○ Custom (advanced) │
│ │
│ Repeats on │
│ [●] Mon [●] Tue [ ] Wed [●] Thu [ ] Fri [ ] Sat [●] Sun │
│ │
│ + Add another class time (different title/time/price) │
│ │
│ Live preview – next 6 weeks │
│ Mon Mar 10 10:00 Gentle Flow $25 │
│ Tue Mar 11 18:30 Vinyasa Power $28 │
│ Thu Mar 13 18:30 Vinyasa Power $28 │
│ Sun Mar 16 17:00 Sunday Restore $25 │
│ … continues forever │
│ │
│ End condition │
│ ● No end date (we show next 18 months automatically) │
│ ○ Ends on ___/___/____ │
│ ○ After ___ occurrences │
│ │
│ [Continue →]│
└────────────────────────────────────────────────────────────────────┘

FORM 3 ─ APPLICATION-ONLY CUSTOM QUESTIONS
┌────────────────────────────────────────────────────────────────────┐
│ Application questions – attendees must answer before payment │
│ │
│ 1. Why do you want to attend this retreat? │
│ Long text – required │
│ │
│ 2. Dietary restrictions or allergies? │
│ Long text – optional │
│ │
│ 3. How experienced are you with yoga? │
│ Beginner / Intermediate / Advanced │
│ │
│ 4. Instagram handle (we love to repost!) │
│ Short text – optional │
│ │
│ 5. Bring a +1 partner? │
│ Yes / No │
│ │
│ + Add another question │
│ [Continue →]│
└────────────────────────────────────────────────────────────────────┘

FORM 4 ─ ATTENDEE APPLYING TO EXCLUSIVE EVENT
┌────────────────────────────────────────────────────────────────────┐
│ Application – Weekend Yoga & Sound Healing Retreat │
│ │
│ Why do you want to attend? │
│ [ I’ve been following Sun Studio for 2 years and this… ] │
│ │
│ Dietary restrictions? │
│ [ Vegan, allergic to nuts ] │
│ Instagram handle │
│ @emmayoga │
│ │
│ You will only be charged if approved │
│ │
│ [Submit Application]│
└────────────────────────────────────────────────────────────────────┘

FORM 5 ─ HOST APPROVAL DASHBOARD
┌────────────────────────────────────────────────────────────────────┐
│ Applications (52 pending · 28 approved · 123 total) │
│ │
│ ○ Emma Wilson @emmayoga Vegan, no nuts [+ Approve] │
│ “Long-time follower, perfect for my birthday” │
│ │
│ ○ Michael & Ana Couple – 10th anniversary [+ Approve ×2] │
│ │
│ ○ John Smith “Will bring camera crew” [− Reject] │
│ │
│ [ ] Select all [Approve selected] [Message selected] │
│ Waitlist: 31 people (auto-approve if spot opens) │
└────────────────────────────────────────────────────────────────────┘

FORM 6 ─ ETERNAL PASS / TICKET (what every attendee has forever)
┌────────────────────────────────────────────────────────────────────┐
│ Emma Wilson – Eternal Pass │
│ Valid forever across all Sun Studio events │
│ │
│ ████████████████████████████████████████████████████████████████ │
│ │
│ Next: Vinyasa Power – Tue Mar 11, 18:30 │
│ Gentle Flow – Mon Mar 17, 10:00 │
│ │
│ ● Hybrid event │
│ [ SCAN AT DOOR ] [ JOIN ZOOM LIVESTREAM ] │
│ │
│ Location revealed 48 h before (approved attendees only) │
│ │
│ + Add to Apple Wallet + Add to Google Wallet │
└────────────────────────────────────────────────────────────────────┘

FORM 7 ─ SELF-SERVICE KIOSK / SCANNER
┌────────────────────────────────────────────────────────────────────┐
│ Welcome to Sun Studio │
│ │
│ Hold your QR code to the camera │
│ │
│ ████████████████████████████ │
│ │
│ → ✓ Emma Wilson – Approved & paid │
│ Welcome back! Enjoy your class │
└────────────────────────────────────────────────────────────────────┘
 
 5. FINAL SUMMARY – You now have everything 
 
 Complete philosophy & rules 
 Every event type & combination 
 Exact money & payout logic 
 Every single professional form with dotted lines 
 Ready to print and give to designers, developers, investors

Nexgate Platform - Check-in System Architecture
Table of Contents 
 
 System Overview 
 Architecture Principles 
 System Components 
 Ticket Generation Flow 
 Scanner Registration Flow 
 Ticket Validation Flow 
 Offline Mode Architecture 
 Security Model 
 Data Models 
 API Specifications 
 Database Schema 
 Deployment Architecture 
 
 
 System Overview 
 What is the Check-in System? 
 The Check-in System is a critical component of the Nexgate platform that handles secure, scalable ticket validation for events. It enables event organizers to verify attendee tickets at entry gates using mobile scanner devices, with the capability to work both online and offline. 
 Key Features 
 
 Cryptographic Security : Uses RSA-signed JWT tokens to prevent ticket forgery 
 Offline Capability : Scanners can validate tickets without internet connectivity 
 Multi-Gate Support : Coordinate validation across multiple entry points 
 Real-time Validation : Immediate duplicate detection when online 
 Scanner Management : Secure device registration and revocation 
 Audit Trail : Complete tracking of all scan activities 
 
 Inspiration 
 The system is inspired by electrical meter voucher systems commonly used in Tanzania and other African countries, where vouchers must be validated offline after purchase, with reconciliation happening later when connectivity is restored. 
 
 Architecture Principles 
 1. Security First 
 
 All tickets are cryptographically signed 
 Cannot be forged without the server's private key 
 Scanner devices only receive public keys for verification 
 
 2. Offline-First Design 
 
 Scanners must function without network connectivity 
 Local validation using JWT signature verification 
 Queue-based synchronization when connectivity returns 
 
 3. Zero-Trust Scanner Model 
 
 Each scanner is individually registered and can be revoked 
 Scanners receive time-limited registration tokens 
 All scanner actions are logged and auditable 
 
 4. Scalability 
 
 Stateless ticket validation (JWT-based) 
 No database queries required for offline validation 
 Server handles only registration and synchronization 
 
 
 System Components 
 Component Diagram 
 ┌─────────────────────────────────────────────────────────────────────────────┐
│ NEXGATE PLATFORM │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Admin Dashboard │────────>│ Event Management │ │
│ └────────┬────────┘ └────────┬─────────┘ │
│ │ │ │
│ │ v │
│ │ ┌─────────────────┐ │
│ │ │ Ticketing │ │
│ │ │ Service │ │
│ │ └────────┬────────┘ │
│ │ │ │
│ v v │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Check-in │<────────│ │ │
│ │ Service │ │ │ │
│ │ ⚡ CORE SYSTEM │ │ │ │
│ └────────┬────────┘ │ │ │
│ │ │ │ │
│ v v v │
│ ┌─────────────────────────────────┐ │
│ │ PostgreSQL Database │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
 ▲ ▲ ▲
 │ │ │
 │ │ │
┌──────────┴────────┐ ┌──────────┴────────┐ ┌───────┴────────┐
│ Scanner App │ │ Customer App │ │ Email Service │
│ ⚡ MOBILE CLIENT │ │ Mobile/Web │ │ (External) │
└───────────────────┘ └──────────┬────────┘ └────────────────┘
 │
 v
 ┌────────────────────┐
 │ Payment Gateway │
 │ (External) │
 └────────────────────┘

Legend:
 ──────> Data Flow
 ⚡ Critical Component
 
 Core Components 
 1. Check-in Service (Spring Boot Backend) 
 Responsibilities: 
 
 Generate and manage scanner registration tokens 
 Issue scanner credentials 
 Generate RSA key pairs for ticket signing 
 Create JWT-based tickets with QR codes 
 Handle online ticket validation requests 
 Receive and process scan logs from scanners 
 Manage scanner settings and configurations 
 Maintain scan history and analytics 
 
 Technology Stack: 
 
 Spring Boot 3.x 
 Spring Security 
 PostgreSQL 
 Redis (for caching and rate limiting) 
 JWT (io.jsonwebtoken library) 
 ZXing (QR code generation) 
 
 2. Scanner Mobile App (Android) 
 Responsibilities: 
 
 Register scanner device using QR code 
 Store scanner credentials and server public key 
 Scan ticket QR codes 
 Validate tickets offline using JWT verification 
 Validate tickets online when connected 
 Queue scan logs for synchronization 
 Sync with server periodically 
 Display scan history and statistics 
 
 Technology Stack: 
 
 Android (Kotlin) 
 Room Database (local storage) 
 WorkManager (background sync) 
 ZXing (QR code scanning) 
 JWT library for validation 
 Retrofit (API communication) 
 
 3. Admin Dashboard (Web) 
 Responsibilities: 
 
 Generate scanner registration QR codes 
 View and manage registered scanners 
 Revoke scanner access 
 Configure scanner settings 
 View scan analytics and reports 
 Monitor real-time scanning activity 
 Export scan data 
 
 Technology Stack: 
 
 React.js / Vue.js 
 Chart.js (analytics) 
 WebSocket (real-time updates) 
 
 
 Ticket Generation Flow 
 Overview 
 When a customer purchases a ticket, the system generates a cryptographically signed JWT token that is embedded in a QR code. This QR code serves as the ticket that the customer presents at the event gate. 
 Detailed Flow 
 Customer Ticketing Check-in Database Email
 │ Service Service │ Service
 │ │ │ │ │
 │ Purchase │ │ │ │
 │ Ticket │ │ │ │
 ├─────────────────>│ │ │ │
 │ │ │ │ │
 │ │ Create Booking │ │ │
 │ ├────────────────────────────────────>│ │
 │ │ │ │ │
 │ │ Request Ticket │ │ │
 │ │ Generation │ │ │
 │ ├────────────────>│ │ │
 │ │ │ │ │
 │ │ │ Load Private │ │
 │ │ │ Key │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Create JWT │ │
 │ │ │ Payload │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Sign JWT with │ │
 │ │ │ Private Key │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Generate QR │ │
 │ │ │ Code │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Store Ticket │ │
 │ │ ├────────────────>│ │
 │ │ │ │ │
 │ │ Return JWT + │ │ │
 │ │ QR Code │ │ │
 │ │<────────────────┤ │ │
 │ │ │ │ │
 │ │ Send Email │ │ │
 │ │ with QR │ │ │
 │ ├──────────────────────────────────────────────────>│
 │ │ │ │ │
 │ │ │ │ Email with │
 │<────────────────────────────────────────────────────────── QR Code │
 │ │ │ │ │
 │ Return Ticket │ │ │ │
 │ Details │ │ │ │
 │<─────────────────┤ │ │ │
 │ │ │ │ │

Legend:
 ───> Synchronous request
 ──> Response
 ─┐ │ Internal processing
 
 Step-by-Step Process 
 Step 1: Customer Completes Purchase 
 Customer → Ticketing Service
- Selects event and ticket type
- Completes payment
- Receives booking confirmation
 
 Step 2: Ticketing Service Requests Ticket Generation 
 Ticketing Service → Check-in Service
POST /api/tickets/generate
{
 "bookingId": "booking-uuid",
 "eventId": "event-uuid",
 "attendeeName": "John Doe",
 "attendeeEmail": "john@example.com",
 "ticketType": "VIP",
 "validFrom": "2025-12-01T18:00:00Z",
 "validUntil": "2025-12-01T23:59:59Z"
}
 
 Step 3: Check-in Service Creates JWT 
 JWT Header: 
 {
 "alg": "RS256",
 "typ": "JWT"
}
 
 JWT Payload: 
 {
 "ticketId": "ticket-uuid-123",
 "bookingId": "booking-uuid",
 "eventId": "event-uuid",
 "eventName": "Tech Conference 2025",
 "attendeeName": "John Doe",
 "attendeeEmail": "john@example.com",
 "ticketType": "VIP",
 "seatNumber": "A-12",
 "iat": 1701234567,
 "exp": 1703826567,
 "nbf": 1701234567
}
 
 Signing Process: 
 1. Encode Header as Base64URL
2. Encode Payload as Base64URL
3. Create signature: 
 SHA256withRSA(base64(header) + "." + base64(payload), PRIVATE_KEY)
4. Final JWT = header.payload.signature
 
 Step 4: Generate QR Code 
 1. Take complete JWT string
2. Generate QR code image (300x300 pixels)
3. Encode as Base64 string
4. Store in ticket record
 
 Step 5: Store and Distribute 
 Database Record:
- ticket_id
- booking_id
- event_id
- jwt_token (full JWT string)
- qr_code_base64
- status (ACTIVE, SCANNED, CANCELLED)
- created_at

Distribution:
- Email to customer with QR code image
- Available in mobile app
- Printable PDF option
 
 Ticket JWT Example 
 eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aWNrZXRJZCI6ImFiYy0xMjMiLCJldmVudElkIjoiZXZlbnQtMDAxIiwiYXR0ZW5kZWVOYW1lIjoiSm9obiBEb2UiLCJhdHRlbmRlZUVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsImlhdCI6MTcwMTIzNDU2NywiZXhwIjoxNzAzODI2NTY3fQ.signature_here
 
 
 Scanner Registration Flow 
 Overview 
 Before a scanner device can validate tickets, it must be registered with the system. This process is similar to WhatsApp's "Link Device" feature, using a time-limited QR code for secure pairing. 
 Detailed Flow 
 Admin Dashboard Check-in Database Scanner
 Service App
 │ │ │ │ │
 │ Click │ │ │ │
 │ "Add │ │ │ │
 │ Scanner" │ │ │ │
 ├─────────>│ │ │ │
 │ │ │ │ │
 │ │ Generate │ │ │
 │ │ Token │ │ │
 │ ├──────────>│ │ │
 │ │ │ │ │
 │ │ │ Create Token │ │
 │ │ │ (UUID) │ │
 │ │ │ Set Expiry │ │
 │ │ │ (5 min) │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Store Token │ │
 │ │ ├─────────────>│ │
 │ │ │ │ │
 │ │ Return │ │ │
 │ │ Token │ │ │
 │ │<──────────┤ │ │
 │ │ │ │ │
 │ │ Generate │ │ │
 │ │ QR Code │ │ │
 │ │─┐ │ │ │
 │ │ │ │ │ │
 │ │<┘ │ │ │
 │ │ │ │ │
 │ Display │ │ │ │
 │ QR Code │ │ │ │
 │<─────────┤ │ │ │
 │ │ │ │ │
 │ │
 │ Scanner scans QR code │
 │ │
 │ Scan QR │ │ │ │
 ├──────────────────────────────────────────────────>│
 │ │ │ │ │
 │ │ │ │ Extract │
 │ │ │ │ Token │
 │ │ │ │─┐ │
 │ │ │ │ │ │
 │ │ │ │<┘ │
 │ │ │ │ │
 │ │ │ Register │ │
 │ │ │ Scanner │ │
 │ │ │<─────────────┼─────────────┤
 │ │ │ {token, │ │
 │ │ │ name} │ │
 │ │ │ │ │
 │ │ │ Validate │ │
 │ │ │ Token │ │
 │ │ ├─────────────>│ │
 │ │ │ │ │
 │ │ │ Token Valid? │ │
 │ │ │<─────────────┤ │
 │ │ │ │ │
 │ │ │ Generate │ │
 │ │ │ Credentials │ │
 │ │ │ (JWT) │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Get Public │ │
 │ │ │ Key │ │
 │ │ │─┐ │ │
 │ │ │ │ │ │
 │ │ │<┘ │ │
 │ │ │ │ │
 │ │ │ Create │ │
 │ │ │ Scanner │ │
 │ │ │ Record │ │
 │ │ ├─────────────>│ │
 │ │ │ │ │
 │ │ │ Mark Token │ │
 │ │ │ as Used │ │
 │ │ ├─────────────>│ │
 │ │ │ │ │
 │ │ │ Return │ │
 │ │ │ {id, │ │
 │ │ │ credentials│ │
 │ │ │ publicKey, │ │
 │ │ │ settings} │ │
 │ │ ├──────────────┼────────────>│
 │ │ │ │ │
 │ │ │ │ Store │
 │ │ │ │ Config │
 │ │ │ │─┐ │
 │ │ │ │ │ │
 │ │ │ │<┘ │
 │ │ │ │ │
 │ Show │ │ │ Show │
 │ Success │ │ │ Success │
 │<─────────────────────────────────────────────────┤
 │ │ │ │ │

Legend:
 ───> Request/Response
 ─┐ │ Internal processing
 .... Scanner scans QR
 
 Step-by-Step Process 
 Step 1: Admin Initiates Registration 
 Admin Dashboard:
1. Navigate to "Scanners" section
2. Click "Add New Scanner" button
3. Specify scanner details:
 - Scanner Name: "Gate A - Main Entrance"
 - Validity: 5 minutes (default)
 - Notes: Optional description
 
 Step 2: Server Generates Registration Token 
 POST /api/registration-tokens/generate
Request:
{
 "validityMinutes": 5,
 "notes": "Gate A scanner for Main Entrance"
}

Server Process:
1. Generate UUID token
2. Calculate expiry time (now + 5 minutes)
3. Store in database:
 - token: "abc-123-xyz-789"
 - expires_at: "2025-11-29T10:15:00Z"
 - used: false
 - created_by: "admin@nexgate.com"

Response:
{
 "token": "abc-123-xyz-789",
 "qrCodeBase64": "data:image/png;base64,iVBORw0KG...",
 "expiresAt": "2025-11-29T10:15:00Z",
 "validityMinutes": 5
}
 
 Step 3: Display QR Code 
 Admin Dashboard displays:
- Large QR code containing the token
- Expiry countdown timer
- Token details
- "Waiting for scanner to connect..." message
 
 Step 4: Scanner Scans QR Code 
 Scanner App:
1. Open camera for QR scanning
2. Scan QR code displayed on admin dashboard
3. Extract token string: "abc-123-xyz-789"
4. Prompt user to confirm device name
 
 Step 5: Scanner Sends Registration Request 
 POST /api/scanners/register
Request:
{
 "token": "abc-123-xyz-789",
 "deviceName": "Gate A Scanner",
 "deviceInfo": {
 "model": "Samsung Galaxy S21",
 "osVersion": "Android 14",
 "appVersion": "1.0.0"
 }
}
 
 Step 6: Server Validates and Issues Credentials 
 Server Validation:
1. Find token in database
2. Check if token exists
3. Check if token.used == false
4. Check if token.expiresAt > now()

If valid:
1. Generate scanner credentials (JWT):
 {
 "scannerId": "scanner-uuid",
 "scannerName": "Gate A Scanner",
 "type": "scanner_credential",
 "iat": now,
 "exp": now + 1 year
 }

2. Sign with server private key

3. Create scanner record in database:
 - scanner_id: UUID
 - name: "Gate A Scanner"
 - credentials: JWT
 - status: ACTIVE
 - settings: default settings JSON
 - created_at: now

4. Mark token as used:
 - used: true
 - used_at: now
 - scanner_name: "Gate A Scanner"

Response:
{
 "scannerId": "scanner-uuid",
 "credentials": "eyJhbGc...scanner_jwt",
 "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...",
 "settings": {
 "offlineModeEnabled": false,
 "syncIntervalMinutes": 15,
 "maxOfflineHours": 24
 }
}
 
 Step 7: Scanner Stores Configuration 
 Scanner App (Local Storage):
1. Save scanner ID
2. Save credentials JWT
3. Convert publicKey string to PublicKey object
4. Save public key
5. Save settings
6. Mark device as "registered"

Scanner is now ready to validate tickets!
 
 Registration Token Lifecycle 
 stateDiagram-v2
 [*] --> Generated: Admin creates token
 Generated --> Active: Token created, timer starts
 Active --> Used: Scanner registers successfully
 Active --> Expired: Time runs out
 Used --> [*]: Token consumed
 Expired --> Deleted: Cleanup job runs
 Deleted --> [*]
 
 note right of Active
 Valid for 5 minutes
 Can only be used once
 end note
 
 
 Ticket Validation Flow 
 Online Mode (Default) 
 sequenceDiagram
 participant Attendee
 participant ScannerApp
 participant CheckInService
 participant Database
 participant Cache
 
 Attendee->>ScannerApp: Present QR Code
 ScannerApp->>ScannerApp: Scan QR Code<br/>Extract JWT
 
 ScannerApp->>CheckInService: Validate Ticket<br/>POST /api/tickets/validate<br/>{jwt, scannerId}
 
 CheckInService->>CheckInService: Verify JWT Signature<br/>using Public Key
 
 alt Signature Invalid
 CheckInService-->>ScannerApp: Error: Invalid Ticket (Forged)
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Invalid Ticket
 else Signature Valid
 CheckInService->>CheckInService: Check JWT Expiration
 
 alt Ticket Expired
 CheckInService-->>ScannerApp: Error: Ticket Expired
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Ticket Expired
 else Ticket Valid
 CheckInService->>Cache: Check if already scanned<br/>(Redis: ticketId)
 
 alt Already Scanned
 Cache-->>CheckInService: Ticket found in scanned set
 CheckInService-->>ScannerApp: Error: Already Scanned
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Ticket Already Used
 else First Scan
 CheckInService->>Cache: Add to scanned set<br/>SET scanned:ticketId
 CheckInService->>Database: Record Scan<br/>{ticketId, scannerId, timestamp}
 CheckInService-->>ScannerApp: Success: Entry Granted
 ScannerApp-->>Attendee: ✅ ENTRY GRANTED<br/>Welcome!
 end
 end
 end
 
 Offline Mode (Emergency) 
 sequenceDiagram
 participant Attendee
 participant ScannerApp
 participant LocalDB
 participant PublicKey
 
 Note over ScannerApp: Scanner is OFFLINE<br/>No internet connection
 
 Attendee->>ScannerApp: Present QR Code
 ScannerApp->>ScannerApp: Scan QR Code<br/>Extract JWT
 
 ScannerApp->>ScannerApp: Parse JWT<br/>(header.payload.signature)
 
 ScannerApp->>PublicKey: Verify Signature<br/>using stored Public Key
 
 alt Signature Invalid
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Invalid Ticket
 else Signature Valid
 ScannerApp->>ScannerApp: Check JWT Expiration<br/>(from exp claim)
 
 alt Ticket Expired
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Ticket Expired
 else Ticket Valid
 ScannerApp->>LocalDB: Check local scanned list<br/>SELECT WHERE ticketId = ?
 
 alt Already Scanned Locally
 LocalDB-->>ScannerApp: Ticket found
 ScannerApp-->>Attendee: ❌ ENTRY DENIED<br/>Already Scanned (Local)
 else First Scan Locally
 ScannerApp->>LocalDB: Add to scanned list<br/>INSERT scan record
 ScannerApp->>LocalDB: Add to sync queue<br/>{ticketId, timestamp, offline:true}
 ScannerApp-->>Attendee: ✅ ENTRY GRANTED<br/>(OFFLINE MODE)
 
 Note over ScannerApp: Scan queued for sync<br/>when connection returns
 end
 end
 end
 
 Validation Details 
 JWT Signature Verification (Cryptographic Process) 
 Server Side (Ticket Generation):
1. Create payload: {ticketId, eventId, ...}
2. Sign: SIGNATURE = SHA256withRSA(header.payload, PRIVATE_KEY)
3. Result: JWT = header.payload.signature

Scanner Side (Validation):
1. Split JWT: parts = jwt.split(".")
2. Extract: header = parts[0], payload = parts[1], signature = parts[2]
3. Verify: SHA256withRSA.verify(header.payload, signature, PUBLIC_KEY)
4. If verification succeeds → Ticket is authentic
5. If verification fails → Ticket is forged/tampered
 
 Online Validation Steps 
 1. Authentication Check
 - Verify scanner credentials (JWT)
 - Check if scanner is ACTIVE (not revoked)

2. Ticket Signature Verification
 - Parse JWT
 - Verify RSA signature
 - If invalid → REJECT (forged ticket)

3. Expiration Check
 - Extract exp claim from JWT
 - Compare with current time
 - If expired → REJECT

4. Duplicate Check (Redis)
 - Key: "scanned:{ticketId}"
 - Check if key exists
 - If exists → REJECT (already scanned)
 - If not exists → SET key with TTL (24 hours)

5. Database Logging
 - Insert into ticket_scans table:
 {
 ticket_id: from JWT,
 scanner_id: from request,
 scanned_at: current timestamp,
 validation_mode: "ONLINE",
 scan_result: "SUCCESS"
 }

6. Response
 - Return success with attendee details
 - Scanner displays: "Welcome, {attendee_name}!"
 
 Offline Validation Steps 
 1. JWT Parsing
 - Split JWT into parts
 - Decode Base64URL payload

2. Signature Verification (Local)
 - Use stored PUBLIC_KEY
 - Verify signature cryptographically
 - If invalid → REJECT

3. Expiration Check (Local)
 - Extract exp from payload
 - Compare with device time
 - If expired → REJECT

4. Local Duplicate Check
 - Query local SQLite: 
 SELECT * FROM scanned_tickets WHERE ticket_id = ?
 - If found → REJECT
 - If not found → Continue

5. Local Recording
 - INSERT INTO scanned_tickets (ticket_id, scanned_at)
 - INSERT INTO sync_queue (ticket_id, scanned_at, synced: false)

6. Response
 - Display: "Entry Granted (Offline Mode)"
 - Show sync pending indicator
 
 
 Offline Mode Architecture 
 Why Offline Mode? 
 Events often happen in locations with poor or no internet connectivity: 
 
 Rural areas 
 Basements/underground venues 
 High-attendance events (network congestion) 
 Outdoor festivals 
 Emergency situations 
 
 The system must continue functioning even without internet. 
 Offline Capabilities 
 graph TB
 subgraph "Online Operations"
 O1[Real-time duplicate detection across all gates]
 O2[Immediate sync to central database]
 O3[Live analytics dashboard]
 O4[Scanner settings updates]
 end
 
 subgraph "Offline Operations"
 F1[JWT signature verification]
 F2[Local duplicate detection at same gate]
 F3[Scan logging to local database]
 F4[Queue for later sync]
 end
 
 subgraph "Limitations in Offline Mode"
 L1[Cannot detect duplicates at other gates]
 L2[Cannot receive scanner revocations immediately]
 L3[Cannot update settings in real-time]
 L4[Relies on device clock for expiry check]
 end
 
 style F1 fill:#90EE90
 style F2 fill:#90EE90
 style L1 fill:#FFB6C1
 style L2 fill:#FFB6C1
 
 Offline Data Flow 
 sequenceDiagram
 participant Gate1 as Scanner (Gate 1)
 participant Gate2 as Scanner (Gate 2)
 participant Server as Check-in Service
 
 Note over Gate1,Gate2: Both scanners go OFFLINE
 
 rect rgb(255, 200, 200)
 Note over Gate1,Gate2: OFFLINE PERIOD
 
 Gate1->>Gate1: Scan Ticket ABC<br/>✅ Valid (first scan at Gate 1)
 Gate2->>Gate2: Scan Ticket ABC<br/>✅ Valid (Gate 2 doesn't know)
 
 Note over Gate1,Gate2: PROBLEM: Same ticket scanned twice!<br/>Offline mode cannot prevent this.
 end
 
 Note over Gate1,Gate2: Connection restored
 
 rect rgb(200, 255, 200)
 Note over Gate1,Gate2: SYNC PERIOD
 
 Gate1->>Server: Sync scans<br/>[{ticketABC, 10:00am}]
 Server->>Server: Record: Gate 1 scanned ABC at 10:00
 
 Gate2->>Server: Sync scans<br/>[{ticketABC, 10:05am}]
 Server->>Server: Detect: ABC already scanned!<br/>Flag as duplicate
 Server->>Server: Create alert for investigation
 end
 
 Sync Strategy 
 When to Sync 
 1. Automatic Sync Triggers:
 - Every N minutes (configurable, default: 15 minutes)
 - When connection restored after being offline
 - When scanner app comes to foreground
 - Before device goes to sleep

2. Manual Sync:
 - Admin can trigger sync from scanner UI
 - Force sync button available

3. Smart Sync:
 - Only sync if there are pending scans
 - Batch multiple scans in single request
 - Retry failed syncs with exponential backoff
 
 Sync Process 
 sequenceDiagram
 participant Scanner
 participant LocalDB
 participant CheckInService
 participant Database
 
 Scanner->>LocalDB: Get pending scans<br/>SELECT * FROM sync_queue<br/>WHERE synced = false
 LocalDB-->>Scanner: Return scan records
 
 Scanner->>CheckInService: POST /api/scanners/sync<br/>{scannerId, scans: [...]}
 
 CheckInService->>CheckInService: Authenticate Scanner
 
 loop For each scan
 CheckInService->>Database: Check if ticket already scanned
 
 alt First scan of this ticket
 CheckInService->>Database: Record scan
 CheckInService->>CheckInService: Mark as SUCCESS
 else Duplicate scan
 CheckInService->>Database: Record as DUPLICATE_SCAN
 CheckInService->>CheckInService: Mark as DUPLICATE<br/>Create alert
 end
 end
 
 CheckInService->>CheckInService: Get latest scanner settings
 CheckInService-->>Scanner: Return {syncResults, settings}
 
 Scanner->>LocalDB: Update sync_queue<br/>SET synced = true
 Scanner->>Scanner: Apply new settings if changed
 
 Sync Payload Example 
 POST /api/scanners/sync
Request:
{
 "scannerId": "scanner-uuid",
 "scans": [
 {
 "ticketId": "ticket-123",
 "scannedAt": "2025-11-29T10:05:00Z",
 "validationMode": "OFFLINE",
 "deviceTime": "2025-11-29T10:05:00Z"
 },
 {
 "ticketId": "ticket-456",
 "scannedAt": "2025-11-29T10:10:00Z",
 "validationMode": "OFFLINE",
 "deviceTime": "2025-11-29T10:10:00Z"
 }
 ],
 "lastSyncAt": "2025-11-29T09:00:00Z"
}

Response:
{
 "syncResults": [
 {
 "ticketId": "ticket-123",
 "status": "SUCCESS",
 "message": "Scan recorded"
 },
 {
 "ticketId": "ticket-456",
 "status": "DUPLICATE",
 "message": "Ticket already scanned at Gate B",
 "originalScanTime": "2025-11-29T10:08:00Z",
 "originalScanner": "Gate B Scanner"
 }
 ],
 "settings": {
 "offlineModeEnabled": true,
 "syncIntervalMinutes": 15,
 "maxOfflineHours": 24
 },
 "serverTime": "2025-11-29T10:30:00Z"
}
 
 Conflict Resolution 
 Scenario: Same ticket scanned at multiple gates while offline

Gate A (10:05am): Scans ticket ABC - Valid ✅
Gate B (10:08am): Scans ticket ABC - Valid ✅ (doesn't know about Gate A)

Both sync at 10:30am:

Server Resolution:
1. Receive scan from Gate A (ABC at 10:05)
 - First scan seen by server
 - Record as VALID
 
2. Receive scan from Gate B (ABC at 10:08)
 - Server already has ABC scanned at 10:05
 - Record as DUPLICATE
 - Create alert for investigation
 
3. Admin Investigation:
 - Review: Was this intentional fraud?
 - Or: Genuine user scanned at wrong gate first?
 - Action: Take appropriate measures

4. Prevention for Future:
 - Reduce offline periods
 - More frequent syncs
 - Better gate coordination
 
 
 Security Model 
 Cryptographic Architecture 
 graph TB
 subgraph "Server (Trust Anchor)"
 PrivateKey[RSA Private Key<br/>4096-bit<br/>NEVER leaves server]
 PublicKey[RSA Public Key<br/>Distributed to scanners]
 end
 
 subgraph "Ticket Generation"
 Ticket[Ticket Data<br/>JSON Payload]
 JWT[Signed JWT Token]
 QR[QR Code<br/>Contains JWT]
 end
 
 subgraph "Scanner Device"
 StoredPubKey[Stored Public Key]
 Verification[Signature Verification]
 end
 
 PrivateKey -->|Signs| JWT
 PublicKey -->|Copied to| StoredPubKey
 Ticket -->|Payload| JWT
 JWT -->|Encoded in| QR
 
 QR -->|Scanned| Verification
 StoredPubKey -->|Verifies| Verification
 
 style PrivateKey fill:#ff6666
 style PublicKey fill:#66ff66
 style Verification fill:#6666ff
 
 Security Layers 
 Layer 1: Scanner Authentication 
 Every scanner request must include:
- Scanner credentials (JWT)
- Signed with server's private key during registration
- Contains: scannerId, scannerName, expiry (1 year)

Server validates:
1. JWT signature is valid
2. JWT not expired
3. Scanner status is ACTIVE (not revoked)
4. Scanner ID exists in database
 
 Layer 2: Ticket Cryptography 
 Ticket Security Guarantees:

1. Cannot Forge Tickets
 - Requires server's private key to sign
 - Private key never leaves server
 - Scanners only have public key (can verify, not sign)

2. Cannot Tamper with Tickets
 - Any modification invalidates signature
 - Changing even 1 character breaks verification
 - Scanner detects tampering immediately

3. Cannot Reuse Expired Tickets
 - Expiry timestamp in JWT payload
 - Verified during each scan
 - Cannot be modified (signature protection)

4. Cannot Clone Tickets (Online Mode)
 - Each ticketId tracked in Redis
 - Duplicate detection across all gates
 - Scan recorded in database
 
 Layer 3: Network Security 
 All API Communication:
- HTTPS/TLS 1.3 only
- Certificate pinning in mobile app
- API rate limiting
- Request signing for sensitive operations
 
 Layer 4: Scanner Revocation 
 Immediate Revocation:
1. Admin marks scanner as REVOKED
2. Scanner added to revocation list (Redis)
3. Next API request from scanner → DENIED
4. Settings push: {status: "REVOKED"}
5. Scanner clears local data

Scanner receives revocation on:
- Next sync attempt
- Real-time push (if WebSocket connected)
- Settings update request
 
 Attack Scenarios and Mitigations 
 Attack 1: Ticket Forgery 
 Attack: Attacker creates fake ticket JWT
Mitigation: 
- Cannot sign without private key
- Signature verification fails
- Scanner rejects ticket
Result: ❌ Attack prevented
 
 Attack 2: Ticket Cloning 
 Attack: User shares same ticket QR with friend

Online Mode:
- First scan: ✅ Valid
- Second scan: ❌ Duplicate detected
Result: ✅ Attack prevented

Offline Mode:
- Different gates: Both scans succeed locally
- Server detects on sync
- Alert generated for investigation
Result: ⚠️ Detected post-facto
 
 Attack 3: Scanner Credential Theft 
 Attack: Attacker steals scanner credentials from device

Mitigation:
1. Admin revokes stolen scanner
2. Scanner credential becomes invalid
3. Server denies all requests
4. New scanner issued with new credentials

Best Practice:
- Secure credential storage (Android Keystore)
- Device encryption
- Remote wipe capability
Result: ✅ Contained quickly
 
 Attack 4: Replay Attack 
 Attack: Attacker captures network traffic, replays requests

Mitigation:
- HTTPS prevents traffic capture
- Request timestamps checked
- Nonce validation for sensitive operations
- Short-lived sessions
Result: ✅ Attack prevented
 
 
 Data Models 
 Database Entities 
 1. registration_tokens 
 CREATE TABLE registration_tokens (
 id UUID PRIMARY KEY,
 token VARCHAR(100) UNIQUE NOT NULL,
 expires_at TIMESTAMP NOT NULL,
 used BOOLEAN DEFAULT FALSE,
 used_at TIMESTAMP,
 created_by VARCHAR(100),
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 validity_minutes INTEGER NOT NULL,
 scanner_name VARCHAR(200),
 notes TEXT
);

-- Indexes
CREATE INDEX idx_token ON registration_tokens(token);
CREATE INDEX idx_valid_tokens ON registration_tokens(used, expires_at) 
 WHERE used = FALSE;
 
 Purpose: Store time-limited tokens for scanner registration 
 Lifecycle: 
 
 Created when admin generates QR code 
 Marked as used when scanner registers 
 Cleaned up after 7 days (scheduled job) 
 
 2. scanners 
 CREATE TABLE scanners (
 id UUID PRIMARY KEY,
 scanner_id VARCHAR(100) UNIQUE NOT NULL,
 name VARCHAR(200) NOT NULL,
 credentials TEXT NOT NULL,
 status VARCHAR(20) NOT NULL, -- ACTIVE, REVOKED
 settings JSONB NOT NULL,
 device_info JSONB,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 last_synced_at TIMESTAMP,
 created_by VARCHAR(100)
);

-- Indexes
CREATE INDEX idx_scanner_id ON scanners(scanner_id);
CREATE INDEX idx_scanner_status ON scanners(status);
CREATE INDEX idx_last_synced ON scanners(last_synced_at);
 
 Settings JSONB Structure: 
 {
 "offlineModeEnabled": false,
 "syncIntervalMinutes": 15,
 "offlineDataSource": "AUTO_SYNC",
 "maxOfflineHours": 24,
 "allowedEventIds": ["event-1", "event-2"]
}
 
 3. tickets 
 CREATE TABLE tickets (
 id UUID PRIMARY KEY,
 ticket_id VARCHAR(100) UNIQUE NOT NULL,
 booking_id UUID NOT NULL,
 event_id UUID NOT NULL,
 attendee_name VARCHAR(200) NOT NULL,
 attendee_email VARCHAR(200),
 ticket_type VARCHAR(50),
 jwt_token TEXT NOT NULL,
 qr_code_base64 TEXT,
 status VARCHAR(20) NOT NULL, -- ACTIVE, SCANNED, CANCELLED
 valid_from TIMESTAMP NOT NULL,
 valid_until TIMESTAMP NOT NULL,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 
 FOREIGN KEY (booking_id) REFERENCES bookings(id),
 FOREIGN KEY (event_id) REFERENCES events(id)
);

-- Indexes
CREATE INDEX idx_ticket_id ON tickets(ticket_id);
CREATE INDEX idx_booking_id ON tickets(booking_id);
CREATE INDEX idx_event_id ON tickets(event_id);
CREATE INDEX idx_ticket_status ON tickets(status);
 
 4. ticket_scans 
 CREATE TABLE ticket_scans (
 id UUID PRIMARY KEY,
 ticket_id VARCHAR(100) NOT NULL,
 scanner_id UUID NOT NULL,
 scanned_at TIMESTAMP NOT NULL,
 validation_mode VARCHAR(20) NOT NULL, -- ONLINE, OFFLINE
 scan_result VARCHAR(20) NOT NULL, -- SUCCESS, DUPLICATE, EXPIRED, INVALID
 device_time TIMESTAMP,
 synced_at TIMESTAMP,
 metadata JSONB,
 
 FOREIGN KEY (scanner_id) REFERENCES scanners(id)
);

-- Indexes
CREATE INDEX idx_ticket_scans_ticket ON ticket_scans(ticket_id);
CREATE INDEX idx_ticket_scans_scanner ON ticket_scans(scanner_id);
CREATE INDEX idx_ticket_scans_time ON ticket_scans(scanned_at);
CREATE INDEX idx_ticket_scans_result ON ticket_scans(scan_result);
 
 Metadata JSONB Example: 
 {
 "attendeeName": "John Doe",
 "eventName": "Tech Conference 2025",
 "gateLocation": "Main Entrance",
 "duplicateOf": "scan-uuid-123",
 "alertGenerated": true
}
 
 5. rsa_keys 
 CREATE TABLE rsa_keys (
 id UUID PRIMARY KEY,
 key_version INTEGER UNIQUE NOT NULL,
 private_key TEXT NOT NULL, -- Encrypted
 public_key TEXT NOT NULL,
 algorithm VARCHAR(20) DEFAULT 'RS256',
 key_size INTEGER DEFAULT 4096,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 rotated_at TIMESTAMP,
 status VARCHAR(20) NOT NULL -- ACTIVE, ROTATED, REVOKED
);

-- Only one active key at a time
CREATE UNIQUE INDEX idx_active_key ON rsa_keys(status) 
 WHERE status = 'ACTIVE';
 
 Scanner Local Database (SQLite) 
 -- Scanner credentials and config
CREATE TABLE scanner_config (
 key VARCHAR(50) PRIMARY KEY,
 value TEXT NOT NULL
);

-- Locally scanned tickets
CREATE TABLE scanned_tickets (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 ticket_id VARCHAR(100) UNIQUE NOT NULL,
 scanned_at TIMESTAMP NOT NULL,
 attendee_name VARCHAR(200),
 event_name VARCHAR(200),
 validation_result VARCHAR(20) NOT NULL
);

-- Pending sync queue
CREATE TABLE sync_queue (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 ticket_id VARCHAR(100) NOT NULL,
 scanned_at TIMESTAMP NOT NULL,
 device_time TIMESTAMP NOT NULL,
 validation_mode VARCHAR(20) NOT NULL,
 synced BOOLEAN DEFAULT FALSE,
 sync_attempts INTEGER DEFAULT 0,
 last_sync_attempt TIMESTAMP,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_sync_pending ON sync_queue(synced) WHERE synced = FALSE;
CREATE INDEX idx_scanned_ticket_id ON scanned_tickets(ticket_id);
 
 
 API Specifications 
 Base URL 
 Production: https://api.nexgate.com/v1
Staging: https://staging-api.nexgate.com/v1
 
 Authentication 
 All requests require authentication via JWT in header:
Authorization: Bearer {scanner_credentials_jwt}
 
 API Endpoints 
 1. Generate Registration Token 
 POST /api/registration-tokens/generate
Authorization: Bearer {admin_jwt}

Request Body:
{
 "validityMinutes": 5,
 "notes": "Gate A scanner for Main Entrance"
}

Response: 201 Created
{
 "token": "abc-123-xyz-789",
 "qrCodeBase64": "data:image/png;base64,iVBORw0KG...",
 "expiresAt": "2025-11-29T10:15:00Z",
 "validityMinutes": 5
}

Errors:
400 Bad Request - Invalid validity minutes
401 Unauthorized - Invalid admin credentials
 
 2. Register Scanner 
 POST /api/scanners/register

Request Body:
{
 "token": "abc-123-xyz-789",
 "deviceName": "Gate A Scanner",
 "deviceInfo": {
 "model": "Samsung Galaxy S21",
 "osVersion": "Android 14",
 "appVersion": "1.0.0"
 }
}

Response: 201 Created
{
 "scannerId": "scanner-uuid",
 "credentials": "eyJhbGc...scanner_jwt",
 "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg...",
 "settings": {
 "offlineModeEnabled": false,
 "syncIntervalMinutes": 15,
 "maxOfflineHours": 24
 }
}

Errors:
400 Bad Request - Invalid or expired token
409 Conflict - Token already used
 
 3. Validate Ticket (Online) 
 POST /api/tickets/validate
Authorization: Bearer {scanner_credentials}

Request Body:
{
 "jwt": "eyJhbGc...ticket_jwt",
 "scannerId": "scanner-uuid",
 "deviceTime": "2025-11-29T10:30:00Z"
}

Response: 200 OK
{
 "valid": true,
 "ticketId": "ticket-123",
 "attendeeName": "John Doe",
 "eventName": "Tech Conference 2025",
 "ticketType": "VIP",
 "message": "Entry granted"
}

Response: 400 Bad Request (Duplicate)
{
 "valid": false,
 "ticketId": "ticket-123",
 "reason": "DUPLICATE",
 "message": "Ticket already scanned",
 "originalScanTime": "2025-11-29T10:25:00Z",
 "originalScanner": "Gate B Scanner"
}

Response: 400 Bad Request (Expired)
{
 "valid": false,
 "ticketId": "ticket-123",
 "reason": "EXPIRED",
 "message": "Ticket has expired",
 "expiredAt": "2025-11-29T10:00:00Z"
}

Response: 400 Bad Request (Invalid)
{
 "valid": false,
 "reason": "INVALID_SIGNATURE",
 "message": "Ticket signature is invalid (possible forgery)"
}

Errors:
401 Unauthorized - Invalid scanner credentials
403 Forbidden - Scanner revoked
 
 4. Sync Scanner 
 POST /api/scanners/sync
Authorization: Bearer {scanner_credentials}

Request Body:
{
 "scannerId": "scanner-uuid",
 "scans": [
 {
 "ticketId": "ticket-123",
 "scannedAt": "2025-11-29T10:05:00Z",
 "validationMode": "OFFLINE",
 "deviceTime": "2025-11-29T10:05:00Z"
 }
 ],
 "lastSyncAt": "2025-11-29T09:00:00Z"
}

Response: 200 OK
{
 "syncResults": [
 {
 "ticketId": "ticket-123",
 "status": "SUCCESS",
 "message": "Scan recorded"
 }
 ],
 "settings": {
 "offlineModeEnabled": true,
 "syncIntervalMinutes": 15
 },
 "serverTime": "2025-11-29T10:30:00Z",
 "pendingUpdates": []
}

Errors:
401 Unauthorized - Invalid scanner credentials
403 Forbidden - Scanner revoked
 
 5. Get Scanner Settings 
 GET /api/scanners/{scannerId}/settings
Authorization: Bearer {scanner_credentials}

Response: 200 OK
{
 "offlineModeEnabled": false,
 "syncIntervalMinutes": 15,
 "offlineDataSource": "AUTO_SYNC",
 "maxOfflineHours": 24,
 "allowedEventIds": ["event-1", "event-2"]
}

Errors:
401 Unauthorized - Invalid scanner credentials
404 Not Found - Scanner not found
 
 6. Revoke Scanner 
 POST /api/scanners/{scannerId}/revoke
Authorization: Bearer {admin_jwt}

Response: 200 OK
{
 "scannerId": "scanner-uuid",
 "status": "REVOKED",
 "revokedAt": "2025-11-29T10:30:00Z"
}

Errors:
401 Unauthorized - Invalid admin credentials
404 Not Found - Scanner not found
 
 
 Database Schema 
 Full Schema Diagram 
 erDiagram
 REGISTRATION_TOKENS ||--o| SCANNERS : "used_by"
 SCANNERS ||--o{ TICKET_SCANS : "performs"
 TICKETS ||--o{ TICKET_SCANS : "scanned"
 EVENTS ||--o{ TICKETS : "contains"
 BOOKINGS ||--|| TICKETS : "generates"
 RSA_KEYS ||--o{ TICKETS : "signs"
 
 REGISTRATION_TOKENS {
 uuid id PK
 varchar token UK
 timestamp expires_at
 boolean used
 timestamp used_at
 varchar created_by
 int validity_minutes
 varchar scanner_name
 }
 
 SCANNERS {
 uuid id PK
 varchar scanner_id UK
 varchar name
 text credentials
 varchar status
 jsonb settings
 jsonb device_info
 timestamp last_synced_at
 }
 
 TICKETS {
 uuid id PK
 varchar ticket_id UK
 uuid booking_id FK
 uuid event_id FK
 varchar attendee_name
 text jwt_token
 text qr_code_base64
 varchar status
 timestamp valid_from
 timestamp valid_until
 }
 
 TICKET_SCANS {
 uuid id PK
 varchar ticket_id FK
 uuid scanner_id FK
 timestamp scanned_at
 varchar validation_mode
 varchar scan_result
 jsonb metadata
 }
 
 EVENTS {
 uuid id PK
 varchar name
 timestamp event_date
 varchar venue
 }
 
 BOOKINGS {
 uuid id PK
 uuid event_id FK
 varchar customer_email
 timestamp booking_date
 }
 
 RSA_KEYS {
 uuid id PK
 int key_version UK
 text private_key
 text public_key
 varchar status
 }
 
 Key Relationships 
 1. registration_tokens → scanners
 - One token can register one scanner
 - Token is marked as used when scanner created
 
2. scanners → ticket_scans
 - One scanner performs many scans
 - Track which scanner scanned which ticket
 
3. tickets → ticket_scans
 - One ticket can be scanned multiple times (duplicates logged)
 - Each scan recorded separately
 
4. events → tickets
 - One event has many tickets
 - Tickets belong to specific event
 
5. bookings → tickets
 - One booking generates one or more tickets
 - Ticket inherits customer info from booking
 
6. rsa_keys → tickets
 - Active RSA key used to sign all new tickets
 - Key rotation supported for security
 
 
 Deployment Architecture 
 Infrastructure Overview 
 graph TB
 subgraph "Client Layer"
 ScannerApp[Scanner Android App]
 AdminWeb[Admin Web Dashboard]
 CustomerApp[Customer Mobile App]
 end
 
 subgraph "Load Balancer"
 LB[AWS Application Load Balancer]
 end
 
 subgraph "Application Layer"
 API1[Check-in Service Instance 1]
 API2[Check-in Service Instance 2]
 API3[Check-in Service Instance 3]
 end
 
 subgraph "Cache Layer"
 Redis[(Redis Cluster<br/>Scan Deduplication)]
 end
 
 subgraph "Database Layer"
 PG_Primary[(PostgreSQL Primary)]
 PG_Replica[(PostgreSQL Replica)]
 end
 
 subgraph "Storage Layer"
 S3[S3 Bucket<br/>QR Code Images]
 end
 
 subgraph "Monitoring"
 CloudWatch[CloudWatch Logs]
 Grafana[Grafana Dashboard]
 end
 
 ScannerApp --> LB
 AdminWeb --> LB
 CustomerApp --> LB
 
 LB --> API1
 LB --> API2
 LB --> API3
 
 API1 --> Redis
 API2 --> Redis
 API3 --> Redis
 
 API1 --> PG_Primary
 API2 --> PG_Primary
 API3 --> PG_Primary
 
 PG_Primary --> PG_Replica
 
 API1 --> S3
 API2 --> S3
 API3 --> S3
 
 API1 --> CloudWatch
 API2 --> CloudWatch
 API3 --> CloudWatch
 
 CloudWatch --> Grafana
 
 Deployment Configuration 
 Production Environment 
 Check-in Service:
 Instances: 3 (Auto-scaling: 2-10)
 Instance Type: t3.large (2 vCPU, 8 GB RAM)
 Deployment: Blue-Green with ECS
 Health Check: /api/health every 30s
 
Database:
 Type: Amazon RDS PostgreSQL 15
 Instance: db.r6g.xlarge (4 vCPU, 32 GB RAM)
 Storage: 500 GB SSD (auto-scaling enabled)
 Replication: 1 read replica in different AZ
 Backup: Daily automated backups, 30-day retention
 
Redis:
 Type: Amazon ElastiCache
 Node Type: cache.r6g.large (2 vCPU, 13 GB RAM)
 Cluster: 3 nodes (1 primary, 2 replicas)
 Persistence: AOF enabled
 
Load Balancer:
 Type: Application Load Balancer
 SSL: AWS Certificate Manager
 Zones: Multi-AZ deployment
 
Monitoring:
 CloudWatch: All application and infrastructure metrics
 Grafana: Custom dashboards for scan analytics
 PagerDuty: Alert escalation
 
Backups:
 Database: Daily automated + on-demand
 Redis: Daily snapshots
 S3: Versioning enabled
 
 Scaling Strategy 
 Horizontal Scaling (API Instances):
- Metric: CPU > 70% or Request Count > 1000/min
- Scale up: Add 1 instance
- Scale down: Remove 1 instance if CPU < 30%
- Min instances: 2
- Max instances: 10

Database Scaling:
- Vertical: Upgrade instance type during low-traffic window
- Horizontal: Add read replicas for reporting/analytics
- Connection pooling: HikariCP with max 100 connections

Redis Scaling:
- Vertical: Upgrade node type
- Horizontal: Add replica nodes
- Cluster mode: Enable for > 10M keys

Regional Expansion:
- Deploy Check-in Service in multiple AWS regions
- Use Route 53 for geo-routing
- Replicate database across regions (read replicas)
 
 Disaster Recovery 
 RTO (Recovery Time Objective): 1 hour
RPO (Recovery Point Objective): 5 minutes

Recovery Procedures:

1. Database Failure:
 - Automatic failover to replica (< 2 minutes)
 - Promote replica to primary
 - Update application config
 - Restore read replica from backup

2. API Service Failure:
 - Auto Scaling Group spawns new instances
 - Load Balancer routes to healthy instances
 - Failed instances terminated and replaced

3. Redis Failure:
 - Automatic failover to replica
 - Application continues with slight latency
 - Rebuild cache from database if needed

4. Complete Region Failure:
 - Route 53 failover to backup region
 - Promote backup region database to primary
 - Update scanner apps via backend config
 
 
 Monitoring and Analytics 
 Key Metrics 
 Scanner Metrics:
- Total active scanners
- Scanners online vs offline
- Scan rate per scanner
- Sync frequency and success rate
- Average offline duration

Ticket Metrics:
- Total scans per event
- Valid scans vs duplicates vs invalid
- Scan success rate
- Average scan time (online vs offline)
- Peak scan throughput

Performance Metrics:
- API response time (p50, p95, p99)
- Database query performance
- Redis hit/miss ratio
- Error rate by endpoint

Security Metrics:
- Invalid ticket attempts
- Scanner authentication failures
- Duplicate scan alerts
- Suspicious patterns (multiple duplicates)
 
 Alerting 
 Critical Alerts (PagerDuty):
- Database connection pool exhausted
- API error rate > 5%
- Redis cluster down
- Duplicate scan rate > 10%

Warning Alerts (Slack):
- Scanner offline > 30 minutes
- Sync failure rate > 20%
- API response time > 2s (p95)
- Database replication lag > 10s

Info Alerts (Email):
- Daily scan summary
- Weekly duplicate report
- Monthly scanner registration report
 
 
 Appendix 
 Glossary 
 RSA (Rivest-Shamir-Adleman) : Asymmetric encryption algorithm used for digital signatures 
 JWT (JSON Web Token) : Compact, URL-safe means of representing claims between two parties 
 QR Code : Two-dimensional barcode that can be scanned by cameras 
 Scanner Registration : Process of linking a scanner device to the system 
 Offline Mode : Scanner operation without internet connectivity 
 Duplicate Scan : Attempt to scan the same ticket multiple times 
 Sync Queue : Local storage of scans waiting to be sent to server 
 Public Key : Cryptographic key used to verify signatures (safe to distribute) 
 Private Key : Cryptographic key used to create signatures (must remain secret) 
 References 
 
 JWT RFC: https://datatracker.ietf.org/doc/html/rfc7519 
 RSA Cryptography: PKCS #1 v2.2 
 QR Code Standard: ISO/IEC 18004:2015 
 Android Keystore: https://developer.android.com/training/articles/keystore 
 
 Change Log 
 Version 1.0 (2025-11-29) 
 
 Initial architecture document 
 Complete system design 
 All core flows documented 
 
 
 Document Maintained By: Nexgate Platform Team 
 Last Updated: November 29, 2025 
 Next Review: December 29, 2025

NEXGATE EVENT MANAGEMENT PLATFORM
Version: 3.0 
 Last Updated: November 2024 
 Document Owner: Product Team 
 
 1. PLATFORM OVERVIEW 
 What is Nexgate Events? 
 A social commerce-integrated event management platform where events and products live together in one ecosystem. 
 The Problem We Solve 
 For Hosts: 
 
 Slow payouts (5-7 days) 
 High fees (8-15%) 
 Events disconnected from products 
 Complex ticket management 
 Limited check-in devices 
 
 For Attendees: 
 
 Events discovered through spam 
 Tickets scattered everywhere 
 Can't see which friends are going 
 No product connection 
 
 The Nexgate Solution 
 For Hosts: 
 
 ✅ 48-hour payout (fastest in industry) 
 ✅ 10% platform fee (transparent) 
 ✅ Link products to events (unlimited) 
 ✅ Simple per-tier pricing 
 ✅ Unlimited scanner devices 
 ✅ Coupon code system 
 ✅ Social feed discovery 
 
 For Attendees: 
 
 ✅ Discover through friends 
 ✅ One QR per ticket 
 ✅ Shop event products 
 ✅ Hybrid options (physical/online) 
 ✅ Mix free & paid tickets 
 ✅ Use coupon codes 
 
 
 2. CORE PHILOSOPHY 
 The 7 Unbreakable Laws 
 1. One Ticket = One Unique QR Code 
 Every ticket has its own cryptographically signed QR code. Prevents fraud, perfect tracking, works offline. 
 2. 48-Hour Payout Rule 
 Host receives money 48 hours after event ends. Period. 
 3. Social Commerce First 
 Events ARE products. They live in the same feed. Events drive product sales, products drive event attendance. 
 4. Hybrid Events, Hybrid Pricing 
 Physical ≠ Online. Different experiences = different prices. 
 5. Per-Tier Pricing Freedom 
 Each tier decides: FREE or PAID. No global toggle. Mix freely. 
 6. Device Linking for Scanners 
 No accounts needed. Generate link → Scan with any device → Revoke anytime. 
 7. Gallery is Required 
 Minimum 3 photos. Events with photos get 3x more bookings. 
 
 3. USER ROLES 
 Host (Event Creator) 
 What they can do: 
 
 Create/edit/delete events 
 Set ticket pricing & tiers 
 Link unlimited products 
 Generate scanner links 
 Create coupon codes 
 View analytics 
 Receive payouts 
 Respond to reviews 
 
 Attendee 
 What they can do: 
 
 Browse/search events 
 Purchase tickets (with coupons) 
 Transfer tickets 
 Check-in with QR 
 Leave reviews 
 Shop event products 
 Share events 
 
 Scanner Device (No Account) 
 What it is: 
 
 Any device with scanner link 
 No Nexgate account needed 
 
 What it can do: 
 
 Scan QR codes (specific event only) 
 Show check-in confirmation 
 Display real-time count 
 Work offline 
 
 
 4. EVENT TYPES 
 One-Time Event 
 Single date, fixed capacity. 
 Use cases: Product launches, pop-ups, workshops, concerts 
 Example: 
 Fashion Launch Party
March 15, 2025, 7:00 PM
Wynwood Gallery, Miami
$25 General, $50 VIP
100 capacity
 
 Recurring Series 
 Repeats weekly/monthly. 
 Use cases: Yoga classes, networking meetups, bootcamps 
 Configuration: 
 
 Repeat: Weekly/Bi-weekly/Monthly 
 Days: Mon, Wed, Fri 
 End: Never/After X times/End date 
 
 Example: 
 Sunday Morning Yoga
Every Sunday, 8:00 AM
$25 per class
30 capacity per class
 
 How Recurring Payments Work 
 Pay-Per-Class Model: 
 Each class is a separate purchase with its own ticket. 
 USER BUYS TICKETS:

March 16 class → $25 → QR Code #1
March 23 class → $25 → QR Code #2 
March 30 class → $25 → QR Code #3

Each ticket = unique QR code
Each purchase = separate transaction
Each class = separate payout 48h after
 
 Why different QR codes? 
 
 Clear separation (each class is distinct) 
 Works offline (no database lookup needed) 
 Prevents fraud (can't reuse old ticket) 
 Easy refunds (refund specific class) 
 Simple transfers (transfer one class) 
 Better tracking (attendance per class) 
 
 User Experience: 
 Tickets grouped in wallet by series: 
 ┌─────────────────────────────────────┐
│ SUNDAY MORNING YOGA SERIES │
├─────────────────────────────────────┤
│ Upcoming classes you purchased: │
│ │
│ ✓ March 16 · 8:00 AM │
│ [View QR Code] │
│ │
│ ○ March 23 · 8:00 AM │
│ [View QR Code] │
│ │
│ ○ March 30 · 8:00 AM │
│ [View QR Code] │
└─────────────────────────────────────┘

Smart features:
- Grouped by series name
- Highlights today's class
- Reminders show correct QR
- "Class 1 of 3 you purchased"
 
 Host Dashboard: 
 SUNDAY MORNING YOGA (Recurring)

Next 6 classes:
━━━━━━━━━━━━━━━━━━
March 16 · $25 · 12/30 sold
March 23 · $25 · 18/30 sold
March 30 · $25 · 20/30 sold
April 6 · $25 · 25/30 sold
April 13 · $25 · 30/30 sold
April 20 · $25 · 30/30 sold

Revenue per class
Payout 48h after each class
 
 Future Enhancement (Phase 2): 
 
 Class packs: 10-class pack for $200 
 Monthly unlimited: $99/month subscription 
 Same QR for packs (use anytime) 
 
 Multi-Day Event 
 Multiple consecutive days, same ticket. 
 Use cases: Festivals, retreats, conferences 
 Features: 
 
 Same QR works all days 
 Track attendance per day 
 Allow re-entry each day 
 
 Example: 
 Wellness Retreat
March 15-17, 2025 (3 days)
Bali Resort
$450 (all 3 days)
50 capacity
 
 Location Types 
 In-Person Only 
 
 Google Maps address (always visible) 
 Parking info (optional) 
 Venue capacity 
 
 Online Only 
 
 Zoom/Google Meet/Custom 
 Link revealed after purchase 
 Unlimited or limited capacity 
 
 Hybrid (Physical + Online) 
 
 Separate pricing for each 
 Different capacities 
 Different benefits 
 Independent tracking 
 
 
 5. TICKET SYSTEM 
 Per-Tier Pricing (Core Concept) 
 Each tier independently decides: FREE or PAID 
 No global toggle. Maximum flexibility. 
 Creating Ticket Tiers 
 Every tier has: 
 
 Name - "VIP", "General", "Economy" 
 Pricing - ○ FREE or ○ PAID $[__] 
 Capacity - How many available 
 Description - What's included 
 
 Example: In-Person Event 
 HOST CREATES:

TIER 1: Economy
- Pricing: ● FREE
- Capacity: 30
- Description: Standing room, general entry

TIER 2: Regular
- Pricing: ● PAID $35
- Capacity: 20
- Description: Reserved seat, drink, gift bag

TIER 3: VIP
- Pricing: ● PAID $75
- Capacity: 10
- Description: Front row, meet & greet, merch

SYSTEM DISPLAYS:
"FREE & PAID OPTIONS"
"From FREE to $75"
Total: 60 tickets
 
 Example: Hybrid Event 
 PHYSICAL TICKETS:

TIER 1: Standard In-Person
- ● PAID $40 · 25 capacity
- Entry, seating, refreshments

TIER 2: VIP In-Person
- ● PAID $85 · 10 capacity
- Front row, meet & greet

ONLINE TICKETS:

TIER 1: Basic Virtual
- ● FREE · Unlimited
- Livestream only

TIER 2: Premium Virtual
- ● PAID $20 · 100 capacity
- HD stream, recording, Q&A
 
 Ticket Purchase Flow 
 Single Tier Purchase 
 1. Select tier: "VIP ($75)"
2. Quantity: 2
3. Enter names for each
4. Apply coupon (optional)
5. Payment: $150
6. 2 unique QR codes sent
 
 Mixed Tier Purchase 
 1. Select "Economy (FREE)" × 2
2. Add "VIP ($75)" × 1
3. Enter 3 names
4. Apply coupon: "FRIEND20" (-$15)
5. Payment: $60
6. 3 unique QR codes sent
 
 QR Code Structure 
 Each QR contains: 
 
 Unique ticket ID 
 Event ID 
 User ID 
 Tier ID 
 Cryptographic signature 
 Cannot be forged 
 Works offline 
 
 
 6. HYBRID EVENTS & PRICING 
 Why Hybrid Matters 
 Physical ≠ Online = Different pricing 
 Host Setup 
 STEP 1: Choose ● Hybrid

STEP 2: Physical Tiers
Economy: FREE, 20 capacity
Premium: $50, 15 capacity

STEP 3: Online Tiers
Basic: FREE, Unlimited
Premium: $20, 50 capacity
 
 Attendee Selection 
 STEP 1: Choose Type
● In-Person ○ Virtual

STEP 2: Choose Tier
[Based on selection]

Physical:
💚 Economy - FREE
⭐ Premium - $50

Virtual:
💻 Basic - FREE
💻 Premium - $20
 
 
 7. SCANNER SYSTEM (DEVICE LINKING) 
 How It Works 
 Generate link → Open on ANY device → Scan 
 No app. No account. Any device. 
 Creating Scanner Links 
 EVENT DASHBOARD → [+ Add Scanner]

Device name: "Front Desk iPad"
Expires: ● After event ○ Custom

[Generate Link]

→ Link: nexgate.app/scan/abc123xyz
→ QR code to share
→ Valid for this event only
 
 Using Scanner 
 On ANY device: 
 
 Open link in browser 
 Camera permission 
 Point at ticket QR 
 Instant validation 
 
 Scanner Interface: 
 ┌─────────────────────┐
│ NEXGATE SCANNER │
│ Beach Yoga - Mar 16 │
├─────────────────────┤
│ [CAMERA VIEW] │
│ Point at QR code │
├─────────────────────┤
│ 28/60 checked in │
└─────────────────────┘
 
 Success: 
 ✓ Emma Wilson
Ticket: #2847
Tier: VIP ($75)
First-time! 🎉
 
 Features 
 Works Online: 
 
 Real-time validation 
 Live updates 
 Full details 
 
 Works Offline: 
 
 Cryptographic validation 
 Local storage 
 Syncs when online 
 
 Security: 
 
 Auto-expires 
 Revoke anytime 
 Event-specific 
 Activity tracking 
 
 
 8. PRODUCT INTEGRATION (SIMPLIFIED) 
 Link Products to Events 
 Attach products from your shop or any shop on Nexgate. Unlimited products. 
 Adding Products 
 EVENT DASHBOARD → Products Tab

[+ Add Product from My Shop]
[+ Add by Product Link]

OPTION 1: From Your Shop
→ Browse your products
→ Select any/all
→ [Add Selected]

OPTION 2: By Link
→ Paste: nexgate.com/products/yoga-mat
→ System fetches details
→ [Add Product]
 
 Managing Products 
 ATTACHED PRODUCTS (12):

📸 Yoga Mat - $65
 From: @yogagear
 [Remove]

📸 Water Bottle - $15
 From: Your Shop
 [Remove]

[+ Add More Products]

Can add/remove anytime:
✓ Before event
✓ During event
✓ After event
 
 Display to Attendees 
 On Event Page: 
 EVENT MARKETPLACE

📸 Yoga Mat - $65
 by @yogagear
 [View Product]

📸 Water Bottle - $15
 by @sunstudio
 [View Product]

📸 Workshop Recording - $10
 by @sunstudio
 [View Product]

+ View All Products (9 more)

[Browse Event Marketplace →]
 
 Shows first 3-5 products, then "View All" 
 Event Marketplace Page: 
Full grid of all attached products (unlimited) 
 That's It! 
 Simple rules: 
 
 ✅ Attach unlimited products 
 ✅ Yours or others' shops 
 ✅ Add/remove anytime 
 ✅ No timing restrictions 
 ✅ Display on event page 
 ✅ Users shop when they want 
 
 
 9. COUPON SYSTEM (NEW!) 
 Generate Discount Codes 
 Create coupon codes for ticket discounts. 
 Creating Coupons 
 EVENT DASHBOARD → Coupons Tab

[+ Create New Coupon]

┌─────────────────────────────────────┐
│ CREATE COUPON │
├─────────────────────────────────────┤
│ Coupon code: │
│ [EARLYBIRD2025] │
│ │
│ Discount type: │
│ ● Percentage: [20]% │
│ ○ Fixed amount: $[__] │
│ │
│ Applies to: │
│ ☑️ All ticket tiers │
│ ☐ Specific tiers only │
│ │
│ Usage limit: │
│ ● Unlimited uses │
│ ○ Limited: [100] total uses │
│ ○ One use per customer │
│ │
│ Valid period: │
│ Start: [March 1, 2025] │
│ End: [March 10, 2025] │
│ ○ No expiration │
│ │
│ [Create Coupon] │
└─────────────────────────────────────┘
 
 Managing Coupons 
 ACTIVE COUPONS (3):

┌─────────────────────────────────────┐
│ EARLYBIRD2025 │
│ 20% off all tickets │
│ Used: 23 times │
│ Valid until: Mar 10 │
│ [Copy Code] [Edit] [Disable] │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ FRIEND50 │
│ $5 off all tickets │
│ Used: 7/50 times │
│ No expiration │
│ [Copy Code] [Edit] [Disable] │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ VIP100 │
│ $10 off VIP tier only │
│ Used: 2/10 times │
│ Valid until: Mar 15 │
│ [Copy Code] [Edit] [Disable] │
└─────────────────────────────────────┘
 
 Attendee Using Coupon 
 CHECKOUT

Order Summary:
VIP Ticket × 1 $75.00

Have a coupon code?
[EARLYBIRD2025 ] [Apply]

✓ Coupon applied: EARLYBIRD2025
Discount (20%): -$15.00
────────────────────────────────
Total: $60.00

[Complete Purchase - $60]
 
 Sharing Coupons 
 EARLYBIRD2025

[Copy Code] → "Copied!"

Share via:
[📧 Email] [📱 SMS] [📋 Copy Link]

Direct link with coupon:
nexgate.com/events/beach-yoga?coupon=EARLYBIRD2025
 
 Coupon Analytics 
 COUPON PERFORMANCE

EARLYBIRD2025:
- Uses: 23
- Discount given: $345
- Revenue generated: $1,380
- Conversion boost: +15%

Top performers:
1. EARLYBIRD2025 (23 uses)
2. FRIEND50 (7 uses)
3. VIP100 (2 uses)
 
 
 10. REVIEW SYSTEM 
 Collection Timeline 
 2 hours after event: 
"Quick rating ⭐" → Tap 5 stars 
 24 hours later: 
Full review request + incentives 
 1 week later: 
Final reminder 
 Review Form 
 RATE BEACH YOGA

Overall: ⭐⭐⭐⭐⭐ (required)

Quick ratings (optional):
Venue: ⭐⭐⭐⭐⭐
Instructor: ⭐⭐⭐⭐⭐
Organization: ⭐⭐⭐⭐☆

Tell us more: (optional)
[Amazing sunrise session...]

Add photos: [+] [+] [+]

Privacy:
● Public ○ Anonymous ○ Private

EARN:
✓ 50 points ($5 credit)
✓ 10% off next event
+ Photos: 100 bonus points

[Submit Review]
 
 Display 
 ⭐ 4.8 out of 5 (127 reviews)

BREAKDOWN:
⭐⭐⭐⭐⭐ ████████ 89%
⭐⭐⭐⭐☆ ███ 8%

MOST MENTIONED:
🌅 Great vibes (42)
🧘 Amazing instructor (38)

REVIEWS:

Emma Wilson ⭐⭐⭐⭐⭐
"Magical sunrise session..."
📸📸📸
👍 12 helpful

↳ Host: "Thank you Emma! 🙏"
 
 
 11. PAYMENT & PAYOUT 
 When Attendee Buys 
 1. Select ticket
2. Apply coupon (optional)
3. Enter payment
4. Charge immediately
5. Funds → Escrow
6. Confirmation sent
 
 Escrow System 
 Purchase → Escrow → Event Completes → Wait 48h → Payout

Timeline:
Mar 1: Ticket bought ($75) → Escrow
Mar 16: Event ends (7pm)
Mar 18: Payout (7pm, 48h later)
 
 The 48-Hour Rule 
 Event End Time + 48 Hours = Payout 
 Payout Calculation 
 SIMPLE:
Gross: $1,000
Platform fee (10%): -$100
Net: $900

MIXED FREE + PAID:
Free: 40 tickets (no revenue)
Paid: 30 × $25 = $750
Fee (on paid only): -$75
Net: $675

WITH COUPONS:
Gross: $1,500
Coupons used: -$200
Platform fee (10% of $1,300): -$130
Net: $1,170
 
 Payout Dashboard 
 UPCOMING:
Beach Yoga - Mar 16
Payout: Mar 18, 7pm
Amount: $945
Status: ⏰ 32h left

COMPLETED:
Sunset Yoga - Mar 9
Paid: Mar 11, 6:30pm
Amount: $612.50
Status: ✓ Deposited
 
 Refunds 
 Host-initiated: 
 
 Full refund to attendee 
 Platform fee non-refundable 
 Host absorbs fee cost 
 
 Automatic (event cancelled): 
 
 All attendees refunded 
 Full amount + platform fee 
 Nexgate absorbs cost 
 Within 24 hours 
 
 
 12. NOTIFICATION SYSTEM 
 Philosophy 
 Simple, smart, not annoying. 
 Only send notifications that matter. Automatic reminders based on event timing. 
 Attendance Confirmation (Optional) 
 Host can enable: 
 EVENT SETTINGS → Attendee Management

☑️ Request attendance confirmation
Send [3] days before event

Attendees confirm they're coming
Host sees who confirmed vs pending
 
 Attendee receives: 
 ┌─────────────────────────────────────┐
│ CONFIRM YOUR ATTENDANCE │
├─────────────────────────────────────┤
│ Beach Yoga - March 16, 5pm │
│ │
│ Are you still coming? │
│ │
│ [✓ Yes, I'm Coming] │
│ [✗ Can't Make It] │
└─────────────────────────────────────┘
 
 Host Dashboard: 
 ATTENDANCE TRACKER

Total tickets: 30

✓ Confirmed: 22 people
⏳ Pending: 5 (no response)
✗ Can't attend: 3 people

Expected: ~73% attendance

[Send Reminder to Pending]
 
 Benefits: 
 
 Host plans better (food, materials, space) 
 Reduces no-shows 
 Professional event management 
 
 Reminder Schedule (Automatic) 
 Smart formula based on event timing: 
 IF event is 2-7 DAYS AWAY:
→ 1 day before
→ 1 hour before

IF event is 8-30 DAYS AWAY:
→ 1 week before
→ 1 day before
→ 1 hour before

IF event is 30+ DAYS AWAY:
→ 2 weeks before
→ 1 week before
→ 1 day before
→ 1 hour before

IF event is SAME DAY:
→ 1 hour before only
 
 Rules: 
 
 Always: 1 hour before (critical) 
 If time allows: 1 day before 
 If far out: 1 week before 
 If very far: 2 weeks before 
 Maximum: 4 reminders total 
 Minimum: 1 reminder (1h before) 
 
 Adaptive Reminders 
 System adjusts based on purchase timing: 
 Event: March 30 (30 days away)

User A buys March 1:
→ 2 weeks, 1 week, 1 day, 1h reminders

User B buys March 28 (2 days before):
→ 1 day, 1h reminders only

User C buys March 30 (same day):
→ 1h reminder only

Each user gets only relevant reminders
 
 Notification Timeline 
 Before Event: 
 [X days before] (if host enabled)
"Confirm your attendance"
[Yes, I'm Coming] [Can't Make It]

[Auto-calculated]
"Beach Yoga is next week!"

[1 day before]
🔔 Beach Yoga tomorrow at 5pm!
📍 1234 Ocean Drive, Miami Beach
🚗 Street parking ($2/hr)
[View Ticket] [Get Directions]

[1 hour before]
🔔 Beach Yoga starts in 1 hour!
⏰ 5:00 PM - Don't be late!
[View Your Ticket QR]
 
 After Event: 
 [2 hours after]
"How was Beach Yoga? ⭐"
Tap to rate: ⭐⭐⭐⭐⭐
Earn 50 points!

[24 hours after]
"Leave a review (earn 50 points)"
[Write Review]
 
 Total Notifications Per Attendee 
 Minimum (same-day event): 
 
 1h before reminder 
 Rate request (2h after)
Total: 2 notifications 
 
 Typical (1-week advance): 
 
 1 day before reminder 
 1h before reminder 
 Rate request (2h after)
Total: 3 notifications 
 
 Maximum (1+ month advance): 
 
 Confirmation request (if host enabled) 
 2 weeks before 
 1 week before 
 1 day before 
 1h before 
 Rate request (2h after)
Total: 6 notifications 
 
 Host Notifications 
 Sales Activity: 
 "New ticket sale! 🎉"
Sarah Chen · Premium ($40)
Total: 23/30 tickets

[View Dashboard]
 
 Sold Out: 
 "Beach Yoga is SOLD OUT! 🔥"
30/30 tickets · $1,200 gross

[View Analytics]
 
 New Review: 
 "New 5-star review! ⭐"
Emma: "Amazing sunrise session..."

[View Review] [Respond]
 
 Payout Ready: 
 "💰 Your payout is ready!"
$945 from Beach Yoga

[View Details]
 
 Host Controls 
 EVENT SETTINGS → Notifications

ATTENDANCE CONFIRMATION:
○ Disabled
● Enabled: Send [3] days before

REMINDER SCHEDULE:
● Automatic (smart formula) ← Default
○ Custom schedule

POST-EVENT:
☑️ Request rating (2h after)
☑️ Request review (24h after)

[Save Settings]
 
 User Notification Settings 
 NOTIFICATION PREFERENCES

Event Reminders:
☑️ Smart reminders (recommended)
☐ Disable all except 1h before

Review Requests:
☑️ After attending events
☐ Disable review requests

Friend Activity:
☑️ When friends create events
☑️ When friends buy tickets

Cannot disable:
- 1h before event (critical)

[Save Preferences]
 
 Anti-Spam Rules 
 Frequency Caps: 
 
 Max 4 reminders per event 
 No more than 2 notifications per day 
 24h+ between marketing notifications 
 
 Smart Batching: 
If multiple events same day: 
 "You have 3 events tomorrow 🗓️"
- Beach Yoga · 5pm
- Pottery Class · 7pm
- Food Festival · 8pm

[View All Tickets]
 
 
 13. USER JOURNEYS 
 Journey 1: Host Creates Event 
 1. Emma logs in
2. [Create Event]
3. Upload 3 photos
4. Title: "Beach Yoga"
5. Category: Fitness
6. Location: ● Hybrid
7. Physical: FREE (20) + $40 (15)
8. Online: FREE (∞) + $15 (50)
9. Link products: Yoga mat, recording
10. Create coupon: "EARLY20" (20% off)
11. [Publish]
12. Share on Instagram

Time: 12 minutes
 
 EVENT DAY:
- Generate 2 scanner links
- Send to iPad + volunteer
- 28 physical checked in
- 119 online joined
- Event ends successfully
 
 48 HOURS LATER:
- Payout: $945 deposited
- 24 reviews (4.9 stars)
- 18 product purchases
 
 Journey 2: Attendee Discovers & Attends 
 Sarah scrolling feed
→ "Beach Yoga - Emma +8 friends going"
→ Taps event

Event page:
→ Beautiful photos
→ 4.9 stars
→ Virtual Premium: $15

Checkout:
→ Apply coupon: "EARLY20"
→ $15 → $12
→ Apple Pay
→ Done!

Event day:
→ Zoom link opens
→ 130 people online
→ 90-min session
→ Downloads recording

2 hours later:
→ "Rate ⭐"
→ 5 stars
→ Earns 150 points
→ Buys recording: $10
 
 
 14. TECHNICAL REQUIREMENTS 
 Performance 
 
 Event page: < 2s 
 Search: < 1s 
 Scanner: < 500ms 
 Payment: < 3s 
 
 Scalability 
 
 10k concurrent users 
 1k simultaneous purchases 
 500 simultaneous scans 
 
 Availability 
 
 99.9% uptime 
 Zero-downtime deploys 
 < 4h recovery 
 
 Security 
 
 OAuth login 
 JWT tokens 
 AES-256 encryption 
 TLS 1.3 
 PCI DSS compliant 
 GDPR compliant 
 SHA-256 QR signing 
 
 Mobile 
 
 Mobile-first design 
 Works on 3G/4G 
 Offline scanner 
 PWA support 
 iOS 14+, Android 8+ 
 
 
 15. BUSINESS RULES 
 Capacity 
 
 Cannot oversell 
 Atomic transactions 
 Per-tier tracking 
 Sold-out states 
 
 Ticket Transfers 
 Allowed: 
 
 Before event starts 
 Unlimited transfers 
 New QR for recipient 
 
 Not allowed: 
 
 During/after event 
 
 Check-In Rules 
 Standard: One scan
 Multi-day: Same QR all days
 Recurring: Track class usage 
 Coupon Rules 
 Validation checks: 
 
 Code exists 
 Not expired 
 Usage limit not reached 
 Applies to selected tickets 
 Can be combined? (host setting) 
 
 Refund Policies 
 Host options: 
 
 Full refund anytime 
 Full until X days before 
 Partial after deadline 
 No refunds 
 
 
 16. SUCCESS METRICS 
 Platform Growth (Year 1) 
 
 Month 1: 50 events, 500 tickets 
 Month 6: 500 events, 10k tickets 
 Month 12: 2k events, 50k tickets 
 
 Key Metrics 
 Engagement: 
 
 View → purchase: 15%+ 
 Repeat attendees: 40%+ 
 Review rate: 40%+ 
 Social share: 20%+ 
 Coupon usage: 25%+ 
 
 Revenue: 
 
 GMV growth 
 Platform revenue (10%) 
 Avg ticket price 
 Product revenue: +30% 
 
 Quality: 
 
 Event rating: 4.5+ 
 Host retention: 60%+ 
 Scanner success: 99%+ 
 Uptime: 99.9%+ 
 
 
 17. IMPLEMENTATION ROADMAP 
 Phase 1: MVP (Months 1-3) 
 Build: 
 
 Event creation (all types) 
 Hybrid pricing 
 Per-tier pricing (free+paid) 
 Multiple ticket purchase 
 Unique QR per ticket 
 Gallery (3 photos min) 
 Device linking scanner 
 Product attachment (unlimited) 
 Coupon system 
 48-hour payout 
 Basic reviews 
 Feed integration 
 
 Success: 
 
 50 events 
 500 tickets 
 4.0+ rating 
 
 Phase 2: Enhanced (Months 4-6) 
 Add: 
 
 Advanced analytics 
 Ticket transfer 
 Review filters 
 Host responses 
 Attendee messaging 
 Advanced coupons (tiered, bundled) 
 Product marketplace analytics 
 
 Success: 
 
 200 events/month 
 30% hybrid adoption 
 50% review rate 
 
 Phase 3: Scale (Months 7-12) 
 Add: 
 
 Class packs 
 Monthly passes 
 Multi-language 
 International currency 
 API access 
 White-label 
 
 Success: 
 
 1,000+ events/month 
 50,000+ tickets/month 
 Profitable 
 
 
 CONCLUSION 
 What Makes Nexgate Different 
 The Formula: 
 Social Commerce + Events = Network Effects
 
 The Flywheel: 
 Host creates event
→ Links products
→ Creates coupons
→ Shares to feed
→ Friends discover
→ Use coupons
→ Buy tickets
→ Shop products
→ Leave reviews
→ More social proof
→ REPEAT
 
 Key Advantages 
 
 48-hour payout - Fastest in industry 
 Unlimited products - Any shop on platform 
 Coupon system - Built-in marketing tool 
 Device linking - Unlimited scanners 
 Per-tier pricing - Mix free + paid freely 
 Hybrid events - Physical + online pricing 
 Social discovery - Friends drive attendance 
 
 Next Steps 
 
 Build MVP (3 months) 
 Beta with 50 hosts 
 Iterate & improve 
 Scale to 1,000+ hosts 
 Dominate social commerce events 
 
 
 END OF DOCUMENT 
 Version: 3.0 - Simplified & Focused 
 Last Updated: November 2024

Event Management System - Requirements & User Flow Documentation
Table of Contents 
 
 Overview 
 Event Types 
 Core Features 
 Event Creation Flows 
 Ticket Management 
 Registration & Attendance 
 User Roles & Permissions 
 Business Rules 
 Data Relationships 
 
 
 Overview 
 The Event Management System supports three distinct event lifecycle types with flexible ticketing, multiple location options, and comprehensive access control. The system is designed to handle everything from simple one-time events to complex recurring series with variable pricing. 
 Key Capabilities 
 
 Multiple event types (One-time, Multi-day, Recurring) 
 Flexible ticket types with varied pricing 
 Multiple location modes (In-person, Online, Hybrid) 
 Access control (Public, Private, Unlisted) 
 Integration with online meeting providers 
 Comprehensive registration and attendance tracking 
 
 
 Event Types 
 1. One-Time Event 
 A single event occurring once at a specific date and time. 
 Required Information: 
 
 Start date & time 
 End date & time 
 Location details 
 Ticket types (optional) 
 
 Example: 
 Event: Tech Conference 2025
Date: March 15, 2025
Time: 9:00 AM - 5:00 PM
Location: Convention Center
 
 
 2. Multi-Day Event 
 A single event spanning multiple consecutive or non-consecutive days. 
 Required Information: 
 
 Start date 
 End date 
 Schedule per day (can vary) 
 Location details (can vary per day) 
 Single ticket for all days 
 
 Characteristics: 
 
 ONE event, multiple days 
 ONE ticket purchase covers ALL days 
 Each day can have different start/end times 
 Each day can have different locations 
 User registers once, attends multiple days 
 
 Example: 
 Event: Music Festival 2025
Dates: June 10-12, 2025

Day 1 (June 10): 2:00 PM - 11:00 PM - Main Stage
Day 2 (June 11): 12:00 PM - 11:00 PM - Multiple Stages 
Day 3 (June 12): 1:00 PM - 10:00 PM - Main Stage

Ticket: $200 (access all 3 days)
 
 
 3. Recurring Event 
 A series of separate events happening on a repeating schedule. 
 Required Information: 
 
 Series name & description 
 Recurrence pattern:
 
 Frequency: Daily, Weekly, Monthly, Yearly 
 Interval: Every 1 week, every 2 weeks, etc. 
 Days of week (for weekly) 
 Day of month (for monthly) 
 
 
 Date range: Start date → End date OR number of occurrences 
 Default schedule for sessions 
 Default ticket types 
 
 Characteristics: 
 
 MULTIPLE separate events (series) 
 Each session = independent event 
 Users buy tickets per session 
 Must buy at least ONE session to register for series 
 Can buy multiple sessions at once or separately 
 Each session can have own ticket types and pricing 
 
 Example: 
 Series: Monday Yoga Classes
Pattern: Every Monday
Time: 9:00 AM - 10:00 AM
Duration: January - March 2025 (12 sessions)

Individual Sessions:
- Session 1: Jan 6, 2025, 9-10 AM
- Session 2: Jan 13, 2025, 9-10 AM
- Session 3: Jan 20, 2025, 9-10 AM
... (12 total)

Each session has tickets: Regular ($15), Student ($10), Member ($8)
 
 Recurrence Patterns Supported: 
 
 Daily: Every day, every 2 days, weekdays only, etc. 
 Weekly: Every Monday, Mon/Wed/Fri, every 2 weeks on Tuesday, etc. 
 Monthly: First Monday, 15th of each month, last Friday, etc. 
 Yearly: Same date each year 
 
 
 Core Features 
 Event Visibility & Access Control 
 ┌──────────────────────────────────────────────────────────────┐
│ VISIBILITY OPTIONS │
├──────────────────────────────────────────────────────────────┤
│ │
│ ○ Public │
│ • Discoverable in search │
│ • Anyone can view and register │
│ • Appears in event listings │
│ │
│ ○ Private │
│ • Not discoverable │
│ • Invitation-only access │
│ • Organizer sends invitations │
│ • Invitees must be explicitly added │
│ │
│ ○ Unlisted │
│ • Not discoverable in search │
│ • Anyone with link can access │
│ • No invitation required │
│ • Link-based sharing │
│ │
└──────────────────────────────────────────────────────────────┘
 
 Public Events 
 
 Visible to everyone 
 Appear in search results and listings 
 Anyone can register/buy tickets 
 No access restrictions 
 
 Private Events 
 
 Not visible in search 
 Access by invitation only 
 Organizer manages invitations 
 
 Invitation System: 
 
 Organizer invites by email or username 
 Invitations have optional expiration dates 
 Invitation statuses: Pending, Accepted, Declined, Expired 
 Invited users get notifications 
 Can generate shareable invite links with tokens 
 
 Invitation Expiration Rules: 
 
 Automatically expires when event ends 
 Optional custom expiration date 
 Token-based links can be regenerated (invalidates old tokens) 
 
 Unlisted Events 
 
 Not discoverable in search 
 Anyone with the link can access 
 No explicit invitation needed 
 Access through direct URL only 
 
 Access Mechanism: 
 
 Simple approach: Direct URL with event ID 
 Secure approach: URL includes access token 
 Organizer can regenerate link (invalidates previous link) 
 No expiration on the link itself (valid until event ends) 
 
 
 Location Types 
 ┌──────────────────────────────────────────────────────────────┐
│ LOCATION TYPE │
├──────────────────────────────────────────────────────────────┤
│ │
│ ○ In-Person Only │
│ Required: Physical address, venue name │
│ Optional: Coordinates (lat/long), directions │
│ │
│ ○ Online Only │
│ Required: Meeting platform, link/credentials │
│ Options: Manual link OR provider integration │
│ │
│ ○ Hybrid (Both) │
│ Required: ALL in-person + online details │
│ Attendees choose: in-person OR online │
│ │
└──────────────────────────────────────────────────────────────┘
 
 In-Person Events 
 Required Fields: 
 
 Venue name 
 Full address 
 City, State, Country 
 
 Optional Fields: 
 
 Latitude/Longitude (for maps) 
 Venue description 
 Parking information 
 Accessibility details 
 
 Online Events 
 Meeting Provider Options: 
 Option 1: Manual Entry 
 
 Organizer provides meeting link directly 
 Platform-agnostic (Zoom, Google Meet, Teams, Discord, etc.) 
 Organizer manages meeting creation 
 Meeting details stored:
 
 Meeting URL 
 Meeting ID (optional) 
 Password/Access code (optional) 
 
 
 
 Security for Manual Links: 
 
 Link hidden until 30 minutes before event 
 User must be logged in 
 User must have valid ticket 
 Link visible until 1 hour after event ends 
 
 Option 2: Provider Integration (Zoom) 
 
 OAuth connection to organizer's account 
 System creates meeting automatically 
 Enhanced security features available 
 
 Zoom Integration Flow: 
 ┌─────────────────────────────────────────────────────────────┐
│ Event Creation: Online Event → Select "Connect with Zoom" │
└──────────────────┬──────────────────────────────────────────┘
 ↓
 ┌─────────────────────┐
 │ Has Zoom connected? │
 └─────────┬───────────┘
 ↓
 ┌───────┴────────┐
 │ │
 NO YES
 │ │
 ↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ Redirect to Zoom │ │ Create meeting │
│ OAuth page │ │ immediately via │
│ │ │ API │
└────────┬─────────┘ └─────────┬────────┘
 │ │
 ↓ │
┌──────────────────┐ │
│ User authorizes │ │
│ on Zoom │ │
└────────┬─────────┘ │
 │ │
 ↓ │
┌──────────────────┐ │
│ Save tokens to │ │
│ user account │ │
└────────┬─────────┘ │
 │ │
 ↓ │
┌──────────────────┐ │
│ Create meeting │←──────────┘
│ via Zoom API │
└────────┬─────────┘
 │
 ↓
┌──────────────────────────────┐
│ Each paid attendee gets │
│ UNIQUE registration link │
│ (cannot be shared) │
└──────────────────────────────┘
 
 Zoom Integration Benefits: 
 
 Each registered attendee gets unique join URL 
 URLs tied to email (not shareable) 
 Waiting room can be enabled 
 Attendance tracking automatic 
 Registration approval control 
 
 Zoom Account Responsibility: 
 
 Organizer uses their own Zoom account 
 Organizer pays for Zoom subscription 
 Platform creates meetings using organizer's credentials 
 No cost to platform 
 
 Hybrid Events 
 Requirements: 
 
 ALL in-person details (venue, address, etc.) 
 ALL online details (meeting link/platform) 
 
 Attendee Selection: 
 
 During registration, attendee chooses: "Attending in-person" OR "Attending online" 
 Helps organizer plan:
 
 Seating/catering for in-person 
 Meeting capacity for online 
 
 
 Attendee can change preference before event 
 
 
 Event Creation Flows 
 Flow 1: One-Time Event Creation 
 ┌──────────────────────────────────────────────────────────────┐
│ CREATE ONE-TIME EVENT │
└──────────────────────┬───────────────────────────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 1: Basic Information │
 │ • Event name │
 │ • Description │
 │ • Category │
 │ • Cover image │
 │ • Visibility (Public/Private│
 │ /Unlisted) │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 2: Date & Time │
 │ • Start date & time │
 │ • End date & time │
 │ • Timezone │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 3: Location Type │
 │ Select: │
 │ • In-person │
 │ • Online │
 │ • Hybrid │
 └──────────┬──────────────────┘
 ↓
 ┌───────────────┴────────────────┐
 │ │
 ↓ ↓
┌────────────────┐ ┌──────────────────────┐
│ If In-Person: │ │ If Online: │
│ • Venue │ │ • Choose provider │
│ • Address │ │ - Manual link │
│ • Coordinates │ │ - Zoom integration │
└───────┬────────┘ │ • Meeting details │
 │ └──────────┬───────────┘
 │ │
 └────────┬─────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 4: Tickets │
 │ [Add Ticket Type] button │
 │ │
 │ For each ticket type: │
 │ • Name │
 │ • Price │
 │ • Quantity │
 │ • Description │
 │ • Sales start/end dates │
 │ │
 │ [Add Another Ticket Type] │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 5: Review & Publish │
 │ • Preview event details │
 │ • Save as draft OR │
 │ • Publish immediately │
 └─────────────────────────────┘
 
 
 Flow 2: Multi-Day Event Creation 
 ┌──────────────────────────────────────────────────────────────┐
│ CREATE MULTI-DAY EVENT │
└──────────────────────┬───────────────────────────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Steps 1-3: Same as one-time │
 │ (Basic info, Location type) │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 4: Multi-Day Schedule │
 │ • Overall start date │
 │ • Overall end date │
 │ │
 │ Configure each day: │
 │ │
 │ Day 1: │
 │ • Date: March 10, 2025 │
 │ • Start time: 9:00 AM │
 │ • End time: 6:00 PM │
 │ • Location (can differ) │
 │ • Description │
 │ │
 │ Day 2: │
 │ • Date: March 11, 2025 │
 │ • Start time: 10:00 AM │
 │ • End time: 8:00 PM │
 │ • Location (can differ) │
 │ • Description │
 │ │
 │ [Add Another Day] │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 5: Tickets │
 │ │
 │ Ticket Type Options: │
 │ │
 │ 1. Full Event Pass (common) │
 │ • Price: $200 │
 │ • Access to all days │
 │ │
 │ 2. Day Passes (optional) │
 │ • Day 1 only: $80 │
 │ • Day 2 only: $80 │
 │ • Day 3 only: $60 │
 │ │
 │ 3. VIP/Tier options │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 6: Review & Publish │
 └─────────────────────────────┘
 
 
 Flow 3: Recurring Event Creation 
 ┌──────────────────────────────────────────────────────────────┐
│ CREATE RECURRING EVENT │
└──────────────────────┬───────────────────────────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 1: Series Information │
 │ • Series name │
 │ • Description │
 │ • Category │
 │ • Cover image │
 │ • Visibility │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 2: Recurrence Pattern │
 │ │
 │ Frequency: │
 │ ○ Daily │
 │ ○ Weekly │
 │ ○ Monthly │
 │ ○ Yearly │
 │ │
 │ Interval: │
 │ Every [1] [week(s)] │
 │ │
 │ If Weekly, select days: │
 │ ☐ Monday │
 │ ☐ Tuesday │
 │ ☐ Wednesday │
 │ ☐ Thursday │
 │ ☐ Friday │
 │ ☐ Saturday │
 │ ☐ Sunday │
 │ │
 │ Session Time: │
 │ Start: [9:00 AM] │
 │ End: [10:00 AM] │
 │ │
 │ Duration: │
 │ ○ End date: [March 31, 2025]│
 │ ○ Number of occurrences: 12 │
 │ ○ No end date (ongoing) │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 3: Location Type │
 │ (Same as one-time event) │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 4: Default Ticket Types│
 │ │
 │ These will apply to ALL │
 │ sessions by default: │
 │ │
 │ Ticket Type 1: │
 │ • Name: Regular │
 │ • Price: $15 │
 │ • Quantity per session: 15 │
 │ • Description │
 │ │
 │ Ticket Type 2: │
 │ • Name: Student │
 │ • Price: $10 │
 │ • Quantity per session: 3 │
 │ • Requires verification │
 │ │
 │ Ticket Type 3: │
 │ • Name: Member │
 │ • Price: $8 │
 │ • Quantity per session: 2 │
 │ │
 │ [Add Another Ticket Type] │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 5: Generate Sessions │
 │ │
 │ System generates all │
 │ sessions based on pattern: │
 │ │
 │ Preview: │
 │ • Session 1: Jan 6, 9-10 AM │
 │ • Session 2: Jan 13, 9-10 AM│
 │ • Session 3: Jan 20, 9-10 AM│
 │ ... (12 total sessions) │
 │ │
 │ Each session gets: │
 │ • Copy of all ticket types │
 │ • Default quantities │
 │ • Default prices │
 │ │
 │ [Confirm & Generate] │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 6: Customize Sessions │
 │ (Optional) │
 │ │
 │ Organizer can edit any │
 │ individual session: │
 │ │
 │ • Change prices │
 │ • Add/remove ticket types │
 │ • Change quantities │
 │ • Modify time/location │
 │ • Cancel specific session │
 │ │
 │ Example: Jan 20 special │
 │ guest session │
 │ • Increase price to $20 │
 │ • Add VIP ticket ($30) │
 └──────────┬──────────────────┘
 ↓
 ┌─────────────────────────────┐
 │ Step 7: Publish Series │
 │ • All sessions now active │
 │ • Users can start registering│
 └─────────────────────────────┘
 
 
 Ticket Management 
 Ticket Types Structure 
 For One-Time & Multi-Day Events: 
 Event
 │
 ├── Ticket Type 1: Early Bird
 │ ├── Name: "Early Bird"
 │ ├── Price: $80
 │ ├── Quantity Available: 100
 │ ├── Quantity Sold: 87
 │ ├── Description: "Save $40 with early registration"
 │ ├── Sales Start: Dec 1, 2024
 │ ├── Sales End: Jan 31, 2025
 │ └── Status: Active
 │
 ├── Ticket Type 2: Regular
 │ ├── Name: "Regular"
 │ ├── Price: $120
 │ ├── Quantity Available: 200
 │ ├── Quantity Sold: 45
 │ ├── Sales Start: Feb 1, 2025
 │ ├── Sales End: March 14, 2025
 │ └── Status: Active
 │
 └── Ticket Type 3: VIP
 ├── Name: "VIP Pass"
 ├── Price: $250
 ├── Quantity Available: 50
 ├── Quantity Sold: 28
 ├── Benefits: ["VIP lounge access", "Meet speakers", "Premium seating"]
 ├── Sales Start: Dec 1, 2024
 ├── Sales End: March 14, 2025
 └── Status: Active
 
 For Recurring Events: 
 Series: "Monday Yoga"
 │
 ├── Default Ticket Templates
 │ ├── Template 1: Regular ($15)
 │ ├── Template 2: Student ($10)
 │ └── Template 3: Member ($8)
 │
 └── Sessions
 │
 ├── Session 1: Jan 6
 │ ├── Ticket Type: Regular - $15 (15 available, 12 sold)
 │ ├── Ticket Type: Student - $10 (3 available, 2 sold)
 │ └── Ticket Type: Member - $8 (2 available, 1 sold)
 │
 ├── Session 2: Jan 13
 │ ├── Ticket Type: Regular - $15 (15 available, 8 sold)
 │ ├── Ticket Type: Student - $10 (3 available, 1 sold)
 │ └── Ticket Type: Member - $8 (2 available, 0 sold)
 │
 └── Session 3: Jan 20 (Customized - Special Guest)
 ├── Ticket Type: Regular - $20 (15 available, 5 sold) ← Price changed
 ├── Ticket Type: Student - $15 (3 available, 1 sold) ← Price changed
 └── Ticket Type: VIP - $30 (5 available, 2 sold) ← New type added
 
 Ticket Type Properties 
 Required Fields: 
 
 Name (e.g., "Early Bird", "VIP", "Student") 
 Price (can be $0 for free tickets) 
 Quantity available 
 
 Optional Fields: 
 
 Description 
 Benefits list 
 Sales start date/time 
 Sales end date/time 
 Minimum purchase quantity 
 Maximum purchase quantity per user 
 Requires verification (e.g., student ID) 
 Hidden (invite-only tickets) 
 
 Ticket Purchase Rules 
 General Rules: 
 
 User must select at least one ticket 
 Cannot exceed available quantity 
 Cannot purchase after sales end date 
 Cannot purchase before sales start date 
 One ticket per session per user (for recurring events) 
 
 For Recurring Events: 
 
 First purchase = automatic series registration 
 Must buy at least one session to join series 
 Can buy different ticket types for different sessions 
 Can purchase multiple sessions at once 
 Can return later to purchase more sessions 
 
 Ticket Selection Flow: 
 ┌──────────────────────────────────────────────────────────────┐
│ EVENT: Tech Conference 2025 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Available Tickets: │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ☐ Early Bird - $80 │ │
│ │ Save $40 with early registration │ │
│ │ [Only 13 left!] Sales end Jan 31 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ☐ Regular - $120 │ │
│ │ Standard conference access │ │
│ │ [200 available] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ☐ VIP Pass - $250 │ │
│ │ ✓ All conference access │ │
│ │ ✓ VIP lounge access │ │
│ │ ✓ Meet the speakers │ │
│ │ ✓ Premium seating │ │
│ │ [22 available] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Quantity: [1] ▼ │
│ │
│ Selected: Regular Ticket × 1 = $120 │
│ │
│ [Proceed to Checkout →] │
└──────────────────────────────────────────────────────────────┘
 
 For Recurring Events: 
 ┌──────────────────────────────────────────────────────────────┐
│ SERIES: Monday Yoga - January 2025 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Select sessions you want to attend: │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Session: Monday, Jan 6, 2025 · 9:00 AM - 10:00 AM │ │
│ │ │ │
│ │ ☐ Regular - $15 [15 available] │ │
│ │ ☐ Student - $10 [3 available] │ │
│ │ ☐ Member - $8 [2 available] │ │
│ │ ☐ First-Timer Free - $0 [5 available] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Session: Monday, Jan 13, 2025 · 9:00 AM - 10:00 AM │ │
│ │ │ │
│ │ ☐ Regular - $15 [15 available] │ │
│ │ ☐ Student - $10 [3 available] │ │
│ │ ☐ Member - $8 [2 available] │ │
│ │ ☐ First-Timer Free - $0 [5 available] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Session: Monday, Jan 20, 2025 · 9:00 AM - 10:00 AM │ │
│ │ 🌟 SPECIAL GUEST SESSION │ │
│ │ │ │
│ │ ☐ Regular - $20 [15 available] │ │
│ │ ☐ Student - $15 [3 available] │ │
│ │ ☐ VIP - $30 [5 available] Meet the guest! │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Selected: │
│ • Jan 6 - Student ticket: $10 │
│ • Jan 13 - Student ticket: $10 │
│ • Jan 20 - Regular ticket: $20 │
│ │
│ Total: $40 for 3 sessions │
│ │
│ [Proceed to Checkout →] │
└──────────────────────────────────────────────────────────────┘
 
 
 Registration & Attendance 
 Registration Flow 
 For One-Time & Multi-Day Events: 
 User selects ticket
 ↓
Proceeds to checkout
 ↓
Payment processed
 ↓
Registration created
 ↓
Ticket issued
 ↓
Confirmation email sent
 ↓
User can view ticket in "My Events"
 
 For Recurring Events: 
 User selects session(s) and ticket type(s)
 ↓
First purchase?
 ↓
 ┌───┴────┐
 YES NO
 │ │
 ↓ ↓
Create Already
Series registered
Registration
 │ │
 └───┬────┘
 ↓
Payment processed
 ↓
Session ticket(s) issued
 ↓
Confirmation email sent
 ↓
User can:
• View registered series
• See owned session tickets
• Purchase more sessions
 
 Registration Data Tracking 
 One-Time Event Registration: 
 
 User ID 
 Event ID 
 Ticket Type ID 
 Payment status 
 Registration date/time 
 Ticket unique code/QR 
 Attendance status (for check-in) 
 
 Multi-Day Event Registration: 
 
 User ID 
 Event ID 
 Ticket Type ID 
 Payment status 
 Registration date/time 
 Ticket unique code/QR 
 Attendance per day (optional tracking):
 
 Day 1: Attended/Not attended 
 Day 2: Attended/Not attended 
 Day 3: Attended/Not attended 
 
 
 
 Recurring Event Registration: 
 Series Level: 
 
 User ID 
 Series ID 
 Registration date (first purchase date) 
 Status: Active/Cancelled 
 Total sessions owned 
 
 Session Level: 
 
 User ID 
 Session ID 
 Ticket Type ID 
 Payment status 
 Purchase date/time 
 Ticket unique code/QR 
 Attendance status 
 
 Attendance Tracking 
 Check-in Methods: 
 
 QR code scan 
 Manual check-in by organizer 
 Email verification 
 Unique ticket code entry 
 
 For Recurring Events: 
 
 Track attendance per session 
 User can have tickets for sessions 1, 3, 5 but not 2, 4 
 Each session = separate check-in 
 
 Attendance Reports: 
 
 Who registered vs who attended 
 No-show rate 
 For recurring: attendance patterns across sessions 
 
 
 User Roles & Permissions 
 Role Definitions 
 1. Event Organizer (Creator) 
 
 Creates events 
 Manages event details 
 Creates/modifies ticket types 
 Manages invitations (for private events) 
 Views registrations and attendance 
 Can edit event before it starts 
 Can cancel event 
 Receives payments (event revenue) 
 
 2. Attendee (Regular User) 
 
 Browses public/unlisted events 
 Registers for events 
 Purchases tickets 
 Receives invitations (for private events) 
 Views owned tickets 
 Can cancel registration (based on refund policy) 
 
 3. Platform Administrator 
 
 Manages all events 
 Can modify/delete any event 
 Views all registrations 
 Manages users 
 Handles disputes 
 Platform-level reporting 
 
 Organizer Capabilities 
 Event Management: 
 
 Create new events (all types) 
 Edit unpublished events (full edit) 
 Edit published events (limited - can't change past dates, can modify future details) 
 Cancel events (with attendee notifications) 
 Duplicate events 
 
 Ticket Management: 
 
 Create ticket types 
 Modify ticket quantities 
 Modify ticket prices (before sales start) 
 Enable/disable ticket types 
 Set sales periods 
 
 Recurring Event Specific: 
 
 Customize individual sessions 
 Cancel specific sessions 
 Reschedule sessions (with attendee notifications) 
 Modify session-specific ticket pricing 
 
 Registration Management: 
 
 View all registrations 
 Export attendee lists 
 Send messages to attendees 
 Manually add attendees (comp tickets) 
 Issue refunds 
 Check-in attendees 
 
 For Private Events: 
 
 Send invitations 
 Approve/reject registration requests 
 Regenerate invite links 
 View invitation status 
 
 Attendee Capabilities 
 Event Discovery: 
 
 Browse public events 
 Search events by category, date, location 
 Filter by event type 
 View event details 
 
 Registration: 
 
 Register for public events 
 Access unlisted events via link 
 Accept invitations for private events 
 Select ticket types 
 Purchase multiple tickets/sessions 
 
 Account Management: 
 
 View "My Events" (upcoming, past, cancelled) 
 View tickets/QR codes 
 Download tickets 
 Cancel registration (if allowed) 
 Rate/review events (after attendance) 
 
 For Recurring Events: 
 
 View registered series 
 See which sessions owned 
 Purchase additional sessions 
 View attendance history 
 
 
 Business Rules 
 Event Creation Rules 
 
 
 Required Information: 
 
 Event name (3-200 characters) 
 Start date/time (must be in future) 
 End date/time (must be after start) 
 Location details (based on type) 
 At least one ticket type OR mark as free 
 
 
 
 Date Validation: 
 
 Start date must be in future (at event creation) 
 End date must be after start date 
 For multi-day: Each day's dates must be sequential or specified 
 For recurring: End date must be after start date OR specify number of occurrences 
 
 
 
 Location Validation: 
 
 In-person: Address required 
 Online: Meeting link OR provider integration required 
 Hybrid: Both in-person AND online details required 
 
 
 
 Ticket Validation: 
 
 At least one ticket type OR mark event as free 
 Ticket quantity must be positive integer or unlimited 
 Price must be >= 0 
 Sales end date must be before or on event start date 
 Sales start date must be before sales end date 
 
 
 
 Registration Rules 
 
 
 Capacity Management: 
 
 Cannot register if event/session is at capacity 
 Cannot register if ticket type is sold out 
 For recurring: Each session has independent capacity 
 
 
 
 Time Restrictions: 
 
 Cannot register after event starts 
 Cannot register before ticket sales start 
 Cannot register after ticket sales end 
 For recurring: Can register for future sessions even if past sessions occurred 
 
 
 
 User Restrictions: 
 
 One registration per user per event (one-time/multi-day) 
 One ticket per user per session (recurring) 
 Cannot purchase same ticket type twice for same session 
 Can purchase different ticket types across different sessions 
 
 
 
 Private Event Rules: 
 
 Must have valid invitation 
 Invitation must not be expired 
 User email must match invitation email (or logged-in account) 
 
 
 
 Payment Rules: 
 
 Payment required before ticket issued (except free tickets) 
 Payment amount must match ticket price at time of purchase 
 For recurring: Can pay for multiple sessions in single transaction 
 
 
 
 Cancellation & Refund Rules 
 Event Cancellation (by Organizer): 
 
 Can cancel event at any time 
 Must notify all registered attendees 
 Automatic full refund to all attendees 
 For recurring: Can cancel entire series OR individual sessions 
 
 Registration Cancellation (by Attendee): 
 
 Depends on organizer's refund policy 
 Common policies:
 
 Full refund until X days before event 
 Partial refund until Y days before event 
 No refund after Z days before event 
 
 
 For recurring:
 
 Can cancel individual session tickets 
 If all sessions cancelled, series registration remains (can buy more sessions later) 
 
 
 
 Automated Actions: 
 
 Send cancellation notifications 
 Process refunds 
 Update capacities 
 Mark tickets as cancelled 
 
 Recurring Event Specific Rules 
 
 
 Session Generation: 
 
 Sessions auto-generated based on recurrence pattern 
 Each session gets copy of default ticket types 
 Sessions created in "scheduled" status 
 
 
 
 Session Customization: 
 
 Organizer can modify any session independently 
 Changes to one session don't affect others 
 Can cancel individual sessions 
 
 
 
 Registration Requirements: 
 
 Must purchase at least one session to register for series 
 First purchase = automatic series registration 
 Can purchase more sessions anytime before session starts 
 
 
 
 Session Attendance: 
 
 Each session = separate attendance record 
 User can own tickets for non-consecutive sessions 
 Missing a session doesn't affect future session tickets 
 
 
 
 Series Management: 
 
 Cannot delete series if any session has registrations 
 Can cancel future sessions 
 Past sessions remain in history 
 
 
 
 
 Data Relationships 
 Core Entities 
 ┌─────────────────────────────────────────────────────────────┐
│ User │
├─────────────────────────────────────────────────────────────┤
│ • User ID (PK) │
│ • Name, Email, Phone │
│ • Zoom access token (optional) │
│ • Account created date │
│ • Role (Organizer, Attendee, Admin) │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ creates
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Event (One-Time or Multi-Day) │
├─────────────────────────────────────────────────────────────┤
│ • Event ID (PK) │
│ • Organizer ID (FK → User) │
│ • Name, Description, Category │
│ • Event Type (ONE_TIME, MULTI_DAY) │
│ • Visibility (PUBLIC, PRIVATE, UNLISTED) │
│ • Location Type (IN_PERSON, ONLINE, HYBRID) │
│ • Start Date/Time, End Date/Time │
│ • Venue/Address (if in-person) │
│ • Meeting URL/Platform (if online) │
│ • Access Token (if unlisted) │
│ • Status (DRAFT, PUBLISHED, CANCELLED, COMPLETED) │
│ • Created/Updated timestamps │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ has many
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Ticket Type │
├─────────────────────────────────────────────────────────────┤
│ • Ticket Type ID (PK) │
│ • Event ID (FK → Event) │
│ • Name (e.g., "Early Bird", "VIP") │
│ • Price │
│ • Quantity Available │
│ • Quantity Sold │
│ • Description, Benefits │
│ • Sales Start Date, Sales End Date │
│ • Status (ACTIVE, SOLD_OUT, EXPIRED) │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ purchased via
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Event Registration │
├─────────────────────────────────────────────────────────────┤
│ • Registration ID (PK) │
│ • Event ID (FK → Event) │
│ • User ID (FK → User) │
│ • Ticket Type ID (FK → Ticket Type) │
│ • Unique Ticket Code / QR Code │
│ • Payment Status (PENDING, PAID, REFUNDED) │
│ • Registration Date/Time │
│ • Attendance Status (NOT_ATTENDED, ATTENDED) │
│ • Check-in Time (if attended) │
│ • Attendance Mode (IN_PERSON, ONLINE) - for hybrid events │
└─────────────────────────────────────────────────────────────┘

For Multi-Day Events:
┌─────────────────────────────────────────────────────────────┐
│ Event Day Schedule │
├─────────────────────────────────────────────────────────────┤
│ • Day Schedule ID (PK) │
│ • Event ID (FK → Event) │
│ • Day Number (1, 2, 3...) │
│ • Date │
│ • Start Time, End Time │
│ • Location (if different from main) │
│ • Description │
└─────────────────────────────────────────────────────────────┘

For Private Events:
┌─────────────────────────────────────────────────────────────┐
│ Event Invitation │
├─────────────────────────────────────────────────────────────┤
│ • Invitation ID (PK) │
│ • Event ID (FK → Event) │
│ • Invited User ID (FK → User, nullable) │
│ • Invited Email (if user doesn't exist) │
│ • Invite Token (unique) │
│ • Status (PENDING, ACCEPTED, DECLINED, EXPIRED) │
│ • Sent At, Expires At │
│ • Responded At │
└─────────────────────────────────────────────────────────────┘
 
 Recurring Event Relationships 
 ┌─────────────────────────────────────────────────────────────┐
│ Recurring Event Series │
├─────────────────────────────────────────────────────────────┤
│ • Series ID (PK) │
│ • Organizer ID (FK → User) │
│ • Series Name, Description │
│ • Visibility, Location Type │
│ • Recurrence Pattern (DAILY, WEEKLY, MONTHLY, YEARLY) │
│ • Recurrence Interval (e.g., every 2 weeks) │
│ • Days of Week (if weekly) │
│ • Day of Month (if monthly) │
│ • Series Start Date, Series End Date │
│ • Default Session Duration │
│ • Default Session Time │
│ • Total Sessions Generated │
│ • Status (DRAFT, PUBLISHED, CANCELLED, COMPLETED) │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ has many
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Series Session (Individual Occurrence) │
├─────────────────────────────────────────────────────────────┤
│ • Session ID (PK) │
│ • Series ID (FK → Recurring Event Series) │
│ • Session Number (1, 2, 3...) │
│ • Session Date │
│ • Start Time, End Time │
│ • Location (can differ from default) │
│ • Meeting URL (if online) │
│ • Status (SCHEDULED, CANCELLED, COMPLETED) │
│ • Is Customized (boolean - if differs from defaults) │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ has many
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Session Ticket Type │
├─────────────────────────────────────────────────────────────┤
│ • Session Ticket Type ID (PK) │
│ • Session ID (FK → Series Session) │
│ • Name (e.g., "Regular", "Student") │
│ • Price (can differ per session) │
│ • Quantity Available │
│ • Quantity Sold │
│ • Description │
│ • Requires Verification (boolean) │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ purchased via
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Series Registration (User enrolled in series) │
├─────────────────────────────────────────────────────────────┤
│ • Series Registration ID (PK) │
│ • Series ID (FK → Recurring Event Series) │
│ • User ID (FK → User) │
│ • Registration Date (first purchase date) │
│ • Status (ACTIVE, CANCELLED) │
│ • Total Sessions Owned │
└──────────────┬──────────────────────────────────────────────┘
 │
 │ owns
 ↓
┌─────────────────────────────────────────────────────────────┐
│ Session Ticket (User's ticket for specific session) │
├─────────────────────────────────────────────────────────────┤
│ • Session Ticket ID (PK) │
│ • Series Registration ID (FK → Series Registration) │
│ • Session ID (FK → Series Session) │
│ • Session Ticket Type ID (FK → Session Ticket Type) │
│ • User ID (FK → User) │
│ • Unique Ticket Code / QR Code │
│ • Payment Status (PENDING, PAID, REFUNDED) │
│ • Purchase Date/Time │
│ • Attendance Status (NOT_ATTENDED, ATTENDED) │
│ • Check-in Time (if attended) │
└─────────────────────────────────────────────────────────────┘
 
 Relationship Summary 
 One-to-Many Relationships: 
 
 User → Events (user creates many events) 
 Event → Ticket Types (event has many ticket types) 
 Event → Registrations (event has many registrations) 
 User → Registrations (user has many registrations) 
 Event → Event Invitations (private event has many invitations) 
 
 For Recurring Events: 
 
 Recurring Series → Sessions (series has many sessions) 
 Session → Session Ticket Types (each session has many ticket types) 
 User → Series Registrations (user registered for many series) 
 Series Registration → Session Tickets (registration owns many session tickets) 
 
 For Multi-Day Events: 
 
 Event → Day Schedules (event has schedule for each day) 
 
 
 Key Technical Considerations 
 Performance Optimization 
 
 
 Capacity Checks: 
 
 Cache available tickets count 
 Use optimistic locking for ticket purchases 
 Handle race conditions (multiple users buying last ticket) 
 
 
 
 Recurring Event Sessions: 
 
 Generate sessions in batches 
 Index sessions by date for quick lookups 
 Lazy load past sessions 
 
 
 
 Search & Discovery: 
 
 Index events by: date, category, location, visibility 
 Full-text search on event name/description 
 Filter by event type, price range, location type 
 
 
 
 Security Considerations 
 
 
 Access Control: 
 
 Verify user owns event before allowing edits 
 Validate invitation tokens 
 Rate limit invitation sends 
 Sanitize all user inputs 
 
 
 
 Payment Security: 
 
 Never store credit card details 
 Use payment gateway webhooks for status updates 
 Verify payment before issuing tickets 
 Handle failed payments gracefully 
 
 
 
 Online Meeting Links: 
 
 Don't expose links before access time 
 Validate user has valid ticket before showing link 
 Log link access for security auditing 
 Support link regeneration for compromised links 
 
 
 
 Edge Cases to Handle 
 
 
 Event Modifications: 
 
 Event date changed after registrations exist → Notify all attendees 
 Event cancelled → Auto-refund + notifications 
 Ticket quantity reduced below sold amount → Prevent or handle gracefully 
 Event visibility changed from Public to Private → Handle existing registrations 
 
 
 
 Recurring Events: 
 
 Session cancelled → Refund that session only, keep other tickets 
 Series cancelled → Refund all future sessions 
 User wants to change ticket type for purchased session → Depends on policy 
 Session time conflicts with another event user registered for → User's problem to manage 
 
 
 
 Capacity: 
 
 Event at capacity but user in checkout process → Reserve temporarily or first-come-first-served 
 Refund issued → Increase available capacity 
 Organizer increases capacity mid-sale → Allow more registrations 
 
 
 
 Payment: 
 
 Payment pending during checkout → Hold temporarily, expire after X minutes 
 Payment fails → Release reserved ticket, notify user 
 Refund requested → Verify eligibility, process, update capacity 
 
 
 
 
 User Experience Flows 
 Attendee: Discovering & Registering for One-Time Event 
 Homepage / Event Listing
 ↓
Browse or Search Events
 ↓
Filter by Category/Date/Location
 ↓
Click on Event
 ↓
View Event Details Page
• Description, Date, Location
• Available Tickets
• Organizer Info
 ↓
Select Ticket Type & Quantity
 ↓
Click "Register" or "Buy Ticket"
 ↓
Login / Sign Up (if not logged in)
 ↓
Review Order
• Ticket details
• Price breakdown
• Terms & conditions
 ↓
Enter Payment Information
 ↓
Complete Purchase
 ↓
Receive Confirmation
• Email with ticket
• QR code for check-in
 ↓
View in "My Events"
• Access ticket anytime
• Add to calendar
• Get event updates
 ↓
Day of Event
• Show QR code for check-in
• Access online meeting link (if applicable)
 
 Attendee: Registering for Recurring Event 
 Browse Events
 ↓
Discover Recurring Series
 ↓
View Series Details
• Pattern (every Monday)
• Dates covered
• Location & time
 ↓
View All Sessions
• Upcoming sessions listed
• Each with ticket options
 ↓
Select Session(s) & Ticket Type(s)
• Can select multiple sessions
• Choose appropriate ticket type per session
 ↓
Review Selection
• Jan 6 - Student: $10
• Jan 13 - Student: $10
• Jan 20 - Regular: $20
• Total: $40
 ↓
Checkout & Pay
 ↓
First Purchase?
 ↓
 YES → Create Series Registration
 NO → Add to existing registration
 ↓
Receive Confirmation
• Email with tickets for purchased sessions
• Series enrollment confirmed
 ↓
View in "My Events"
• See series enrollment
• View owned session tickets
• See upcoming sessions
 ↓
Later: Want to Attend More Sessions
 ↓
Go to Series Page
 ↓
Select Additional Sessions
 ↓
Purchase & Receive New Tickets
 ↓
Day of Session
• Show session-specific QR code
• Check in for that session only
 
 Organizer: Creating One-Time Event 
 Dashboard
 ↓
Click "Create New Event"
 ↓
Select Event Type: One-Time
 ↓
Step 1: Basic Info
• Name, description, category
• Upload cover image
• Select visibility
 ↓
Step 2: Date & Time
• Start date/time
• End date/time
• Timezone
 ↓
Step 3: Location
• Select type (in-person/online/hybrid)
• Enter details based on type
• If online + Zoom: Connect account or manual link
 ↓
Step 4: Tickets
• Add ticket types
• For each: name, price, quantity, description
• Set sales periods
 ↓
Step 5: Review
• Preview how event looks
• Check all details
 ↓
Save as Draft OR Publish
 ↓
If Published:
• Event goes live
• Appears in listings (if public)
• Tickets available for purchase
 ↓
Monitor Event
• View registrations
• Track ticket sales
• Send updates to attendees
 ↓
Day of Event
• Check in attendees
• View attendance list
 ↓
After Event
• View final attendance
• Download reports
• Close event
 
 Organizer: Creating Recurring Event Series 
 Dashboard
 ↓
Click "Create New Event"
 ↓
Select Event Type: Recurring
 ↓
Step 1: Series Info
• Series name & description
• Cover image
• Visibility
 ↓
Step 2: Recurrence Pattern
• Frequency (daily/weekly/monthly/yearly)
• Interval (every X weeks)
• Days of week (if weekly)
• Start & end date OR # of occurrences
 ↓
Step 3: Session Details
• Default session time
• Default duration
• Location type & details
 ↓
Step 4: Default Tickets
• Create ticket type templates
• These apply to all sessions by default
• Name, price, quantity per session
 ↓
Review Pattern & Generate Sessions
• Preview: "This will create 12 sessions"
• Show first few sessions as examples
 ↓
Confirm Generation
 ↓
System Creates All Sessions
• Each session = copy of defaults
• All sessions in "scheduled" status
 ↓
Optional: Customize Individual Sessions
• Click on any session
• Modify price, add ticket types, change time
• Example: Special guest session - increase price
 ↓
Publish Series
• All sessions go live
• Users can start registering
 ↓
Ongoing Management
• Monitor registrations per session
• Can still customize future sessions
• Cancel individual sessions if needed
• View attendance per session
 ↓
Each Session Day
• Check in attendees for that specific session
• Track attendance per session
 ↓
After Series Completes
• View overall series analytics
• Attendance patterns
• Revenue per session
• Download reports
 
 
 Additional Features & Considerations 
 Waitlist Management 
 When Event/Session is at Capacity: 
 User tries to register
 ↓
Event is full
 ↓
Option to "Join Waitlist"
 ↓
User added to waitlist
 ↓
If spot opens (cancellation/refund)
 ↓
First person on waitlist notified
 ↓
Limited time to claim spot (e.g., 24 hours)
 ↓
If claimed: Registration processed
If not claimed: Offer to next person
 
 Waitlist Properties: 
 
 Order (first-come, first-served) 
 Expiration time for offers 
 Notifications when spots available 
 User can leave waitlist 
 
 Event Updates & Notifications 
 Notify Attendees When: 
 
 Event details changed (date, time, location) 
 Event cancelled 
 New announcement from organizer 
 Reminder before event (24 hours, 1 hour) 
 Event starts soon (for online events - show link) 
 
 For Recurring Events: 
 
 Notify about upcoming session (24 hours before) 
 Notify if specific session cancelled 
 Notify if session time/location changed 
 
 Analytics & Reporting 
 For Organizers: 
 
 Total registrations 
 Revenue by ticket type 
 Sales over time (graph) 
 Attendance rate 
 Demographics (if collected) 
 For recurring: Per-session breakdown 
 
 For Platform: 
 
 Total events created 
 Event types distribution 
 Popular categories 
 Revenue metrics 
 User engagement 
 
 Social Features 
 Event Sharing: 
 
 Share on social media 
 Generate shareable link 
 Embed event widget 
 
 Reviews & Ratings: 
 
 After event, attendees can rate/review 
 Displayed on event page 
 Builds organizer reputation 
 
 Following: 
 
 Users can follow organizers 
 Get notified of new events 
 
 
 Implementation Priority 
 Phase 1: MVP (Core Features) 
 
 One-time event creation & management 
 Basic ticket types (free & paid) 
 Simple registration flow 
 Public event visibility 
 In-person and manual online link support 
 Basic attendee management 
 
 Phase 2: Enhanced Features 
 
 Multi-day events 
 Recurring events (simple - uniform pricing) 
 Private events with invitation system 
 Unlisted events 
 Multiple ticket types per event 
 Waitlist functionality 
 
 Phase 3: Advanced Features 
 
 Recurring events with variable pricing per session 
 Zoom/provider integration 
 Hybrid events with attendance mode selection 
 Advanced analytics 
 Event series customization (per-session modifications) 
 Refund automation 
 
 Phase 4: Premium Features 
 
 Social features (reviews, following) 
 Event recommendations 
 Advanced reporting 
 API for third-party integrations 
 White-label options 
 Mobile app 
 
 
 Conclusion 
 This document provides comprehensive requirements for an event management system supporting three distinct event types with flexible ticketing and access control. The system is designed to scale from simple one-time events to complex recurring series with variable pricing. 
 Key Differentiators: 
 
 Support for recurring events with per-session ticket management 
 Flexible visibility options (public, private, unlisted) 
 Multiple location modes including hybrid 
 Integration with online meeting providers 
 Granular ticket type control 
 
 Success Metrics: 
 
 Number of events created 
 Registration conversion rate 
 User satisfaction (organizers & attendees) 
 Platform revenue 
 Event completion rate 
 
 This system empowers organizers to create professional events while providing attendees with a smooth discovery and registration experience. 
 
 Document Version: 1.0 
 Last Updated: November 23, 2025 
 Status: Requirements Approved - Ready for Development

Event Categories Management API
Author : Josh S. Sakweli, Backend Lead Team 
 Last Updated : 2025-12-11 
 Version : v1.0 
 Base URL : https://api.nexgate.com/api/v1 
 Short Description : The Event Categories Management API provides administrative functionality for managing event categories in the Nexgate platform. This API enables authorized users (Staff Admin/Super Admin) to create, update, retrieve event categories, and seed default categories for event classification and organization. 
 Hints : 
 
 All POST/PUT endpoints require STAFF_ADMIN or SUPER_ADMIN role 
 GET endpoints are accessible to all authenticated users 
 Category names are unique (case-insensitive) 
 Slugs are auto-generated from category names and must be unique 
 Slug collisions are handled automatically by appending numbers (e.g., music-1, music-2) 
 Categories support featured and active status flags 
 Pagination is 1-indexed (page=1 for first page), default size is 10 items 
 Event count tracks number of events in each category (updated automatically) 
 Color codes must be valid hex format (#FF5733 or #F57) 
 Icon URLs must be valid image URLs or paths 
 
 
 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"
}
 
 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 
 For better visual clarity, all endpoints use colored badges for HTTP methods with the following standard colors: 
 
 GET - GET - Green (Safe, read-only operations) 
 POST - POST - Blue (Create new resources) 
 PUT - PUT - Yellow (Update/replace entire resource) 
 
 
 EventCategoryResponse Structure 
 This is the standard response structure returned by all category endpoints: 
 {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 125,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": "admin_user",
 "updatedAt": "2025-12-11T14:20:30"
}
 
 Response Field Descriptions 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 categoryId 
 string (UUID) 
 Unique identifier for the category 
 
 
 name 
 string 
 Category display name (unique, case-insensitive) 
 
 
 slug 
 string 
 URL-friendly identifier (auto-generated, unique) 
 
 
 description 
 string 
 Category description 
 
 
 iconUrl 
 string 
 URL or path to category icon image 
 
 
 colorCode 
 string 
 Hex color code for category theming (#RRGGBB or #RGB) 
 
 
 isActive 
 boolean 
 Whether category is active and visible 
 
 
 isFeatured 
 boolean 
 Whether category should be featured/highlighted 
 
 
 eventCount 
 integer 
 Number of events in this category 
 
 
 createdBy 
 string 
 Username of the admin who created the category 
 
 
 createdAt 
 string 
 ISO 8601 timestamp of creation 
 
 
 updatedBy 
 string 
 Username of the admin who last updated (null if never updated) 
 
 
 updatedAt 
 string 
 ISO 8601 timestamp of last update (null if never updated) 
 
 
 
 
 Endpoints 
 1. Create Category 
 Purpose : Create a new event category (requires admin privileges) 
 Endpoint : POST {base_url}/e-events/categories 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication + Admin Role) 
 Required Roles : ROLE_STAFF_ADMIN or ROLE_SUPER_ADMIN 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Request JSON Sample : 
 {
 "name": "Music & Concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 name 
 string 
 Yes 
 Category name 
 Min: 2, Max: 100 characters 
 
 
 description 
 string 
 No 
 Category description 
 Max: 500 characters 
 
 
 iconUrl 
 string 
 No 
 URL or path to icon image 
 Must be valid image URL (jpg, jpeg, png, gif, svg, webp) or path starting with /icons/ 
 
 
 colorCode 
 string 
 No 
 Hex color code 
 Must be valid hex format: #RRGGBB or #RGB (e.g., #FF5733 or #F57) 
 
 
 isActive 
 boolean 
 Yes 
 Whether category is active 
 Required boolean value 
 
 
 isFeatured 
 boolean 
 Yes 
 Whether category is featured 
 Required boolean value 
 
 
 
 Success Response : Returns standard EventCategoryResponse structure (see "EventCategoryResponse Structure" section above) 
 Success Response Message : "Category created successfully" 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Category created successfully",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 0,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 }
}
 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 400 BAD_REQUEST : Category already exists (duplicate name) 
 401 UNAUTHORIZED : Authentication issues (empty, invalid, expired, or malformed tokens) 
 403 FORBIDDEN : Access denied (insufficient permissions - not admin) 
 404 NOT_FOUND : User not found or not authenticated 
 422 UNPROCESSABLE_ENTITY : Validation errors with detailed field information 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 Error Response Examples : 
 Already Exists - Duplicate Name (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Category already exists",
 "action_time": "2025-12-11T10:30:45",
 "data": "Category with name 'Music & Concerts' already exists"
}
 
 Access Denied - Insufficient Permissions (403): 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "Access denied",
 "action_time": "2025-12-11T10:30:45",
 "data": "Access denied. Insufficient permissions."
}
 
 Validation Error - Invalid Name (422): 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Validation failed",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "name": "Category name must be between 2 and 100 characters"
 }
}
 
 Validation Error - Invalid Color Code (422): 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Validation failed",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "colorCode": "Color code must be a valid hex color (e.g., #FF5733 or #F57)"
 }
}
 
 Validation Error - Invalid Icon URL (422): 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Validation failed",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "iconUrl": "Icon URL must be a valid image URL or path"
 }
}
 
 
 2. Update Category 
 Purpose : Update an existing event category (requires admin privileges) 
 Endpoint : PUT {base_url}/e-events/categories/{categoryId} 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication + Admin Role) 
 Required Roles : ROLE_STAFF_ADMIN or ROLE_SUPER_ADMIN 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 categoryId 
 string 
 Yes 
 UUID of the category to update 
 Must be valid UUID format 
 
 
 
 Request JSON Sample : 
 {
 "name": "Live Music & Concerts",
 "description": "Updated description for live music events",
 "iconUrl": "https://cdn.nexgate.com/icons/music-updated.svg",
 "colorCode": "#FF1744",
 "isActive": true,
 "isFeatured": false
}
 
 Request Body Parameters (All Optional): 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 name 
 string 
 No 
 Updated category name 
 Min: 2, Max: 100 characters 
 
 
 description 
 string 
 No 
 Updated description 
 Max: 500 characters 
 
 
 iconUrl 
 string 
 No 
 Updated icon URL/path 
 Must be valid image URL or path 
 
 
 colorCode 
 string 
 No 
 Updated hex color code 
 Must be valid hex format: #RRGGBB or #RGB 
 
 
 isActive 
 boolean 
 No 
 Updated active status 
 
 
 
 isFeatured 
 boolean 
 No 
 Updated featured status 
 
 
 
 
 Success Response : Returns standard EventCategoryResponse structure with updated values 
 Success Response Message : "Category updated successfully" 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 400 BAD_REQUEST : Category name already exists (when changing name to duplicate) 
 401 UNAUTHORIZED : Authentication issues 
 403 FORBIDDEN : Access denied (insufficient permissions - not admin) 
 404 NOT_FOUND : Category not found with given ID 
 422 UNPROCESSABLE_ENTITY : Validation errors 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 Error Response Examples : 
 Not Found - Invalid Category ID (404): 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Category not found",
 "action_time": "2025-12-11T10:30:45",
 "data": "Category not found with ID: 550e8400-e29b-41d4-a716-446655440000"
}
 
 Already Exists - Duplicate Name (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Category already exists",
 "action_time": "2025-12-11T10:30:45",
 "data": "Category with name 'Sports & Fitness' already exists"
}
 
 
 3. Get Category by ID 
 Purpose : Retrieve a single category by its UUID 
 Endpoint : GET {base_url}/e-events/categories/{categoryId} 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 categoryId 
 string 
 Yes 
 UUID of the category to retrieve 
 Must be valid UUID format 
 
 
 
 Success Response : Returns standard EventCategoryResponse structure 
 Success Response Message : "Category retrieved successfully" 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Category retrieved successfully",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 125,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": "admin_user",
 "updatedAt": "2025-12-11T14:20:30"
 }
}
 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 401 UNAUTHORIZED : Authentication issues 
 404 NOT_FOUND : Category not found with given ID 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 
 4. Get Category by Slug 
 Purpose : Retrieve a single category by its URL-friendly slug 
 Endpoint : GET {base_url}/e-events/categories/slug/{slug} 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 slug 
 string 
 Yes 
 URL-friendly slug of the category 
 Lowercase, hyphen-separated (e.g., music-concerts) 
 
 
 
 Example Request URL : https://api.nexgate.com/api/v1/e-events/categories/slug/music-concerts 
 Success Response : Returns standard EventCategoryResponse structure 
 Success Response Message : "Category retrieved successfully" 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 401 UNAUTHORIZED : Authentication issues 
 404 NOT_FOUND : Category not found with given slug 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 Error Response Example : 
 Not Found - Invalid Slug (404): 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Category not found",
 "action_time": "2025-12-11T10:30:45",
 "data": "Category not found with slug: non-existent-category"
}
 
 
 5. Get All Categories 
 Purpose : Retrieve all event categories (unpaginated, complete list) 
 Endpoint : GET {base_url}/e-events/categories/all 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Categories retrieved successfully",
 "action_time": "2025-12-11T10:30:45",
 "data": [
 {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 125,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 },
 {
 "categoryId": "660e8400-e29b-41d4-a716-446655440001",
 "name": "Sports & Fitness",
 "slug": "sports-fitness",
 "description": "Yoga, gym classes, marathons, tournaments, and outdoor activities",
 "iconUrl": "https://cdn.nexgate.com/icons/sports.svg",
 "colorCode": "#4CAF50",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 89,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 }
 ]
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data 
 Array of EventCategoryResponse objects (complete list, no pagination) 
 
 
 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 401 UNAUTHORIZED : Authentication issues 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 
 6. Get Paginated Categories 
 Purpose : Retrieve event categories with pagination support 
 Endpoint : GET {base_url}/e-events/categories/paged 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (1-indexed) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Number of items per page 
 Min: 1, Max: 100 
 10 
 
 
 
 Example Request URL : https://api.nexgate.com/api/v1/e-events/categories/paged?page=1&size=10 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Categories retrieved successfully",
 "action_time": "2025-12-11T10:30:45",
 "data": {
 "content": [
 {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "https://cdn.nexgate.com/icons/music.svg",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 125,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 }
 ],
 "pageable": {
 "pageNumber": 0,
 "pageSize": 10,
 "sort": {
 "sorted": false,
 "empty": true,
 "unsorted": true
 },
 "offset": 0,
 "paged": true,
 "unpaged": false
 },
 "totalPages": 1,
 "totalElements": 10,
 "last": true,
 "size": 10,
 "number": 0,
 "sort": {
 "sorted": false,
 "empty": true,
 "unsorted": true
 },
 "numberOfElements": 10,
 "first": true,
 "empty": false
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 content 
 Array of EventCategoryResponse objects for current page 
 
 
 totalPages 
 Total number of pages available 
 
 
 totalElements 
 Total number of categories across all pages 
 
 
 size 
 Number of items per page (requested size) 
 
 
 number 
 Current page number (0-indexed in response) 
 
 
 first 
 Boolean indicating if this is the first page 
 
 
 last 
 Boolean indicating if this is the last page 
 
 
 empty 
 Boolean indicating if the result set is empty 
 
 
 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 401 UNAUTHORIZED : Authentication issues 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 
 7. Seed Categories 
 Purpose : Populate the database with default event categories (requires admin privileges) 
 Endpoint : POST {base_url}/e-events/categories/seed 
 Access Level : 🔒 Protected (Requires Bearer Token Authentication + Admin Role) 
 Required Roles : ROLE_STAFF_ADMIN or ROLE_SUPER_ADMIN 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token for authentication (format: Bearer <token> ) 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Request Body : None (no body required) 
 Default Categories Seeded : 
 This endpoint creates the following 10 default categories (if they don't already exist): 
 
 Music & Concerts - Live music performances, concerts, festivals, and DJ events (Featured) 
 Sports & Fitness - Yoga, gym classes, marathons, tournaments, and outdoor activities (Featured) 
 Business & Networking - Professional meetups, conferences, workshops, and networking events (Featured) 
 Food & Drink - Food festivals, cooking classes, wine tastings, and dining experiences 
 Arts & Culture - Art exhibitions, theater, dance, museums, and cultural events 
 Education & Learning - Workshops, seminars, courses, bootcamps, and training sessions (Featured) 
 Social & Community - Parties, meetups, social clubs, game nights, and community events 
 Technology & Innovation - Tech talks, hackathons, product launches, and startup events 
 Wellness & Spirituality - Meditation, yoga retreats, healing workshops, and mindfulness events 
 Entertainment - Comedy shows, movie screenings, gaming, and entertainment events 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Categories seeded successfully",
 "action_time": "2025-12-11T10:30:45",
 "data": [
 {
 "categoryId": "550e8400-e29b-41d4-a716-446655440000",
 "name": "Music & Concerts",
 "slug": "music-concerts",
 "description": "Live music performances, concerts, festivals, and DJ events",
 "iconUrl": "",
 "colorCode": "#E91E63",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 0,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 },
 {
 "categoryId": "660e8400-e29b-41d4-a716-446655440001",
 "name": "Sports & Fitness",
 "slug": "sports-fitness",
 "description": "Yoga, gym classes, marathons, tournaments, and outdoor activities",
 "iconUrl": "",
 "colorCode": "#4CAF50",
 "isActive": true,
 "isFeatured": true,
 "eventCount": 0,
 "createdBy": "admin_user",
 "createdAt": "2025-12-11T10:30:45",
 "updatedBy": null,
 "updatedAt": null
 }
 // ... (remaining 8 categories)
 ]
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data 
 Array of EventCategoryResponse objects (newly created categories only, or all existing if none were created) 
 
 
 
 Behavior Notes : 
 
 Only creates categories that don't already exist (checked by name, case-insensitive) 
 If all 10 default categories already exist, returns the existing categories 
 All seeded categories are marked as active ( isActive: true ) 
 Categories marked as "Featured" have isFeatured: true 
 Event count starts at 0 for newly seeded categories 
 The authenticated admin user is set as createdBy for all new categories 
 Slugs are auto-generated from category names 
 
 Standard Error Types : 
 Application-Level Exceptions (400-499) 
 
 401 UNAUTHORIZED : Authentication issues 
 403 FORBIDDEN : Access denied (insufficient permissions - not admin) 
 404 NOT_FOUND : User not found or not authenticated 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors 
 
 Error Response Example : 
 Access Denied - Not Admin (403): 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "Access denied",
 "action_time": "2025-12-11T10:30:45",
 "data": "Access denied. Insufficient permissions."
}
 
 
 Quick Reference Guide 
 Common HTTP Status Codes 
 
 200 OK : Successful GET/POST/PUT request 
 400 Bad Request : Invalid request data or business rule violation (duplicate names) 
 401 Unauthorized : Authentication required/failed 
 403 Forbidden : Insufficient permissions (not admin) 
 404 Not Found : Resource not found (category, user) 
 422 Unprocessable Entity : Validation errors 
 500 Internal Server Error : Server error 
 
 Authentication & Authorization 
 
 Bearer Token : Include Authorization: Bearer your_token in headers 
 Required Roles for POST/PUT : ROLE_STAFF_ADMIN or ROLE_SUPER_ADMIN 
 GET Endpoints : Accessible to all authenticated users 
 
 Data Format Standards 
 
 Dates : ISO 8601 format (2025-12-11T14:30:00) 
 IDs : UUID format (e.g., 550e8400-e29b-41d4-a716-446655440000) 
 Slugs : Lowercase, hyphen-separated (e.g., music-concerts) 
 Color Codes : Hex format #RRGGBB or #RGB (e.g., #FF5733 or #F57) 
 Pagination : 1-indexed pages (page=1 for first page), default size=10 
 
 Category Name Rules 
 
 Minimum 2 characters, maximum 100 characters 
 Must be unique (case-insensitive check) 
 Automatically trimmed of whitespace 
 Used to auto-generate slug 
 
 Slug Generation Rules 
 
 Auto-generated from category name 
 Lowercase only 
 Special characters removed 
 Spaces replaced with hyphens 
 Duplicate hyphens removed 
 Leading/trailing hyphens removed 
 Collisions handled by appending numbers (e.g., music-1, music-2) 
 
 Icon URL Validation 
 
 Must be valid HTTP/HTTPS URL ending in image extension (jpg, jpeg, png, gif, svg, webp) 
 OR must be path starting with /icons/ ending in image extension 
 Examples:
 
 Valid: https://cdn.nexgate.com/icons/music.svg 
 Valid: /icons/music.png 
 Invalid: https://example.com/file.pdf 
 
 
 
 Color Code Validation 
 
 Must start with # 
 Must be 6-character hex (e.g., #FF5733) or 3-character hex (e.g., #F57) 
 Case-insensitive (A-F or a-f) 
 Examples:
 
 Valid: #FF5733 , #F57 , #e91e63 
 Invalid: FF5733 , #GG5733 , #12345 
 
 
 
 Featured Categories 
 Categories with isFeatured: true are typically displayed prominently in UI: 
 
 Music & Concerts 
 Sports & Fitness 
 Business & Networking 
 Education & Learning 
 
 Event Count 
 
 Automatically updated when events are created/deleted in a category 
 Starts at 0 for new categories 
 Used for analytics and sorting popular categories

Events  API

Events  Management API
Author : Josh S. Sakweli, Backend Lead Team
 Last Updated : 2026-05-25
 Version : v1.2 
 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 : 
 
 All write operations (create, update, publish) require a valid Bearer token. Read operations on published events are public. 
 Dates and times must always use ISO 8601 format with timezone offset (e.g., 2025-06-15T09:00:00+03:00 ). The API stores and returns ZonedDateTime . 
 Event creation follows a staged workflow : BASIC_INFO → SCHEDULE → LOCATION_DETAILS → TICKETS . All required stages must be completed before publishing. 
 Slugs are auto-generated from the event title with a UUID suffix to guarantee uniqueness — do not pass a slug manually. 
 Pagination uses 1-based page numbers (page=1 is the first page). 
 Category seeding and management live in the Event Categories API (base: /api/v1/e-events/categories ), documented separately. 
 
 
 Event Creation User Journey 
 [Organizer]
 │
 │ POST /draft
 ▼
 ┌─────────────┐
 │ BASIC INFO │ title, category, format, description, media, ctaLabel (optional)
 └──────┬──────┘
 │ PATCH /drafts/{id}/basic-info (optional update)
 │
 │ PATCH /draft/{id}/schedule
 ▼
 ┌──────────────┐
 │ SCHEDULE │ days[ date, startTime, endTime ], timezone
 └──────┬───────┘
 │
 │ PATCH /draft/{id}/location
 ▼
 ┌──────────────────┐
 │ LOCATION DETAILS │ venue (IN_PERSON/HYBRID) or virtualDetails (ONLINE/HYBRID)
 └──────┬───────────┘ or skip entirely (TBA format)
 │
 │ (Ticket creation via Tickets API — required before publish)
 ▼
 ┌──────────────┐
 │ TICKETS │ at least one active ticket required
 └──────┬───────┘
 │
 │ (Optional enrichment)
 │ PATCH /drafts/{id}/highlights
 │ PATCH /drafts/{id}/faqs
 │ PATCH /drafts/{id}/lineup
 │ PATCH /drafts/{id}/agenda
 │ POST /draft/{id}/products/{productId}
 │ POST /draft/{id}/shops/{shopId}
 │
 │ PATCH /{eventId}/publish
 ▼
 ┌───────────────┐
 │ PUBLISHED │ RSA keys generated, ctaLabel auto-derived if not set,
 │ │ category count incremented, event visible in public feed
 └───────────────┘
 
 Event Status Flow 
 DRAFT ◄──────────────────────► PUBLISHED
 │ (unpublish │
 │ if 0 tickets sold) │ (unpublish — only if no tickets sold)
 │ │
 │ (discardDraft) │
 ▼ ▼
 [deleted] CANCELLED ◄─── (cancel from any non-terminal status)
 │
 [terminal state]

 PUBLISHED ──► (system / scheduled job) ──► HAPPENING ──► COMPLETED
 
 
 Status Rules : 
 
 DRAFT ↔ PUBLISHED — Free movement. Unpublish is only allowed if zero tickets have been sold. 
 CANCELLED — Terminal. Can be triggered from any non-terminal status. Triggers bulk refund if tickets were sold. 
 HAPPENING / COMPLETED — System-managed via scheduled jobs. 
 
 
 
 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 
 
 
 ctaLabel 
 string 
 CTA button label (e.g., "Get Tickets", "Register for Free"). Auto-derived from ticket pricing at publish if not explicitly set 
 
 
 hasApplicantForm 
 boolean 
 Whether an applicant form is configured for this event 
 
 
 applicantForm.displayTime 
 string 
 When the form is shown: BEFORE_CHECKOUT or AFTER_CHECKOUT 
 
 
 applicantForm.isRequired 
 boolean 
 Whether form submission is required for online attendees 
 
 
 applicantForm.applyToAtDoor 
 boolean 
 Whether the form also applies to at-door check-in 
 
 
 currentStage 
 string 
 Current event creation stage 
 
 
 completedStages[] 
 array 
 List of completed stage names 
 
 
 completionPercentage 
 integer 
 0–100 completion percentage 
 
 
 canPublish 
 boolean 
 Whether all required stages are completed 
 
 
 createdAt 
 ZonedDateTime 
 Creation timestamp 
 
 
 updatedAt 
 ZonedDateTime 
 Last update timestamp 
 
 
 createdBy 
 string 
 Username of creator 
 
 
 updatedBy 
 string 
 Username of last editor 
 
 
 
 EventDayInfo 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 id 
 UUID 
 Day entity ID 
 
 
 date 
 string 
 Date in YYYY-MM-DD format 
 
 
 startTime 
 string 
 Start time in HH:mm:ss 
 
 
 endTime 
 string 
 End time in HH:mm:ss 
 
 
 description 
 string 
 Optional day description 
 
 
 dayOrder 
 integer 
 Display order (1-based) 
 
 
 
 HighlightEntry 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 type 
 string 
 AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM 
 
 
 title 
 string 
 Display title for this highlight 
 
 
 value 
 string 
 Short value (e.g., "18+") 
 
 
 description 
 string 
 Longer explanation 
 
 
 
 FaqEntry 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 question 
 string 
 The FAQ question 
 
 
 answer 
 string 
 The answer 
 
 
 order 
 integer 
 Display order 
 
 
 
 LineupEntry 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 entryType 
 string 
 PLATFORM_USER or CUSTOM 
 
 
 userId 
 UUID 
 Platform user ID (only when entryType=PLATFORM_USER ) 
 
 
 name 
 string 
 Display name (auto-enriched from user profile when PLATFORM_USER ) 
 
 
 role 
 string 
 HEADLINER , PERFORMER , SPEAKER , DJ , HOST , PANELIST , MODERATOR , GUEST 
 
 
 title 
 string 
 Professional title (e.g., "Lead Vocalist") 
 
 
 bio 
 string 
 Short biography 
 
 
 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 within its sales window 
 
 
 saleStatusMessage 
 string 
 Human-readable explanation of the current sale status 
 
 
 
 
 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 
 
 
 hasApplicantForm 
 boolean 
 Whether an applicant form is configured 
 
 
 ctaLabel 
 string 
 CTA button label 
 
 
 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 
 
 GET — Green ( #28a745 ) — Safe, read-only operations 
 POST — Blue ( #007bff ) — Create new resources 
 PUT — Yellow ( #ffc107 , black text) — Replace entire resource 
 PATCH — Orange ( #fd7e14 ) — Partial update 
 DELETE — Red ( #dc3545 ) — Remove resource 
 
 
 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": 25,
 "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"
 },
 "ctaLabel": null,
 "hasApplicantForm": false,
 "applicantForm": null,
 "createdAt": "2025-02-17T10:30:45+03:00",
 "updatedAt": null,
 "createdBy": "amina.hassan",
 "updatedBy": null
 }
}
 
 Success Response Fields : See Shared Response Object A — EventResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token — see Shared Error: 401 
 
 
 404 
 Category ID not found 
 
 
 422 
 Missing required fields (title, categoryId, eventFormat) — see Shared Error: 422 
 
 
 
 
 2. Get My Drafts 
 Purpose : Returns a paginated list of all DRAFT events owned by the authenticated organizer. 
 Endpoint : GET /api/v1/e-events/drafts 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Drafts retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [
 {
 "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "title": "Dar es Salaam Jazz Festival 2025",
 "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
 "shortDescription": "An annual celebration of jazz and live music...",
 "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "categoryName": "Music & Concerts",
 "eventFormat": "IN_PERSON",
 "eventVisibility": "PUBLIC",
 "status": "DRAFT",
 "startDateTime": null,
 "endDateTime": null,
 "timezone": null,
 "locationSummary": null,
 "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
 "hasApplicantForm": false,
 "ctaLabel": null,
 "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",
 "ctaLabel": "Get Tickets",
 "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 
 
 
 ctaLabel 
 string 
 No 
 CTA button label override 
 Max: 50 characters. If omitted, existing value is kept; auto-derived at publish if never set 
 
 
 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 : 
 
 All existing days are replaced on each call. To update the schedule, resend the full days array. 
 Dates must be unique — duplicate dates in the same request are rejected with 422 . 
 Days must be provided in chronological order (sorted ascending by date). 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Schedule updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . schedule.days will be populated, and schedule.startDateTime / schedule.endDateTime will be set from the first and last day. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the draft owner 
 
 
 404 
 Draft not found 
 
 
 422 
 Days out of order, duplicate dates, date in the past, missing time fields 
 
 
 
 
 7. Update Draft — Location 
 Purpose : Sets the physical venue and/or virtual meeting details for the event. Required fields depend on eventFormat . For TBA format, this endpoint can be called but no fields are required — the stage is automatically marked complete. Advances currentStage to TICKETS . 
 Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/location 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 draftId 
 UUID 
 Yes 
 The draft to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample (IN_PERSON): 
 {
 "venue": {
 "name": "Mlimani City Arena",
 "address": "Sam Nujoma Road, Dar es Salaam",
 "coordinates": {
 "latitude": -6.7724,
 "longitude": 39.2083
 }
 }
}
 
 Request JSON Sample (ONLINE): 
 {
 "virtualDetails": {
 "meetingLink": "https://zoom.us/j/123456789",
 "meetingId": "123 456 789",
 "passcode": "jazz2025"
 }
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 venue 
 object 
 Conditional 
 Required when format is IN_PERSON or HYBRID 
 — 
 
 
 venue.name 
 string 
 Yes (if venue) 
 Venue name 
 Max: 200 characters 
 
 
 venue.address 
 string 
 No 
 Full address 
 Max: 500 characters 
 
 
 venue.coordinates.latitude 
 BigDecimal 
 No 
 GPS latitude 
 — 
 
 
 venue.coordinates.longitude 
 BigDecimal 
 No 
 GPS longitude 
 — 
 
 
 virtualDetails 
 object 
 Conditional 
 Required when format is ONLINE or HYBRID 
 — 
 
 
 virtualDetails.meetingLink 
 string 
 Yes (if virtualDetails) 
 Meeting URL 
 Max: 500 characters 
 
 
 virtualDetails.meetingId 
 string 
 No 
 Platform meeting ID 
 Max: 100 characters 
 
 
 virtualDetails.passcode 
 string 
 No 
 Meeting passcode 
 Max: 100 characters 
 
 
 
 
 Format-based rules : 
 
 IN_PERSON → venue.name is required; virtualDetails is ignored 
 ONLINE → virtualDetails.meetingLink is required; venue is ignored 
 HYBRID → both venue.name and virtualDetails.meetingLink are required 
 TBA → no fields required; stage is immediately marked complete 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Location updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . completedStages will include "LOCATION_DETAILS" when requirements are met and currentStage will advance to "TICKETS" . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the draft owner 
 
 
 404 
 Draft not found 
 
 
 422 
 Missing required venue/virtual fields for the event format 
 
 
 
 
 8. Update Draft — Highlights 
 Purpose : Replaces the full list of event highlights (key attendee-facing info such as age restriction, dress code, parking, etc.). Sending an empty array clears all highlights. 
 Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/highlights 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 draftId 
 UUID 
 Yes 
 The draft to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "highlights": [
 {
 "type": "AGE_RESTRICTION",
 "title": "Age Limit",
 "value": "18+",
 "description": "This event is strictly for adults aged 18 and above."
 },
 {
 "type": "DRESS_CODE",
 "title": "Dress Code",
 "value": "Smart Casual",
 "description": "No sportswear or flip flops allowed."
 }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 highlights 
 array 
 Yes 
 Full list of highlights 
 Empty array is valid (clears existing) 
 
 
 highlights[].type 
 string 
 Yes 
 Highlight category 
 Enum: AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM 
 
 
 highlights[].title 
 string 
 Yes 
 Display title 
 — 
 
 
 highlights[].value 
 string 
 No 
 Short value summary 
 — 
 
 
 highlights[].description 
 string 
 No 
 Longer explanation 
 — 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Highlights updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . data.highlights will reflect the new list. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the draft owner 
 
 
 404 
 Draft not found 
 
 
 
 
 9. Update Draft — FAQs 
 Purpose : Replaces the full list of frequently asked questions for the event. Sending an empty array clears all FAQs. 
 Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/faqs 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 draftId 
 UUID 
 Yes 
 The draft to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "faqs": [
 {
 "question": "Is this event suitable for children?",
 "answer": "No. This event is strictly 18+ only.",
 "order": 1
 },
 {
 "question": "Is parking available?",
 "answer": "Yes, free parking is available on site.",
 "order": 2
 }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 faqs 
 array 
 Yes 
 Full list of FAQs 
 Empty array is valid (clears existing) 
 
 
 faqs[].question 
 string 
 Yes 
 FAQ question text 
 — 
 
 
 faqs[].answer 
 string 
 Yes 
 FAQ answer text 
 — 
 
 
 faqs[].order 
 integer 
 No 
 Display order 
 — 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "FAQs updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . data.faqs will reflect the new list. 
 Possible Error Responses : Same as Endpoint 8 (401, 403, 404). 
 
 10. Update Draft — Lineup 
 Purpose : Replaces the full event lineup. Supports both platform users (whose profile data is auto-enriched) and custom entries for external performers or speakers. Sending an empty array clears the lineup. 
 Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/lineup 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 draftId 
 UUID 
 Yes 
 The draft to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "lineup": [
 {
 "entryType": "PLATFORM_USER",
 "userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
 "role": "HEADLINER",
 "performanceDay": 1,
 "performanceTime": "21:00",
 "order": 1
 },
 {
 "entryType": "CUSTOM",
 "name": "DJ Afrobeat",
 "role": "DJ",
 "title": "International Guest DJ",
 "bio": "Award-winning DJ from Lagos with 10 years of experience.",
 "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 
 
 
 
 
 11. Update Draft — Agenda 
 Purpose : Replaces the full event agenda organized by day and session. Supports both platform users and custom entries as session presenters. Sending an empty array clears the agenda. 
 Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/agenda 
 Access Level : 🔒 Protected (Organizer — own drafts only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 draftId 
 UUID 
 Yes 
 The draft to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "agenda": [
 {
 "dayNumber": 1,
 "date": "2025-07-18",
 "sessions": [
 {
 "startTime": "18:00",
 "endTime": "19:00",
 "title": "Welcome & Opening Ceremony",
 "type": "CEREMONY",
 "location": "Main Stage",
 "presenterType": "PLATFORM_USER",
 "presenterId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
 },
 {
 "startTime": "20:00",
 "endTime": "22:00",
 "title": "Headliner Performance",
 "type": "PERFORMANCE",
 "presenterType": "CUSTOM",
 "presenterName": "The Sauti Sol Band",
 "presenterTitle": "Headline Act",
 "presenterBio": "East Africa's most celebrated group."
 }
 ]
 }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 agenda 
 array 
 Yes 
 Full agenda list (by day) 
 Empty array is valid 
 
 
 agenda[].dayNumber 
 integer 
 Yes 
 Day number (1-based) 
 — 
 
 
 agenda[].date 
 string 
 No 
 Date string ( YYYY-MM-DD ) 
 — 
 
 
 agenda[].sessions 
 array 
 Yes 
 Sessions for this day 
 — 
 
 
 sessions[].startTime 
 string 
 Yes 
 Session start ( HH:mm ) 
 — 
 
 
 sessions[].endTime 
 string 
 Yes 
 Session end ( HH:mm ) 
 — 
 
 
 sessions[].title 
 string 
 Yes 
 Session name 
 — 
 
 
 sessions[].description 
 string 
 No 
 Session description 
 — 
 
 
 sessions[].type 
 string 
 No 
 Session type 
 Enum: GENERAL , PERFORMANCE , CEREMONY , PANEL , WORKSHOP , NETWORKING , MEAL , BREAK 
 
 
 sessions[].location 
 string 
 No 
 Sub-location within venue 
 — 
 
 
 sessions[].presenterType 
 string 
 No 
 Presenter source 
 Enum: PLATFORM_USER , CUSTOM 
 
 
 sessions[].presenterId 
 UUID 
 Conditional 
 Platform user ID 
 Required when presenterType=PLATFORM_USER 
 
 
 sessions[].presenterName 
 string 
 Conditional 
 Presenter name 
 Required when presenterType=CUSTOM ; auto-set when PLATFORM_USER 
 
 
 sessions[].presenterTitle 
 string 
 No 
 Professional title 
 Auto-enriched for platform users 
 
 
 sessions[].presenterBio 
 string 
 No 
 Biography 
 Auto-enriched for platform users 
 
 
 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 
 
 
 
 
 12. Attach Product to Event 
 Purpose : Links an existing active product from the PRODUCT domain to an event. Allowed on both DRAFT and PUBLISHED events. Marks the LINKS stage as completed. 
 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 
 
 
 
 
 13. Remove Product from Event 
 Purpose : Detaches a previously linked product from the event. Allowed on both DRAFT and PUBLISHED events. 
 Endpoint : DELETE /api/v1/e-events/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 
 
 
 
 
 14. Attach Shop to Event 
 Purpose : Links an existing active shop from the PRODUCT domain to an event. Allowed on both DRAFT and PUBLISHED events. Marks the LINKS stage as completed. 
 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 
 
 
 
 
 15. Remove Shop from Event 
 Purpose : Detaches a previously linked shop from the event. Allowed on both DRAFT and PUBLISHED events. 
 Endpoint : DELETE /api/v1/e-events/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 13 (401, 403, 404). 
 
 16. Publish Event 
 Purpose : Transitions a draft event to PUBLISHED status. This is the final step of the creation workflow. The system validates all required stages are complete, performs a duplicate event check against existing published events, generates an RSA key pair used for secure ticket QR code signing, and auto-derives the ctaLabel from ticket pricing if the organizer has not set one. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/publish 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to publish 
 Must be a valid UUID 
 
 
 
 
 Pre-publish checklist (all must pass or the request is rejected with 422 ): 
 
 BASIC_INFO stage completed ✓ 
 SCHEDULE stage completed (at least one day, not in the past) ✓ 
 LOCATION_DETAILS stage completed ✓ 
 At least one active ticket exists for the event ✓ 
 Event start date is not in the past ✓ 
 No duplicate event detected with ≥85% similarity score to another organizer's public event ✓ 
 
 
 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 no active tickets exist 
 
 
 500 
 RSA key generation failed 
 
 
 
 
 17. Unpublish Event 
 Purpose : Reverts a PUBLISHED event back to DRAFT status. Only allowed if zero tickets have been sold across all ticket types. If tickets have been sold, the organizer must cancel the event instead. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/unpublish 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to unpublish 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Event unpublished successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "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 
 
 
 
 
 18. Cancel Event 
 Purpose : Cancels an event. Can be triggered from any non-terminal status ( DRAFT , PUBLISHED , HAPPENING ). This action is irreversible. If the event was published and tickets were sold, a bulk refund process is triggered. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/cancel 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to cancel 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Event cancelled successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "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 
 
 
 
 
 19. Update Published Event — Basic Info 
 Purpose : Updates the description, media, and/or CTA label of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/basic-info 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "description": "Updated: The biggest jazz event in East Africa. Now featuring 35 artists across 3 stages.",
 "ctaLabel": "Get Tickets Now",
 "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 
 
 
 ctaLabel 
 string 
 No 
 CTA button label override 
 Max: 50 characters. If provided and non-blank → saved as-is. If omitted or null → re-derived from current ticket pricing 
 
 
 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": "Event info updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found 
 
 
 400 
 Event is not in PUBLISHED status 
 
 
 422 
 Validation failure on provided fields 
 
 
 
 
 20. Update Published Event — Highlights 
 Purpose : Replaces the full list of highlights for a published event. Behavior is identical to the draft equivalent — sending an empty array clears all highlights. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/highlights 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event to update 
 Must be a valid UUID 
 
 
 
 Request Body : Same format as Endpoint 8 — Update Draft Highlights . 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Highlights updated successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found 
 
 
 400 
 Event is not PUBLISHED 
 
 
 
 
 21. Update Published Event — FAQs 
 Purpose : Replaces the full list of FAQs for a published event. Sending an empty array clears all FAQs. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/faqs 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event to update 
 Must be a valid UUID 
 
 
 
 Request Body : Same format as Endpoint 9 — Update Draft FAQs . 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "FAQs updated successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Possible Error Responses : Same as Endpoint 20 (401, 403, 404, 400). 
 
 22. Update Published Event — Lineup 
 Purpose : Replaces the full event lineup on a published event. Supports the same platform user enrichment as the draft equivalent. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/lineup 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event to update 
 Must be a valid UUID 
 
 
 
 Request Body : Same format as Endpoint 10 — Update Draft Lineup . 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Lineup updated successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found, or a userId in lineup does not exist 
 
 
 400 
 Event is not PUBLISHED 
 
 
 
 
 23. Update Published Event — Agenda 
 Purpose : Replaces the full event agenda on a published event. Supports the same platform user enrichment as the draft equivalent. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/agenda 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event to update 
 Must be a valid UUID 
 
 
 
 Request Body : Same format as Endpoint 11 — Update Draft Agenda . 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Agenda updated successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found, or a presenterId does not exist 
 
 
 400 
 Event is not PUBLISHED 
 
 
 
 
 24. Reveal Location (TBA → Actual) 
 Purpose : Reveals the actual location for an event that was originally published with eventFormat=TBA . The new format cannot be TBA . Required fields depend on the new format chosen. 
 Endpoint : PATCH /api/v1/e-events/{eventId}/published/reveal-location 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published TBA event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample (revealing as IN_PERSON): 
 {
 "eventFormat": "IN_PERSON",
 "venue": {
 "name": "Mlimani City Arena",
 "address": "Sam Nujoma Road, Dar es Salaam",
 "coordinates": {
 "latitude": -6.7724,
 "longitude": 39.2083
 }
 }
}
 
 Request JSON Sample (revealing as ONLINE): 
 {
 "eventFormat": "ONLINE",
 "virtualDetails": {
 "meetingLink": "https://zoom.us/j/123456789",
 "meetingId": "123 456 789",
 "passcode": "jazz2025"
 }
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventFormat 
 string 
 Yes 
 The actual event format 
 Enum: IN_PERSON , ONLINE , HYBRID — cannot be TBA 
 
 
 venue 
 object 
 Conditional 
 Physical venue details 
 Required when new format is IN_PERSON or HYBRID 
 
 
 venue.name 
 string 
 Yes (if venue) 
 Venue name 
 Max: 200 characters 
 
 
 venue.address 
 string 
 No 
 Full address 
 Max: 500 characters 
 
 
 venue.coordinates.latitude 
 BigDecimal 
 No 
 GPS latitude 
 — 
 
 
 venue.coordinates.longitude 
 BigDecimal 
 No 
 GPS longitude 
 — 
 
 
 virtualDetails 
 object 
 Conditional 
 Virtual meeting details 
 Required when new format is ONLINE or HYBRID 
 
 
 virtualDetails.meetingLink 
 string 
 Yes (if virtualDetails) 
 Meeting URL 
 Max: 500 characters 
 
 
 virtualDetails.meetingId 
 string 
 No 
 Platform meeting ID 
 Max: 100 characters 
 
 
 virtualDetails.passcode 
 string 
 No 
 Meeting passcode 
 Max: 100 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Location revealed successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found 
 
 
 400 
 Event is not PUBLISHED, current format is not TBA, or new format is TBA 
 
 
 422 
 Missing required venue/virtual fields for the chosen format 
 
 
 
 
 25. Get Event by ID 
 Purpose : Retrieves full event details. Published events are publicly accessible without authentication. Draft events can only be viewed by their organizer. 
 Endpoint : GET /api/v1/e-events/{eventId} 
 Access Level : 🌐 Public (for PUBLISHED events) | 🔒 Protected (for DRAFT events — organizer only) 
 Authentication : Bearer Token (required only for DRAFT access) 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Conditional 
 Required when accessing a DRAFT event 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to retrieve 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Event retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full EventResponse object" }
}
 
 Success Response Fields : data is a full EventResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 403 
 Attempting to access a DRAFT event without being its organizer 
 
 
 404 
 Event not found or is soft-deleted 
 
 
 
 
 26. Get My Events 
 Purpose : Returns a paginated list of all events (any status) created by the authenticated organizer. 
 Endpoint : GET /api/v1/e-events/my-events 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Events retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ EventSummaryResponse objects ]" ],
 "totalElements": 12,
 "totalPages": 2
 }
}
 
 Success Response Fields : data.content[] contains EventSummaryResponse objects. Pagination follows Standard Paginated Response Wrapper . 
 Possible Error Responses : 401 — see Shared Error: 401 . 
 
 27. Get My Events by Status 
 Purpose : Returns a paginated list of the authenticated organizer's events filtered by a specific status. 
 Endpoint : GET /api/v1/e-events/my-events/status/{status} 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 status 
 string 
 Yes 
 Event status filter 
 Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Events retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ EventSummaryResponse objects ]" ],
 "totalElements": 5,
 "totalPages": 1
 }
}
 
 Success Response Fields : Same as Endpoint 26. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 400 
 Invalid status enum value 
 
 
 
 
 28. Get Events Feed 
 Purpose : Returns a paginated list of all published events for the public discovery feed. Results are ordered by creation date descending. This endpoint will eventually incorporate a recommendation algorithm. 
 Endpoint : GET /api/v1/e-events/events-feed 
 Access Level : 🌐 Public 
 Authentication : None required 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Events feed retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ EventSummaryResponse objects — PUBLISHED only ]" ],
 "totalElements": 87,
 "totalPages": 9
 }
}
 
 Success Response Fields : data.content[] contains EventSummaryResponse objects (PUBLISHED status only). 
 Possible Error Responses : None expected (no authentication required). 
 
 29. Search Events 
 Purpose : Full-text search across published event titles. Results are ordered by start date ascending. 
 Endpoint : GET /api/v1/e-events/search 
 Access Level : 🌐 Public 
 Authentication : None required 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 query 
 string 
 Yes 
 Search keyword(s) 
 Non-empty string 
 — 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Search results retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ EventSummaryResponse objects ]" ],
 "totalElements": 4,
 "totalPages": 1
 }
}
 
 Success Response Fields : data.content[] contains EventSummaryResponse objects. 
 Possible Error Responses : None expected for normal queries (empty results return an empty content array, not a 404). 
 
 30. Filter Events by Date Range 
 Purpose : Returns published events whose schedule overlaps with the provided date range. An event is included if it starts before endDate AND ends after startDate (overlap logic, not exact range match). 
 Endpoint : GET /api/v1/e-events/filter/date 
 Access Level : 🌐 Public 
 Authentication : None required 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 startDate 
 ZonedDateTime 
 Yes 
 Range start 
 ISO 8601 with offset (e.g., 2025-07-01T00:00:00+03:00 ) 
 — 
 
 
 endDate 
 ZonedDateTime 
 Yes 
 Range end 
 ISO 8601 with offset; must be after startDate 
 — 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Filtered events retrieved successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ EventSummaryResponse objects ]" ],
 "totalElements": 7,
 "totalPages": 1
 }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 400 
 startDate is after endDate 
 
 
 
 
 31. Search and Filter Events (Combined) 
 Purpose : Combines keyword search with optional date range filtering in a single call. All parameters are optional — calling with no parameters is equivalent to getting the full published feed. 
 Endpoint : GET /api/v1/e-events/filter 
 Access Level : 🌐 Public 
 Authentication : None required 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 query 
 string 
 No 
 Title keyword search 
 — 
 — 
 
 
 startDate 
 ZonedDateTime 
 No 
 Date range start 
 ISO 8601 with offset 
 — 
 
 
 endDate 
 ZonedDateTime 
 No 
 Date range end 
 ISO 8601 with offset; must be after startDate if both provided 
 — 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 400 
 startDate is after endDate (when both are provided) 
 
 
 
 
 32. Search My Events (Organizer) 
 Purpose : Allows an organizer to search and filter within their own events across all statuses. Supports keyword, status filter, and date range simultaneously. 
 Endpoint : GET /api/v1/e-events/my-events/search 
 Access Level : 🔒 Protected (Organizer — own events only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 query 
 string 
 No 
 Keyword search on title 
 — 
 — 
 
 
 status 
 string 
 No 
 Filter by status 
 Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED 
 — 
 
 
 startDate 
 ZonedDateTime 
 No 
 Date range start 
 ISO 8601 with offset 
 — 
 
 
 endDate 
 ZonedDateTime 
 No 
 Date range end 
 ISO 8601 with offset; must be after startDate if both provided 
 — 
 
 
 page 
 integer 
 No 
 Page number (1-based) 
 Min: 1 
 1 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 10 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 400 
 startDate is after endDate 
 
 
 
 
 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 : 
 
 Tickets can be created while the event is in DRAFT or PUBLISHED status. Full edits (name, price, type, etc.) are only allowed in DRAFT . After publishing, use the dedicated published-ticket endpoint for limited updates. 
 For HYBRID events, you must create at least one IN_PERSON ticket and one ONLINE ticket before the event can be published. 
 Ticket names must be unique per event per attendance mode — you cannot have two IN_PERSON tickets both named "VIP Pass" on the same event. 
 DONATION tickets are restricted to ONLINE_ONLY sales channel and a maximum of 1 ticket per order and per user. They have no fixed price — the buyer freely chooses their donation amount at checkout. 
 The ticket sales window must fall within the event's registration window . Sales cannot start before registration opens or end after registration closes. 
 The minimum gap between salesStartDateTime and salesEndDateTime is 30 minutes . 
 Soft deletion is used — a ticket can only be deleted if zero tickets have been sold. Otherwise, close it using the status endpoint. 
 All datetimes must be in ISO 8601 / ZonedDateTime format (e.g. 2025-08-10T09:00:00+03:00 ). 
 
 
 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: 
 
 salesStartDateTime cannot be in the past 
 salesEndDateTime cannot be in the past 
 salesEndDateTime must be after salesStartDateTime 
 Minimum gap between sales start and end is 30 minutes 
 salesStartDateTime must be on or after registrationOpensAt 
 salesStartDateTime cannot be after registrationClosesAt 
 salesEndDateTime cannot be after registrationClosesAt 
 Neither date can be after eventEndDateTime 
 
 
 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 
 
 GET — Green ( #28a745 ) — Safe, read-only operations 
 POST — Blue ( #007bff ) — Create new resources 
 PUT — Yellow ( #ffc107 , black text) — Replace entire resource 
 PATCH — Orange ( #fd7e14 ) — Partial update 
 DELETE — Red ( #dc3545 ) — Remove resource 
 
 
 Enum Reference 
 TicketPricingType 
 
 
 
 Value 
 Description 
 
 
 
 
 PAID 
 Standard paid ticket. Price must be greater than 0.00 
 
 
 FREE 
 Free entry. Price must be exactly 0.00 
 
 
 DONATION 
 Supporter ticket. No fixed price — buyer freely enters their donation amount at checkout. Restricted to ONLINE_ONLY channel, max 1 per order and per user. price is null in responses 
 
 
 
 SalesChannel 
 
 
 
 Value 
 Description 
 
 
 
 
 EVERYWHERE 
 Available both online and at the door 
 
 
 ONLINE_ONLY 
 Available for purchase online only 
 
 
 AT_DOOR_ONLY 
 Available for purchase at the venue door only 
 
 
 
 AttendanceMode 
 
 
 
 Value 
 Description 
 
 
 
 
 IN_PERSON 
 Ticket grants physical entry to the venue 
 
 
 ONLINE 
 Ticket grants access to the online/virtual stream 
 
 
 
 
 For IN_PERSON events, only IN_PERSON tickets are allowed. For ONLINE events, only ONLINE tickets are allowed. For HYBRID events, both are permitted and at least one of each is required before publishing. 
 
 TicketVisibility 
 
 
 
 Value 
 Description 
 
 
 
 
 VISIBLE 
 Always shown to the public 
 
 
 HIDDEN 
 Never shown to buyers (organizer use only) 
 
 
 HIDDEN_WHEN_NOT_ON_SALE 
 Only visible while the ticket is actively on sale 
 
 
 CUSTOM_SCHEDULE 
 Shown only within a defined date/time window. Requires visibilityStartDate and visibilityEndDate 
 
 
 
 TicketStatus 
 
 
 
 Value 
 Description 
 
 
 
 
 ACTIVE 
 Ticket is live and available for purchase 
 
 
 INACTIVE 
 Temporarily paused. Organizer can reactivate 
 
 
 CLOSED 
 Permanently stopped. Cannot be reopened 
 
 
 SOLD_OUT 
 System-managed. Set automatically when ticketsSold >= totalTickets . Reverts to ACTIVE if capacity is increased 
 
 
 DELETED 
 Soft-deleted. Only possible if zero tickets were sold. Use the Delete endpoint — cannot be set via status update 
 
 
 
 
 Endpoints 
 
 1. Create Ticket 
 Purpose : Creates a new ticket type for a specific event. The event must be in DRAFT or PUBLISHED status. The authenticated user must be the event organizer. 
 Endpoint : POST {base_url}/{eventId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to add the ticket to 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 name 
 string 
 Yes 
 Ticket name (e.g. "VIP Pass", "Early Bird") 
 Min: 2, Max: 100 characters. Must be unique per event per attendance mode 
 
 
 description 
 string 
 No 
 Optional description of what the ticket includes 
 Max: 500 characters 
 
 
 price 
 decimal 
 Conditional 
 Ticket price. Use 0.00 for FREE tickets. Omit or send 0.00 for DONATION tickets 
 Min: 0.00. PAID → must be > 0.00. FREE → must be 0.00. DONATION → ignored 
 
 
 ticketPricingType 
 string 
 Yes 
 Pricing model 
 Enum: PAID , FREE , DONATION 
 
 
 salesChannel 
 string 
 Yes 
 Where the ticket can be purchased. DONATION must use ONLINE_ONLY 
 Enum: EVERYWHERE , ONLINE_ONLY , AT_DOOR_ONLY . Defaults to EVERYWHERE 
 
 
 totalQuantity 
 integer 
 Yes 
 Total number of tickets available 
 Min: 1, Max: 1,000,000 
 
 
 salesStartDateTime 
 datetime 
 No 
 When sales open. Must be within registration window 
 ISO 8601 ZonedDateTime. Cannot be before registrationOpensAt or after registrationClosesAt 
 
 
 salesEndDateTime 
 datetime 
 No 
 When sales close. Must be within registration window 
 ISO 8601 ZonedDateTime. Must be after salesStartDateTime with at least 30 minutes gap 
 
 
 minQuantityPerOrder 
 integer 
 No 
 Minimum tickets per order 
 Min: 1. Defaults to 1 
 
 
 maxQuantityPerOrder 
 integer 
 No 
 Maximum tickets per order. DONATION tickets must be 1 
 Min: 1, Max: 100. Must be ≥ minQuantityPerOrder 
 
 
 maxQuantityPerUser 
 integer 
 No 
 Maximum tickets a single user can purchase across all orders. DONATION tickets must be 1 
 Min: 1, Max: 1000. Must be ≥ maxQuantityPerOrder 
 
 
 visibility 
 string 
 Yes 
 Controls whether the ticket is shown to buyers 
 Enum: VISIBLE , HIDDEN , HIDDEN_WHEN_NOT_ON_SALE , CUSTOM_SCHEDULE . Defaults to VISIBLE 
 
 
 visibilityStartDate 
 datetime 
 No 
 When the ticket becomes visible. Required if visibility is CUSTOM_SCHEDULE 
 ISO 8601 ZonedDateTime 
 
 
 visibilityEndDate 
 datetime 
 No 
 When the ticket stops being visible. Required if visibility is CUSTOM_SCHEDULE 
 ISO 8601 ZonedDateTime. Must be after visibilityStartDate 
 
 
 attendanceMode 
 string 
 Yes 
 Whether this ticket is for physical or online attendance 
 Enum: IN_PERSON , ONLINE . Must match the event format 
 
 
 inclusiveItems 
 array of strings 
 No 
 List of perks included with this ticket (e.g. "Free T-Shirt", "Meet & Greet") 
 Max: 50 items. Each item: max 200 characters, cannot be blank 
 
 
 
 Request JSON Sample (PAID) : 
 {
 "name": "VIP Pass",
 "description": "Full weekend access with backstage entry and a complimentary gift bag.",
 "price": 150.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "totalQuantity": 200,
 "salesStartDateTime": "2026-03-18T08:00:00+03:00",
 "salesEndDateTime": "2026-04-17T23:59:00+03:00",
 "minQuantityPerOrder": 1,
 "maxQuantityPerOrder": 4,
 "maxQuantityPerUser": 4,
 "visibility": "VISIBLE",
 "attendanceMode": "IN_PERSON",
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating"
 ]
}
 
 Request JSON Sample (DONATION) : 
 {
 "name": "Support the Artist",
 "description": "Show your support — donate any amount you choose at checkout.",
 "price": 0.00,
 "ticketPricingType": "DONATION",
 "salesChannel": "ONLINE_ONLY",
 "totalQuantity": 500,
 "salesStartDateTime": "2026-03-18T08:00:00+03:00",
 "salesEndDateTime": "2026-04-17T23:59:00+03:00",
 "minQuantityPerOrder": 1,
 "maxQuantityPerOrder": 1,
 "maxQuantityPerUser": 1,
 "visibility": "VISIBLE",
 "attendanceMode": "IN_PERSON"
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Ticket created successfully",
 "action_time": "2025-02-18T10:30:45",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
 "name": "VIP Pass",
 "description": "Full weekend access with backstage entry and a complimentary gift bag.",
 "price": 150.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "totalTickets": 200,
 "ticketsSold": 0,
 "ticketsRemaining": 200,
 "ticketsAvailable": 200,
 "isSoldOut": false,
 "salesStartDateTime": "2026-03-18T05:00:00Z",
 "salesEndDateTime": "2026-04-17T20:59:00Z",
 "isOnSale": false,
 "minQuantityPerOrder": 1,
 "maxQuantityPerOrder": 4,
 "maxQuantityPerUser": 4,
 "visibility": "VISIBLE",
 "visibilityStartDate": null,
 "visibilityEndDate": null,
 "isCurrentlyVisible": true,
 "attendanceMode": "IN_PERSON",
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating"
 ],
 "status": "ACTIVE",
 "saleStatusMessage": "On sale until Apr 17, 2026",
 "createdAt": "2025-02-18T10:30:45+03:00",
 "updatedAt": null,
 "createdBy": "john_organizer",
 "updatedBy": null
 }
}
 
 
 Note on DONATION response : For DONATION tickets, the price field is null in the response. The buyer enters their own amount at checkout. 
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 id 
 Unique identifier for this ticket type 
 
 
 eventId 
 The event this ticket belongs to 
 
 
 name 
 Ticket name 
 
 
 description 
 Ticket description 
 
 
 price 
 Ticket price. null for DONATION tickets 
 
 
 ticketPricingType 
 Pricing model: PAID , FREE , or DONATION 
 
 
 salesChannel 
 Where the ticket can be purchased 
 
 
 totalTickets 
 Total number of tickets created 
 
 
 ticketsSold 
 Number of tickets purchased so far 
 
 
 ticketsRemaining 
 totalTickets - ticketsSold 
 
 
 ticketsAvailable 
 Same as ticketsRemaining 
 
 
 isSoldOut 
 true if ticketsSold >= totalTickets 
 
 
 salesStartDateTime 
 When ticket sales open 
 
 
 salesEndDateTime 
 When ticket sales close 
 
 
 isOnSale 
 true if ticket is ACTIVE and currently within the sales window 
 
 
 saleStatusMessage 
 Human-readable message describing the current sale state (e.g. "On sale until Apr 17, 2026" , "Sales start Mar 18, 2026" , "Sales ended" , "Sold out" ) 
 
 
 minQuantityPerOrder 
 Minimum per order 
 
 
 maxQuantityPerOrder 
 Maximum per order ( null = no limit) 
 
 
 maxQuantityPerUser 
 Maximum per user across all orders ( null = no limit) 
 
 
 visibility 
 Visibility setting 
 
 
 visibilityStartDate 
 Start of custom visibility window 
 
 
 visibilityEndDate 
 End of custom visibility window 
 
 
 isCurrentlyVisible 
 Whether the ticket is currently visible to buyers 
 
 
 attendanceMode 
 IN_PERSON or ONLINE 
 
 
 inclusiveItems 
 List of perks included with the ticket 
 
 
 status 
 Ticket status: ACTIVE , INACTIVE , CLOSED , SOLD_OUT , DELETED 
 
 
 createdAt 
 Timestamp when the ticket was created 
 
 
 updatedAt 
 Timestamp of last update ( null if never updated) 
 
 
 createdBy 
 Username of the organizer who created the ticket 
 
 
 updatedBy 
 Username of last person who updated the ticket 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Authenticated user is not the event organizer 
 
 
 404 
 Event not found 
 
 
 400 
 Event is not in DRAFT or PUBLISHED status 
 
 
 400 
 Ticket name already exists for this event and attendance mode 
 
 
 422 
 Validation errors (missing required fields, invalid price, sales window outside registration window, gap less than 30 minutes, etc.) 
 
 
 
 Error Response Examples : 
 Event in invalid status (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Tickets can only be created for DRAFT or PUBLISHED events. Current status: CANCELLED",
 "action_time": "2025-02-18T10:30:45",
 "data": "Tickets can only be created for DRAFT or PUBLISHED events. Current status: CANCELLED"
}
 
 Duplicate Ticket Name (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "A ticket with name 'VIP Pass' and attendance mode 'IN_PERSON' already exists for this event",
 "action_time": "2025-02-18T10:30:45",
 "data": "A ticket with name 'VIP Pass' and attendance mode 'IN_PERSON' already exists for this event"
}
 
 
 2. Update Ticket 
 Purpose : Updates the full details of an existing ticket type. The event must still be in DRAFT status. All fields are optional — only the fields you send will be updated. 
 Endpoint : PUT {base_url}/{ticketId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ticketId 
 UUID 
 Yes 
 The ticket to update 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 name 
 string 
 No 
 Updated ticket name 
 Min: 2, Max: 100 characters. Must remain unique per event per attendance mode 
 
 
 description 
 string 
 No 
 Updated description 
 Max: 500 characters 
 
 
 price 
 decimal 
 No 
 Updated price. Ignored for DONATION tickets 
 Min: 0.00. Must be consistent with ticketPricingType 
 
 
 ticketPricingType 
 string 
 No 
 Updated pricing model 
 Enum: PAID , FREE , DONATION 
 
 
 salesChannel 
 string 
 No 
 Updated sales channel. DONATION tickets must be ONLINE_ONLY 
 Enum: EVERYWHERE , ONLINE_ONLY , AT_DOOR_ONLY 
 
 
 totalQuantity 
 integer 
 No 
 Updated total capacity 
 Min: 1, Max: 1,000,000 
 
 
 salesStartDateTime 
 datetime 
 No 
 Updated sales open time 
 ISO 8601 ZonedDateTime. Cannot be before registrationOpensAt 
 
 
 salesEndDateTime 
 datetime 
 No 
 Updated sales close time 
 ISO 8601 ZonedDateTime. At least 30 minutes after salesStartDateTime 
 
 
 minQuantityPerOrder 
 integer 
 No 
 Updated minimum per order 
 Min: 1 
 
 
 maxQuantityPerOrder 
 integer 
 No 
 Updated maximum per order. DONATION must be 1 
 Min: 1, Max: 100 
 
 
 maxQuantityPerUser 
 integer 
 No 
 Updated maximum per user. DONATION must be 1 
 Min: 1, Max: 1000 
 
 
 attendanceMode 
 string 
 No 
 Updated attendance mode 
 Enum: IN_PERSON , ONLINE . Must match event format rules 
 
 
 inclusiveItems 
 array of strings 
 No 
 Full replacement list of inclusive perks 
 Max: 50 items. Each: max 200 characters, cannot be blank 
 
 
 visibility 
 string 
 No 
 Updated visibility 
 Enum: VISIBLE , HIDDEN , HIDDEN_WHEN_NOT_ON_SALE , CUSTOM_SCHEDULE 
 
 
 visibilityStartDate 
 datetime 
 No 
 Updated visibility start 
 Required if changing to CUSTOM_SCHEDULE 
 
 
 visibilityEndDate 
 datetime 
 No 
 Updated visibility end 
 Required if changing to CUSTOM_SCHEDULE . Must be after start 
 
 
 
 Request JSON Sample : 
 {
 "name": "VIP Weekend Pass",
 "price": 175.00,
 "maxQuantityPerOrder": 2,
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating",
 "Artist meet & greet"
 ]
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket updated successfully",
 "action_time": "2025-02-18T11:00:00",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "name": "VIP Weekend Pass",
 "price": 175.00,
 "maxQuantityPerOrder": 2,
 "status": "ACTIVE",
 "updatedAt": "2025-02-18T11:00:00+03:00",
 "updatedBy": "john_organizer"
 }
}
 
 Success Response Fields : Same as Create Ticket response fields . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 Event is not in DRAFT status 
 
 
 400 
 Updated name conflicts with an existing ticket on the same event 
 
 
 422 
 Validation errors (invalid price, quantity inconsistencies, sales window violations, etc.) 
 
 
 
 
 3. Get All Tickets for Event 
 Purpose : Retrieves a lightweight summary list of all active (non-deleted) tickets for a given event, ordered by creation date ascending. 
 Endpoint : GET {base_url}/{eventId} 
 Access Level : 🌐 Public 
 Authentication : None required 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to retrieve tickets for 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Tickets retrieved successfully",
 "action_time": "2025-02-18T10:30:45",
 "data": [
 {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "name": "General Admission",
 "price": 25.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "visibility": "VISIBLE",
 "totalTickets": 1000,
 "ticketsSold": 342,
 "ticketsAvailable": 658,
 "isSoldOut": false,
 "attendanceMode": "IN_PERSON",
 "status": "ACTIVE",
 "isOnSale": true
 },
 {
 "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
 "name": "VIP Pass",
 "price": 150.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "visibility": "VISIBLE",
 "totalTickets": 200,
 "ticketsSold": 200,
 "ticketsAvailable": 0,
 "isSoldOut": true,
 "attendanceMode": "IN_PERSON",
 "status": "SOLD_OUT",
 "isOnSale": false
 }
 ]
}
 
 Success Response Fields (per item): 
 
 
 
 Field 
 Description 
 
 
 
 
 id 
 Unique identifier for the ticket type 
 
 
 name 
 Ticket name 
 
 
 price 
 Ticket price. null for DONATION tickets 
 
 
 ticketPricingType 
 Pricing model 
 
 
 salesChannel 
 Where it can be purchased 
 
 
 visibility 
 Visibility setting 
 
 
 totalTickets 
 Total quantity created 
 
 
 ticketsSold 
 Quantity sold so far 
 
 
 ticketsAvailable 
 Quantity still available 
 
 
 isSoldOut 
 Whether the ticket is sold out 
 
 
 attendanceMode 
 IN_PERSON or ONLINE 
 
 
 status 
 Current ticket status 
 
 
 isOnSale 
 Whether the ticket is currently purchasable 
 
 
 saleStatusMessage 
 Human-readable message describing the current sale state 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 404 
 Event not found 
 
 
 
 
 4. Get Ticket by ID 
 Purpose : Retrieves the full details of a single ticket type by its ID. 
 Endpoint : GET {base_url}/{eventId}/{ticketId} 
 Access Level : 🌐 Public 
 Authentication : None required 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event the ticket belongs to 
 Must be a valid UUID 
 
 
 ticketId 
 UUID 
 Yes 
 The specific ticket to retrieve 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket retrieved successfully",
 "action_time": "2025-02-18T10:30:45",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
 "name": "VIP Pass",
 "description": "Full weekend access with backstage entry and a complimentary gift bag.",
 "price": 150.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "totalTickets": 200,
 "ticketsSold": 45,
 "ticketsRemaining": 155,
 "ticketsAvailable": 155,
 "isSoldOut": false,
 "salesStartDateTime": "2026-03-18T05:00:00Z",
 "salesEndDateTime": "2026-04-17T20:59:00Z",
 "isOnSale": true,
 "saleStatusMessage": "On sale until Apr 17, 2026",
 "minQuantityPerOrder": 1,
 "maxQuantityPerOrder": 4,
 "maxQuantityPerUser": 4,
 "visibility": "VISIBLE",
 "visibilityStartDate": null,
 "visibilityEndDate": null,
 "isCurrentlyVisible": true,
 "attendanceMode": "IN_PERSON",
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating"
 ],
 "status": "ACTIVE",
 "createdAt": "2025-02-18T10:30:45+03:00",
 "updatedAt": "2025-02-18T11:00:00+03:00",
 "createdBy": "john_organizer",
 "updatedBy": "john_organizer"
 }
}
 
 Success Response Fields : Same as Create Ticket response fields . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 404 
 Event not found or ticket not found 
 
 
 
 
 5. Update Ticket Capacity 
 Purpose : Updates the total quantity (capacity) of a ticket. Allowed on both DRAFT and PUBLISHED events. The new capacity must be greater than or equal to the number of tickets already sold. 
 Endpoint : PATCH {base_url}/{eventId}/{ticketId}/capacity 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 ticketId 
 UUID 
 Yes 
 The ticket to update capacity for 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 newTotalQuantity 
 integer 
 Yes 
 The new total capacity 
 Min: 1, Max: 1,000,000. Must be ≥ ticketsSold 
 
 
 
 Request JSON Sample : 
 {
 "newTotalQuantity": 300
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket capacity updated successfully",
 "action_time": "2025-02-18T12:00:00",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "name": "VIP Pass",
 "totalTickets": 300,
 "ticketsSold": 200,
 "ticketsRemaining": 100,
 "ticketsAvailable": 100,
 "isSoldOut": false,
 "status": "ACTIVE",
 "updatedAt": "2025-02-18T12:00:00+03:00",
 "updatedBy": "john_organizer"
 }
}
 
 
 Note: If a ticket was previously SOLD_OUT and the new capacity exceeds tickets sold, the status is automatically reset to ACTIVE . 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 New capacity is less than the number of tickets already sold 
 
 
 422 
 newTotalQuantity is missing or below minimum 
 
 
 
 Capacity Below Sold Count (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Cannot reduce capacity to 100 because 200 tickets have already been sold",
 "action_time": "2025-02-18T12:00:00",
 "data": "Cannot reduce capacity to 100 because 200 tickets have already been sold"
}
 
 
 6. Update Ticket Status 
 Purpose : Manually changes the status of a ticket type. Use this to pause sales ( INACTIVE ), permanently stop sales ( CLOSED ), or reactivate a paused ticket ( ACTIVE ). Works on both DRAFT and PUBLISHED events. The system automatically manages SOLD_OUT status — it cannot be set manually. 
 Endpoint : PATCH {base_url}/{eventId}/{ticketId}/status 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 ticketId 
 UUID 
 Yes 
 The ticket to update status for 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 status 
 string 
 Yes 
 The new status to set 
 Enum: ACTIVE , INACTIVE , CLOSED . Cannot set SOLD_OUT or DELETED manually 
 
 
 
 Status Transition Rules : 
 
 
 
 Current Status 
 Allowed Transitions 
 Notes 
 
 
 
 
 ACTIVE 
 INACTIVE , CLOSED 
 Normal operations 
 
 
 INACTIVE 
 ACTIVE , CLOSED 
 Can be reactivated 
 
 
 SOLD_OUT 
 ACTIVE , CLOSED 
 ACTIVE only if capacity was increased first 
 
 
 CLOSED 
 None 
 Permanent. Cannot be changed 
 
 
 DELETED 
 None 
 Permanent. Cannot be changed 
 
 
 
 Request JSON Sample : 
 {
 "status": "INACTIVE"
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket status updated successfully",
 "action_time": "2025-02-18T13:00:00",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "name": "VIP Pass",
 "status": "INACTIVE",
 "isOnSale": false,
 "updatedAt": "2025-02-18T13:00:00+03:00",
 "updatedBy": "john_organizer"
 }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 Attempted to set SOLD_OUT or DELETED manually 
 
 
 400 
 Attempted to change status of a CLOSED or DELETED ticket 
 
 
 422 
 status field is missing 
 
 
 
 
 7. Delete Ticket 
 Purpose : Soft-deletes a ticket type. The ticket is marked as deleted and hidden from all listings. Deletion is only allowed if zero tickets have been sold. If tickets have already been sold, close the ticket using the status endpoint instead. 
 Endpoint : DELETE {base_url}/{eventId}/{ticketId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 ticketId 
 UUID 
 Yes 
 The ticket to delete 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket deleted successfully",
 "action_time": "2025-02-18T14:00:00",
 "data": null
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 Ticket cannot be deleted because tickets have already been sold 
 
 
 
 Tickets Already Sold (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Cannot delete ticket 'VIP Pass' because 45 tickets have been sold. You can close the ticket instead to stop sales.",
 "action_time": "2025-02-18T14:00:00",
 "data": "Cannot delete ticket 'VIP Pass' because 45 tickets have been sold. You can close the ticket instead to stop sales."
}
 
 
 8. Update Sales Window 
 Purpose : Updates the sales start and/or end datetime of a ticket on a PUBLISHED event. Both fields are optional — omitting one preserves its current value. All existing sales period rules apply (30-minute minimum gap, must fall within registration window, etc.). 
 Endpoint : PATCH {base_url}/{ticketId}/sales-window 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ticketId 
 UUID 
 Yes 
 The ticket to update the sales window for 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 salesStartDateTime 
 datetime 
 No 
 New sales open time 
 ISO 8601 ZonedDateTime. Must be on or after registrationOpensAt . Cannot be after registrationClosesAt 
 
 
 salesEndDateTime 
 datetime 
 No 
 New sales close time 
 ISO 8601 ZonedDateTime. Must be after salesStartDateTime with at least 30 minutes gap. Cannot be after registrationClosesAt 
 
 
 
 
 At least one of salesStartDateTime or salesEndDateTime must be provided. 
 
 Request JSON Sample : 
 {
 "salesEndDateTime": "2026-04-25T23:59:00+03:00"
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket sales window updated successfully",
 "action_time": "2026-02-20T10:00:00",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
 "name": "VIP Pass",
 "salesStartDateTime": "2026-03-18T05:00:00Z",
 "salesEndDateTime": "2026-04-25T20:59:00Z",
 "isOnSale": true,
 "status": "ACTIVE",
 "updatedAt": "2026-02-20T10:00:00+03:00",
 "updatedBy": "john_organizer"
 }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 Ticket is deleted or closed 
 
 
 422 
 Sales window violates registration window, gap less than 30 minutes, or no fields provided 
 
 
 
 Sales Window Outside Registration Window (422): 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Sales end date cannot be after registration closes (2026-04-17T20:59:00Z)",
 "action_time": "2026-02-20T10:00:00",
 "data": {
 "stage": "TICKETS",
 "message": "Sales end date cannot be after registration closes (2026-04-17T20:59:00Z)"
 }
}
 
 
 9. Update Published Ticket 
 Purpose : Performs a limited update on a ticket belonging to a PUBLISHED event. Only three fields are allowed: visibility (and its schedule dates), status (ACTIVE, INACTIVE, or CLOSED), and inclusiveItems . All other fields must be updated while the event is still in DRAFT. 
 Endpoint : PATCH {base_url}/{ticketId}/published 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ticketId 
 UUID 
 Yes 
 The published ticket to update 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 visibility 
 string 
 No 
 Updated visibility setting 
 Enum: VISIBLE , HIDDEN , HIDDEN_WHEN_NOT_ON_SALE , CUSTOM_SCHEDULE 
 
 
 visibilityStartDate 
 datetime 
 No 
 Start of custom visibility window 
 Required if visibility is CUSTOM_SCHEDULE . ISO 8601 ZonedDateTime 
 
 
 visibilityEndDate 
 datetime 
 No 
 End of custom visibility window 
 Required if visibility is CUSTOM_SCHEDULE . Must be after visibilityStartDate . ISO 8601 ZonedDateTime 
 
 
 status 
 string 
 No 
 Updated ticket status 
 Enum: ACTIVE , INACTIVE , CLOSED . Cannot set SOLD_OUT or DELETED 
 
 
 inclusiveItems 
 array of strings 
 No 
 Full replacement list of perks 
 Max: 50 items. Each: max 200 characters, cannot be blank 
 
 
 
 
 This endpoint is specifically for PUBLISHED events. For DRAFT events, use the full PUT /{ticketId} endpoint instead. 
 
 Request JSON Sample : 
 {
 "visibility": "CUSTOM_SCHEDULE",
 "visibilityStartDate": "2026-03-01T00:00:00+03:00",
 "visibilityEndDate": "2026-04-17T23:59:00+03:00",
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating",
 "Artist meet & greet",
 "Exclusive after-party entry"
 ]
}
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Ticket updated successfully",
 "action_time": "2026-02-20T10:00:00",
 "data": {
 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210",
 "name": "VIP Pass",
 "description": "Full weekend access with backstage entry and a complimentary gift bag.",
 "price": 150.00,
 "ticketPricingType": "PAID",
 "salesChannel": "EVERYWHERE",
 "totalTickets": 200,
 "ticketsSold": 87,
 "ticketsRemaining": 113,
 "ticketsAvailable": 113,
 "isSoldOut": false,
 "salesStartDateTime": "2026-03-18T05:00:00Z",
 "salesEndDateTime": "2026-04-17T20:59:00Z",
 "isOnSale": true,
 "minQuantityPerOrder": 1,
 "maxQuantityPerOrder": 4,
 "maxQuantityPerUser": 4,
 "visibility": "CUSTOM_SCHEDULE",
 "visibilityStartDate": "2026-03-01T00:00:00+03:00",
 "visibilityEndDate": "2026-04-17T23:59:00+03:00",
 "isCurrentlyVisible": false,
 "attendanceMode": "IN_PERSON",
 "inclusiveItems": [
 "Backstage access",
 "Complimentary gift bag",
 "Priority seating",
 "Artist meet & greet",
 "Exclusive after-party entry"
 ],
 "status": "ACTIVE",
 "createdAt": "2025-02-18T10:30:45+03:00",
 "updatedAt": "2026-02-20T10:00:00+03:00",
 "createdBy": "john_organizer",
 "updatedBy": "john_organizer"
 }
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Ticket not found 
 
 
 400 
 Event is not in PUBLISHED status — use the draft update endpoint instead 
 
 
 400 
 Ticket is deleted 
 
 
 422 
 Invalid visibility schedule dates, attempted to set SOLD_OUT or DELETED , or CUSTOM_SCHEDULE missing required date fields 
 
 
 
 Event Not Published (400): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "This endpoint is only for published events. Use the draft ticket update endpoint instead.",
 "action_time": "2026-02-20T10:00:00",
 "data": "This endpoint is only for published events. Use the draft ticket update endpoint instead."
}
 
 
 Quick Reference 
 Endpoint Summary 
 
 
 
 # 
 Method 
 Path 
 Description 
 Auth 
 Event Status 
 
 
 
 
 1 
 POST 
 /{eventId} 
 Create a ticket type 
 🔒 Organizer 
 DRAFT or PUBLISHED 
 
 
 2 
 PUT 
 /{ticketId} 
 Full update of ticket details 
 🔒 Organizer 
 DRAFT only 
 
 
 3 
 GET 
 /{eventId} 
 Get all tickets for an event 
 🌐 Public 
 Any 
 
 
 4 
 GET 
 /{eventId}/{ticketId} 
 Get a single ticket by ID 
 🌐 Public 
 Any 
 
 
 5 
 PATCH 
 /{eventId}/{ticketId}/capacity 
 Update ticket capacity 
 🔒 Organizer 
 DRAFT or PUBLISHED 
 
 
 6 
 PATCH 
 /{eventId}/{ticketId}/status 
 Update ticket status 
 🔒 Organizer 
 DRAFT or PUBLISHED 
 
 
 7 
 DELETE 
 /{eventId}/{ticketId} 
 Delete a ticket 
 🔒 Organizer 
 DRAFT or PUBLISHED (0 sold) 
 
 
 8 
 PATCH 
 /{ticketId}/sales-window 
 Update sales window dates 
 🔒 Organizer 
 PUBLISHED 
 
 
 9 
 PATCH 
 /{ticketId}/published 
 Limited update (visibility, status, perks) 
 🔒 Organizer 
 PUBLISHED only 
 
 
 
 Common HTTP Status Codes 
 
 
 
 Code 
 Meaning 
 
 
 
 
 200 OK 
 Successful GET, PATCH, PUT, or DELETE 
 
 
 201 Created 
 Successful POST (ticket created) 
 
 
 400 Bad Request 
 Business rule violated (wrong status, already sold, duplicate name) 
 
 
 401 Unauthorized 
 Missing or invalid token 
 
 
 403 Forbidden 
 Authenticated but not the event organizer 
 
 
 404 Not Found 
 Event or ticket does not exist 
 
 
 422 Unprocessable Entity 
 Validation errors on request fields 
 
 
 
 Business Rule Cheat Sheet 
 
 
 
 Rule 
 Detail 
 
 
 
 
 Create ticket 
 Allowed on DRAFT and PUBLISHED events 
 
 
 Full update ( PUT ) 
 DRAFT events only 
 
 
 Published update ( PATCH /published ) 
 PUBLISHED events only. Visibility, status, and inclusiveItems only 
 
 
 Sales window update 
 PUBLISHED events. Validates against registration window 
 
 
 Capacity update 
 DRAFT and PUBLISHED events 
 
 
 Status update 
 DRAFT and PUBLISHED events 
 
 
 FREE ticket 
 Price must be exactly 0.00 
 
 
 PAID ticket 
 Price must be greater than 0.00 
 
 
 DONATION ticket 
 ONLINE_ONLY channel. Max 1 per order and per user. No fixed price — buyer sets amount at checkout. price is null in response 
 
 
 Sales window 
 Must fall within the event's registration window 
 
 
 Sales period gap 
 Minimum 30 minutes between salesStartDateTime and salesEndDateTime 
 
 
 HYBRID event 
 Must have ≥ 1 IN_PERSON ticket and ≥ 1 ONLINE ticket to publish 
 
 
 Delete 
 Only allowed if ticketsSold = 0 
 
 
 SOLD_OUT 
 System-managed. Cannot be set manually. Auto-cleared when capacity is increased 
 
 
 CLOSED 
 Permanent. Cannot be reversed

Event Checkout & Payment API
Author : Josh S. Sakweli, Backend Lead Team
 Last Updated : 2026-05-23
 Version : v2.0 
 Base URL : https://api.nextgate.co.tz/api/v1 
 Short Description : The NextGate Checkout API manages the complete ticket purchasing lifecycle on the NextGate event platform. It supports two distinct checkout flows: online checkout for registered attendees and at-door ticket sales for event organizers and scanner devices. This API should be used by frontend clients, mobile applications, and authorized scanner hardware integrations. 
 Hints : 
 
 All monetary values are in TZS (Tanzanian Shilling) unless otherwise stated 
 Checkout sessions expire after 15 minutes for online checkout and 1 hour for at-door sales — always check expiresAt before processing payment 
 Bearer token authentication is required for all endpoints 
 Wallet balance is validated at session creation time for PAID tickets — if insufficient, the session is never created and a structured 422 response is returned with shortfall and recommendedTopUp so the frontend can direct the user to top up immediately 
 For FREE tickets, payment is auto-processed immediately upon session creation — no separate payment step is needed 
 For DONATION tickets, maximum 1 ticket per order and cannot be purchased for other attendees 
 Ticket holds are applied immediately on session creation; cancelling the session releases the hold 
 Scanner devices must have the SELL_TICKETS permission assigned and a valid deviceFingerprint registered before calling the scanner sale endpoint 
 
 
 Standard Response Format 
 All API responses follow a consistent structure using our Globe Response Builder pattern: 
 Success Response Structure 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Operation completed successfully",
 "action_time": "2025-09-23T10:30:45",
 "data": {}
}
 
 Error Response Structure 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Error description",
 "action_time": "2025-09-23T10:30:45",
 "data": "Error description"
}
 
 Insufficient Balance Error Structure (422) 
 Returned when wallet balance is insufficient at session creation time for a PAID ticket. No session is created. The data field carries rich balance details so the frontend can guide the user directly to top up. 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Insufficient wallet balance to complete checkout",
 "action_time": "2025-09-23T10:30:45",
 "data": {
 "walletBalance": 50000.00,
 "sessionTotal": 150000.00,
 "shortfall": 100000.00,
 "hasSufficientBalance": false,
 "recommendedTopUp": 100000.00,
 "pspMinimum": 500.00,
 "currency": "TZS"
 }
}
 
 
 
 
 Field 
 Description 
 
 
 
 
 walletBalance 
 Current wallet balance of the user in TZS 
 
 
 sessionTotal 
 Total amount required for this checkout 
 
 
 shortfall 
 Amount missing ( sessionTotal - walletBalance ) 
 
 
 hasSufficientBalance 
 Always false when this error is returned 
 
 
 recommendedTopUp 
 Suggested top-up amount — at minimum equals shortfall , rounded up to pspMinimum if needed 
 
 
 pspMinimum 
 Minimum top-up amount accepted by the payment provider 
 
 
 currency 
 Always TZS 
 
 
 
 
 Standard Response Fields 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 success 
 boolean 
 Always true for successful operations, false for errors 
 
 
 httpStatus 
 string 
 HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) 
 
 
 message 
 string 
 Human-readable message describing the operation result 
 
 
 action_time 
 string 
 ISO 8601 timestamp of when the response was generated 
 
 
 data 
 object/string 
 Response payload for success, error details for failures 
 
 
 
 
 HTTP Method Badge Standards 
 
 GET — Green: Safe, read-only operations 
 POST — Blue: Create new resources 
 PUT — Yellow: Update/replace entire resource 
 PATCH — Orange: Partial updates 
 DELETE — Red: Remove resources 
 
 
 User Journey Flows 
 Flow A — Online Checkout (Registered Attendee) 
 [ Attendee browses event & selects ticket ]
 |
 v
 [ POST /checkout — Create checkout session ]
 |
 ............|............
 . .
 v v
 [ FREE / DONATION ticket ] [ PAID ticket ]
 | |
 | v
 | [ Wallet balance check ]
 | ....................
 | . INSUFFICIENT .
 | ....................
 | |
 | [ 422 response — no session created ]
 | [ data: { shortfall, recommendedTopUp, ... } ]
 |
 | ....................
 | . SUFFICIENT .
 | ....................
 | |
 v v
 [ Auto-processed immediately ] [ Ticket hold applied ]
 | [ Session = PENDING_PAYMENT ]
 | |
 | v
 | [ POST /checkout/{sessionId}/payment ]
 | [ — Deduct from wallet, create escrow ]
 | |
 `----------->-----------'
 |
 v
 [ Booking order created asynchronously ]
 [ QR codes generated & sent to attendees ]
 |
 v
 [ Session status = COMPLETED ]

 At any point before payment:
 [ POST /checkout/{sessionId}/cancel ]
 [ — Releases ticket hold ]
 
 
 Flow B — At-Door Sale via Scanner Device 
 [ Customer arrives at event venue ]
 |
 v
 [ Scanner device sends sale request ]
 [ POST /checkout/sell-at-door-ticket/scanner ]
 |
 ............|............
 . .
 v v
 [ Validate scanner ID ] [ Validate device fingerprint ]
 [ & permissions ] [ & SELL_TICKETS permission ]
 . .
 `..........v............'
 |
 v
 [ Validate ticket type belongs to event ]
 [ Check sales channel = AT_DOOR or BOTH ]
 |
 v
 [ Cash payment processed (no wallet deduction) ]
 [ Booking order created immediately ]
 |
 v
 [ QR codes returned in response ]
 [ immediateCheckIn = true → ticket marked as checked-in ]
 
 
 Flow C — At-Door Sale via Organizer 
 [ Organizer is authenticated & accesses event ]
 |
 v
 [ POST /checkout/{eventId}/organizer ]
 [ — Organizer sells ticket at their counter ]
 |
 v
 [ System verifies organizer owns the event ]
 |
 v
 [ Validate ticket type & attendee count ]
 |
 v
 [ Cash payment recorded (NEUTRAL transaction) ]
 [ Booking order created ]
 |
 v
 [ QR codes returned in response ]
 [ immediateCheckIn flag respected ]
 
 
 Endpoints 
 
 1. Create Checkout Session 
 Purpose : Creates a new online checkout session for a registered attendee purchasing event tickets, holding the requested quantity and initializing the payment intent. 
 Endpoint : POST /api/v1/e-events/checkout 
 Access Level : 🔒 Protected (Requires valid Bearer token — authenticated attendee) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token of the authenticated attendee 
 
 
 Content-Type 
 string 
 Yes 
 Must be application/json 
 
 
 
 Request JSON Sample : 
 {
 "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
 "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
 "ticketsForMe": 2,
 "donationAmount": null,
 "otherAttendees": [
 {
 "name": "Jane Doe",
 "email": "jane.doe@example.com",
 "phone": "+255712345678",
 "quantity": 1
 }
 ],
 "sendTicketsToAttendees": true,
 "paymentMethodId": null
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The ID of the event being booked 
 Must be a valid published event that has not yet started 
 
 
 ticketTypeId 
 UUID 
 Yes 
 The ID of the ticket type being purchased 
 Must belong to the specified event and be active and on sale 
 
 
 ticketsForMe 
 integer 
 Yes 
 Number of tickets for the buyer themselves. Use 0 if the buyer is not attending 
 Min: 0 
 
 
 donationAmount 
 decimal 
 No 
 Donation amount in TZS. Only applicable for DONATION type tickets 
 Only used when ticket pricing type is DONATION 
 
 
 otherAttendees 
 array 
 No 
 List of other attendees to purchase tickets for 
 Each attendee must have valid name, email, and Tanzanian phone number 
 
 
 otherAttendees[].name 
 string 
 Yes (if array provided) 
 Full name of the attendee 
 Min: 2, Max: 100 characters 
 
 
 otherAttendees[].email 
 string 
 Yes (if array provided) 
 Email address of the attendee 
 Valid email format; no duplicate emails in the array 
 
 
 otherAttendees[].phone 
 string 
 Yes (if array provided) 
 Phone number of the attendee 
 Must match Tanzania format: +255[67]XXXXXXXX 
 
 
 otherAttendees[].quantity 
 integer 
 Yes (if array provided) 
 Number of tickets for this attendee 
 Min: 1 
 
 
 sendTicketsToAttendees 
 boolean 
 No 
 If true , QR tickets are sent to each attendee's email. If false , all QR codes are sent to the buyer only 
 Default: true 
 
 
 paymentMethodId 
 UUID 
 No 
 ID of a saved payment method. Defaults to wallet if not provided 
 Optional 
 
 
 
 Business Rules : 
 
 Total quantity = ticketsForMe + sum of all otherAttendees[].quantity — must be at least 1 
 DONATION tickets: maximum 1 per order, cannot be bought for other attendees, online only 
 AT_DOOR_ONLY tickets cannot be purchased through this endpoint 
 Wallet balance is validated upfront for PAID tickets 
 If the event has a required questionnaire set to BEFORE_CHECKOUT , it must be submitted before calling this endpoint 
 FREE tickets are auto-processed immediately — the response will already show PAYMENT_COMPLETED 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Checkout session created successfully",
 "action_time": "2025-09-23T10:30:45",
 "data": {
 "sessionId": "a1b2c3d4-0000-0000-0000-000000000001",
 "status": "PENDING_PAYMENT",
 "customerId": "f1e2d3c4-0000-0000-0000-000000000010",
 "customerUserName": "john_doe",
 "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
 "eventTitle": "Kilimanjaro Jazz Night 2025",
 "ticketDetails": {
 "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
 "ticketTypeName": "VIP",
 "unitPrice": 50000.00,
 "ticketsForBuyer": 2,
 "otherAttendees": [
 {
 "name": "Jane Doe",
 "email": "jane.doe@example.com",
 "phone": "+255712345678",
 "quantity": 1
 }
 ],
 "sendTicketsToAttendees": true,
 "totalQuantity": 3,
 "subtotal": 150000.00
 },
 "pricing": {
 "subtotal": 150000.00,
 "total": 150000.00
 },
 "paymentIntent": {
 "provider": "WALLET",
 "clientSecret": null,
 "paymentMethods": ["WALLET"],
 "status": "PENDING"
 },
 "ticketsHeld": true,
 "ticketHoldExpiresAt": "2025-09-23T10:45:45",
 "expiresAt": "2025-09-23T10:45:45",
 "createdAt": "2025-09-23T10:30:45",
 "updatedAt": "2025-09-23T10:30:45",
 "completedAt": null,
 "createdBookingOrderId": null,
 "isExpired": false,
 "canRetryPayment": false
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 sessionId 
 Unique identifier for this checkout session. Use it for all subsequent actions 
 
 
 status 
 Current session status. Values: PENDING_PAYMENT , PAYMENT_COMPLETED , COMPLETED , PAYMENT_FAILED , CANCELLED , EXPIRED 
 
 
 customerId 
 Account ID of the buyer 
 
 
 customerUserName 
 Username of the buyer 
 
 
 eventId 
 ID of the event being booked 
 
 
 eventTitle 
 Human-readable event title 
 
 
 ticketDetails.ticketTypeId 
 ID of the chosen ticket type 
 
 
 ticketDetails.ticketTypeName 
 Name of the ticket type (e.g., VIP, Regular) 
 
 
 ticketDetails.unitPrice 
 Price per single ticket in TZS 
 
 
 ticketDetails.ticketsForBuyer 
 Number of tickets allocated to the buyer 
 
 
 ticketDetails.otherAttendees 
 List of other attendees and their ticket quantities 
 
 
 ticketDetails.sendTicketsToAttendees 
 Whether QR codes will be emailed to each attendee 
 
 
 ticketDetails.totalQuantity 
 Total tickets across buyer and all attendees 
 
 
 ticketDetails.subtotal 
 Total price before any discounts (TZS) 
 
 
 pricing.subtotal 
 Subtotal amount in TZS 
 
 
 pricing.total 
 Final payable amount in TZS 
 
 
 paymentIntent.provider 
 Payment provider (e.g., WALLET ) 
 
 
 paymentIntent.paymentMethods 
 Available payment methods for this session 
 
 
 paymentIntent.status 
 Payment intent status ( PENDING , COMPLETED ) 
 
 
 ticketsHeld 
 Whether the tickets are currently being held in reserve 
 
 
 ticketHoldExpiresAt 
 Timestamp when the ticket hold expires 
 
 
 expiresAt 
 Timestamp when the entire session expires 
 
 
 createdBookingOrderId 
 Populated after payment is completed — the resulting booking order ID 
 
 
 isExpired 
 Computed flag indicating whether the session has passed its expiry time 
 
 
 canRetryPayment 
 Whether the session allows another payment attempt (true if status is PAYMENT_FAILED and attempts < 5) 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Please complete the event questionnaire before purchasing tickets",
 "action_time": "2025-09-23T10:30:45",
 "data": "Please complete the event questionnaire before purchasing tickets"
}
 
 Possible Errors : 
 
 
 
 HTTP Status 
 Scenario 
 
 
 
 
 400 BAD_REQUEST 
 Event is not published, event has already started, ticket not on sale, AT_DOOR_ONLY ticket purchased online, DONATION rules violated, questionnaire not submitted 
 
 
 401 UNAUTHORIZED 
 Missing, expired, or invalid Bearer token 
 
 
 404 NOT_FOUND 
 Event or ticket type not found 
 
 
 422 UNPROCESSABLE_ENTITY 
 Insufficient wallet balance (returns rich data with shortfall and recommendedTopUp ); or validation failed on request fields 
 
 
 500 INTERNAL_SERVER_ERROR 
 Unexpected server error 
 
 
 
 
 2. Get Checkout Session 
 Purpose : Retrieves the current state of an existing checkout session belonging to the authenticated user. 
 Endpoint : GET /api/v1/e-events/checkout/{sessionId} 
 Access Level : 🔒 Protected (Authenticated attendee — only the session owner can retrieve it) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token of the authenticated attendee 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 sessionId 
 UUID 
 Yes 
 The ID of the checkout session to retrieve 
 Must be a valid UUID belonging to the authenticated user 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Checkout session retrieved successfully",
 "action_time": "2025-09-23T10:35:00",
 "data": {
 "sessionId": "a1b2c3d4-0000-0000-0000-000000000001",
 "status": "PENDING_PAYMENT",
 "customerId": "f1e2d3c4-0000-0000-0000-000000000010",
 "customerUserName": "john_doe",
 "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
 "eventTitle": "Kilimanjaro Jazz Night 2025",
 "ticketDetails": {
 "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
 "ticketTypeName": "VIP",
 "unitPrice": 50000.00,
 "ticketsForBuyer": 2,
 "otherAttendees": [
 {
 "name": "Jane Doe",
 "email": "jane.doe@example.com",
 "phone": "+255712345678",
 "quantity": 1
 }
 ],
 "sendTicketsToAttendees": true,
 "totalQuantity": 3,
 "subtotal": 150000.00
 },
 "pricing": {
 "subtotal": 150000.00,
 "total": 150000.00
 },
 "paymentIntent": {
 "provider": "WALLET",
 "clientSecret": null,
 "paymentMethods": ["WALLET"],
 "status": "PENDING"
 },
 "paymentAttempts": [],
 "ticketsHeld": true,
 "ticketHoldExpiresAt": "2025-09-23T10:45:45",
 "expiresAt": "2025-09-23T10:45:45",
 "createdAt": "2025-09-23T10:30:45",
 "updatedAt": "2025-09-23T10:30:45",
 "completedAt": null,
 "createdBookingOrderId": null,
 "isExpired": false,
 "canRetryPayment": false
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 sessionId 
 Unique session identifier 
 
 
 status 
 Current session status 
 
 
 paymentAttempts 
 List of all payment attempts made on this session, including failures 
 
 
 paymentAttempts[].attemptNumber 
 Sequential attempt number (1-indexed) 
 
 
 paymentAttempts[].paymentMethod 
 Payment method used for this attempt 
 
 
 paymentAttempts[].status 
 Result of the attempt: SUCCESS or FAILED 
 
 
 paymentAttempts[].errorMessage 
 Failure reason if the attempt failed 
 
 
 paymentAttempts[].attemptedAt 
 Timestamp of the attempt 
 
 
 paymentAttempts[].transactionId 
 External or internal transaction reference 
 
 
 All other fields 
 Same as Create Checkout Session response 
 
 
 
 Possible Errors : 
 
 
 
 HTTP Status 
 Scenario 
 
 
 
 
 401 UNAUTHORIZED 
 Missing, expired, or invalid Bearer token 
 
 
 404 NOT_FOUND 
 Session not found or does not belong to the authenticated user 
 
 
 500 INTERNAL_SERVER_ERROR 
 Unexpected server error 
 
 
 
 
 3. Process Payment 
 Purpose : Initiates wallet payment for a pending checkout session, creating an escrow account and triggering asynchronous booking order creation upon success. 
 Endpoint : POST /api/v1/e-events/checkout/{sessionId}/payment 
 Access Level : 🔒 Protected (Session owner only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token of the authenticated attendee 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 sessionId 
 UUID 
 Yes 
 The ID of the checkout session to pay for 
 Must be a valid UUID; session must be in PENDING_PAYMENT status and not expired 
 
 
 
 Business Rules : 
 
 Session must be in PENDING_PAYMENT status 
 Session must not be expired 
 Cannot call this on FREE tickets (auto-processed on creation) 
 Wallet must have sufficient balance at time of payment call 
 Maximum 5 payment attempts per session; after that canRetryPayment becomes false 
 On success, escrow is created, session moves to PAYMENT_COMPLETED , and a booking order is created asynchronously 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Payment completed successfully. Your booking is being processed.",
 "action_time": "2025-09-23T10:38:00",
 "data": {
 "success": true,
 "status": "SUCCESS",
 "message": "Payment completed successfully. Your booking is being processed.",
 "checkoutSessionId": "a1b2c3d4-0000-0000-0000-000000000001",
 "escrowId": "e9f8a7b6-0000-0000-0000-000000000020",
 "escrowNumber": "ESC-2025-000001",
 "orderId": null,
 "orderNumber": null,
 "paymentMethod": "WALLET",
 "amountPaid": 150000.00,
 "platformFee": 7500.00,
 "sellerAmount": 142500.00,
 "currency": "TZS"
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 status 
 Payment result status: SUCCESS , FAILED , or PENDING 
 
 
 checkoutSessionId 
 The checkout session this payment belongs to 
 
 
 escrowId 
 ID of the escrow account holding the funds 
 
 
 escrowNumber 
 Human-readable escrow reference number (format: ESC-YYYY-NNNNNN) 
 
 
 orderId 
 Booking order ID — may be null immediately after payment as booking is created asynchronously 
 
 
 orderNumber 
 Human-readable order reference — null until order is created 
 
 
 paymentMethod 
 Payment method used: WALLET 
 
 
 amountPaid 
 Total amount deducted from buyer's wallet in TZS 
 
 
 platformFee 
 Platform fee amount (5% of total) in TZS 
 
 
 sellerAmount 
 Amount that will be released to the event organizer in TZS 
 
 
 currency 
 Currency code, always TZS 
 
 
 
 Possible Errors : 
 
 
 
 HTTP Status 
 Scenario 
 
 
 
 
 400 BAD_REQUEST 
 Session is not in PENDING_PAYMENT status, or session is expired 
 
 
 401 UNAUTHORIZED 
 Missing, expired, or invalid Bearer token 
 
 
 404 NOT_FOUND 
 Session not found or does not belong to the authenticated user 
 
 
 500 INTERNAL_SERVER_ERROR 
 Unexpected payment processing or booking creation error 
 
 
 
 
 4. Cancel Checkout Session 
 Purpose : Cancels an active checkout session and releases any held tickets back to available inventory. 
 Endpoint : POST /api/v1/e-events/checkout/{sessionId}/cancel 
 Access Level : 🔒 Protected (Session owner only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token of the authenticated attendee 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 sessionId 
 UUID 
 Yes 
 The ID of the checkout session to cancel 
 Must be a valid UUID belonging to the authenticated user 
 
 
 
 Business Rules : 
 
 Cannot cancel a session that is in COMPLETED status 
 Cannot cancel a session that is in PAYMENT_COMPLETED status (payment already processed) 
 Cancelling releases the ticket hold immediately 
 Cancellation does not trigger a refund — refunds are handled separately through the escrow system 
 
 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 : 
 
 The scanner must be active, not expired, and have the SELL_TICKETS permission 
 The deviceFingerprint must exactly match the registered fingerprint for this scanner 
 The number of attendees must equal quantity — a 1-to-1 mapping is enforced 
 Payment method is always CASH — no wallet deduction occurs 
 The ticket type must not be ONLINE_ONLY 
 immediateCheckIn = true automatically marks each generated ticket as checked-in 
 
 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 : 
 
 Only the event organizer (the user who created the event) can call this endpoint 
 Number of entries in attendees must equal quantity 
 Payment is always CASH — no wallet or ledger deduction 
 Ticket type must not be ONLINE_ONLY 
 immediateCheckIn = true marks each ticket as checked-in at time of sale 
 
 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) 
 
 400 BAD_REQUEST : General invalid request, business rule violations, or item already exists 
 401 UNAUTHORIZED : Authentication issues (missing, invalid, expired, or malformed token) 
 403 FORBIDDEN : Access denied, scanner permission issues, organizer mismatch 
 404 NOT_FOUND : Event, ticket type, session, or scanner not found 
 422 UNPROCESSABLE_ENTITY : Bean validation errors with per-field details 
 429 TOO_MANY_REQUESTS : Rate limit exceeded 
 
 Server-Level Exceptions (500+) 
 
 500 INTERNAL_SERVER_ERROR : Unexpected server errors, payment orchestration failures 
 
 
 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 : 
 
 The caller does not need to call POST /{sessionId}/payment — this is skipped entirely 
 The session is returned from the create endpoint already in PAYMENT_COMPLETED status (may shift to COMPLETED once the booking is written) 
 No escrow record exists for this transaction; escrowId will be null in all responses 
 A NEUTRAL transaction history entry is recorded for audit purposes 
 Ticket holds are still applied on creation and released naturally upon booking completion 
 FREE tickets can be ONLINE_ONLY or BOTH depending on the sales channel configuration — AT_DOOR_ONLY FREE tickets go through the scanner/organizer at-door flows instead 
 
 
 PAID Tickets 
 What they are : Tickets with a price greater than 0 TZS . Wallet payment is required. 
 Checkout flow : 
 [ POST /checkout — session created ]
 |
 v
 [ assertSufficientBalanceForCheckout(total) ]
 ............................................
 . INSUFFICIENT BALANCE .
 ............................................
 |
 [ 422 returned immediately ]
 [ No session created ]
 [ data: { ]
 [ walletBalance, ]
 [ sessionTotal, ]
 [ shortfall, ]
 [ recommendedTopUp ]
 [ } ]

 ............................................
 . SUFFICIENT BALANCE .
 ............................................
 |
 v
 [ Ticket hold applied ]
 |
 v
 [ Session status = PENDING_PAYMENT ]
 [ paymentIntent.provider = WALLET ]
 |
 v
 [ POST /{sessionId}/payment called by client ]
 |
 v
 [ Wallet deducted via double-entry ledger ]
 [ Escrow account created (ESC-YYYY-NNNNNN)]
 [ platformFee = 5% of total ]
 [ sellerAmount = total - platformFee ]
 |
 v
 [ PaymentCompletedEvent published (escrow != null) ]
 |
 v
 [ Booking order created asynchronously ]
 [ Ticket serials assigned ]
 [ QR codes generated ]
 |
 v
 [ Session status = COMPLETED ]
 [ Escrow status = HELD ]
 [ (Released to organizer on event completion) ]
 
 Key rules : 
 
 Wallet balance is checked at session creation — if insufficient, a 422 is returned with rich balance data ( shortfall , recommendedTopUp ) and no session is created. A second safety-net check runs at actual payment time in case the balance changed between the two calls 
 Maximum 5 payment attempts per session; after that canRetryPayment = false and a new session must be created 
 If a payment attempt fails, the session moves to PAYMENT_FAILED but the ticket hold remains active until the session expires 
 Escrow holds funds in a separate ledger account — the organizer does not receive the money until the platform releases it after the event 
 orderId in the payment response may be null immediately after payment since booking creation is asynchronous — poll GET /checkout/{sessionId} and check createdBookingOrderId to confirm 
 
 
 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 : 
 
 Strictly 1 ticket per order — the system rejects any request with ticketsForMe > 1 or any otherAttendees 
 Online-only — DONATION tickets cannot be sold at the door through any channel 
 The donationAmount in the request body is the amount that will be charged; the system uses it as the effective unit price 
 Despite being a donation, the standard 5% platform fee still applies and an escrow account is created 
 If donationAmount is null or 0 , the system may treat it as a FREE ticket depending on the ticket's configured minimum — confirm with the organizer's ticket setup 
 
 
 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 
 
 Bearer Token : Include Authorization: Bearer <token> in all request headers 
 All endpoints require authentication 
 For scanner endpoints, the Bearer token must belong to the account linked to the scanner device 
 
 Data Format Standards 
 
 Dates : ISO 8601 format ( 2025-09-23T10:30:45 ) 
 Currency : TZS (Tanzanian Shilling) — decimal values with 2 decimal places 
 UUIDs : Standard UUID v4 format ( xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ) 
 Phone Numbers : Tanzania format only — +255[67]XXXXXXXX

Event Booking Orders API
Author : Josh S. Sakweli, Backend Lead Team
 Last Updated : 2026-05-23
 Version : v1.0 
 Base URL : https://api.nexgate.com/api/v1 
 Short Description : The Event Booking Orders API manages confirmed ticket purchases for events on the Nexgate platform. It allows customers to view their bookings with JWT-signed QR codes, track multi-day check-in history, download ticket PDFs, and enables event organizers to monitor all orders for their events with filtering and revenue stats. 
 Hints : 
 
 Bookings are created automatically after successful checkout payment — never created directly via this API 
 QR codes are RSA-signed JWTs containing full ticket and event data; they cannot be forged 
 Event details (title, venue, organizer) are snapshotted at booking time and never change on a booking record 
 Ticket series are auto-generated (e.g., VIP-0001 , GENER-0042 ) using a per-ticket-type counter 
 Multi-day events store a full checkIns array per ticket — one record per day attended 
 Customers see their own bookings only; organizers see bookings for their events; admins see all 
 The PDF endpoint returns a binary file, not a JSON response 
 
 
 Standard Response Format 
 All JSON endpoints follow a consistent structure using the Globe Response Builder pattern: 
 Success Response Structure 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Operation completed successfully",
 "action_time": "2026-05-23T10:30:45",
 "data": {}
}
 
 Error Response Structure 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Booking not found",
 "action_time": "2026-05-23T10:30:45",
 "data": "Booking not found"
}
 
 Standard Response Fields 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 success 
 boolean 
 true for successful operations, false for errors 
 
 
 httpStatus 
 string 
 HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.) 
 
 
 message 
 string 
 Human-readable description of the result 
 
 
 action_time 
 string 
 ISO 8601 timestamp of when the response was generated 
 
 
 data 
 object/string 
 Response payload on success; error message string on failure 
 
 
 
 
 HTTP Method Badge Standards 
 
 GET - GET - Green (Safe, read-only operations) 
 POST - POST - Blue (Create new resources) 
 PUT - PUT - Yellow (Update/replace entire resource) 
 PATCH - PATCH - Orange (Partial updates) 
 DELETE - DELETE - Red (Remove resources) 
 
 
 Endpoints 
 1. Get Booking by ID 
 Purpose : Retrieve complete booking details including all tickets, check-in history, and event snapshot for a specific booking. 
 Endpoint : GET {base_url}/e-events/booking-orders/{bookingId} 
 Access Level : 🔒 Protected (Booking Owner, Event Organizer, or Admin) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token (format: Bearer <token> ) 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 bookingId 
 string (UUID) 
 Yes 
 Unique booking order ID 
 Must be valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Booking retrieved successfully",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 "bookingId": "550e8400-e29b-41d4-a716-446655440000",
 "bookingReference": "EVT-A3F4B21C",
 "status": "CONFIRMED",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "event": {
 "eventId": "770e8400-e29b-41d4-a716-446655440002",
 "title": "East African Tech Summit 2025",
 "startDateTime": "2025-12-15T09:00:00",
 "endDateTime": "2025-12-17T18:00:00",
 "timezone": "Africa/Nairobi",
 "location": "KICC Nairobi, Harambee Avenue, Nairobi",
 "format": "HYBRID",
 "hasApplicantForm": true,
 "virtualDetails": {
 "meetingLink": "https://zoom.us/j/123456789",
 "meetingId": "123 456 789",
 "passcode": "summit2025"
 }
 },
 "organizer": {
 "name": "TechEvents Kenya",
 "email": "organizer@techevents.ke",
 "phone": "+254712345678"
 },
 "customer": {
 "customerId": "660e8400-e29b-41d4-a716-446655440001",
 "name": "johndoe",
 "email": "john@example.com"
 },
 "tickets": [
 {
 "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440010",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "ticketTypeName": "VIP Pass",
 "ticketSeries": "VIP-0001",
 "ticketNumber": "VIP-0001",
 "price": 150.00,
 "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
 "attendanceMode": "IN_PERSON",
 "attendee": {
 "name": "John Doe",
 "email": "john@example.com",
 "phone": "+255712345678"
 },
 "buyer": {
 "name": "John Doe",
 "email": "john@example.com",
 "buyerType": "SYSTEM_USER"
 },
 "checkIns": [
 {
 "checkInTime": "2025-12-15T09:15:00+03:00",
 "checkInLocation": "Main Gate",
 "checkedInBy": "Scanner Operator 1",
 "dayName": "Day 1 - Opening Day",
 "scannerId": "SCANNER-001",
 "checkInMethod": "QR_SCAN"
 },
 {
 "checkInTime": "2025-12-16T08:45:00+03:00",
 "checkInLocation": "VIP Entrance",
 "checkedInBy": "Scanner Operator 2",
 "dayName": "Day 2 - Conference Day",
 "scannerId": "SCANNER-003",
 "checkInMethod": "QR_SCAN"
 }
 ],
 "hasBeenCheckedIn": true,
 "lastCheckedInAt": "2025-12-16T08:45:00+03:00",
 "lastCheckedInBy": "Scanner Operator 2",
 "lastCheckInLocation": "VIP Entrance",
 "lastCheckInDayName": "Day 2 - Conference Day",
 "status": "USED",
 "validFrom": "2025-12-15T09:00:00+03:00",
 "validUntil": "2025-12-17T18:00:00+03:00"
 },
 {
 "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440011",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "ticketTypeName": "General Admission",
 "ticketSeries": "GENER-0042",
 "ticketNumber": "GENER-0042",
 "price": 50.00,
 "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
 "attendanceMode": "IN_PERSON",
 "attendee": {
 "name": "Jane Smith",
 "email": "jane@example.com",
 "phone": "+255723456789"
 },
 "buyer": {
 "name": "John Doe",
 "email": "john@example.com",
 "buyerType": "SYSTEM_USER"
 },
 "checkIns": [],
 "hasBeenCheckedIn": false,
 "status": "ACTIVE",
 "validFrom": "2025-12-15T09:00:00+03:00",
 "validUntil": "2025-12-17T18:00:00+03:00"
 }
 ],
 "totalTickets": 2,
 "checkedInTicketsCount": 1,
 "subtotal": 200.00,
 "total": 200.00,
 "bookedAt": "2025-12-11T10:30:45",
 "cancelledAt": null
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 bookingId 
 Unique booking UUID 
 
 
 bookingReference 
 Short readable code (e.g., EVT-A3F4B21C ) 
 
 
 status 
 Booking status: CONFIRMED , CANCELLED 
 
 
 formResponseId 
 UUID of the buyer's applicant form response — null if the event had no form 
 
 
 event.eventId 
 Event UUID 
 
 
 event.title 
 Event title at time of booking (immutable snapshot) 
 
 
 event.startDateTime 
 Event start — LocalDateTime (no timezone) 
 
 
 event.endDateTime 
 Event end — LocalDateTime (no timezone) 
 
 
 event.timezone 
 IANA timezone string (e.g., Africa/Nairobi ) 
 
 
 event.location 
 Full venue address snapshotted at booking 
 
 
 event.format 
 IN_PERSON , ONLINE , or HYBRID 
 
 
 event.hasApplicantForm 
 true if the event had an applicant form at time of booking 
 
 
 event.virtualDetails 
 Virtual meeting info — only present for ONLINE / HYBRID events 
 
 
 event.virtualDetails.meetingLink 
 Full meeting URL 
 
 
 event.virtualDetails.meetingId 
 Meeting ID (optional) 
 
 
 event.virtualDetails.passcode 
 Meeting passcode (optional) 
 
 
 organizer.name 
 Organizer name snapshotted at booking 
 
 
 organizer.email 
 Organizer email 
 
 
 organizer.phone 
 Organizer phone 
 
 
 customer.customerId 
 Customer account UUID 
 
 
 customer.name 
 Customer username 
 
 
 customer.email 
 Customer email 
 
 
 tickets 
 Array of all booked ticket instances 
 
 
 tickets[].ticketInstanceId 
 Unique ticket instance UUID 
 
 
 tickets[].formResponseId 
 UUID of the applicant form response linked to this ticket — same as order-level formResponseId 
 
 
 tickets[].ticketTypeName 
 Ticket type name (e.g., VIP Pass ) 
 
 
 tickets[].ticketSeries 
 Auto-generated series (e.g., VIP-0001 ) 
 
 
 tickets[].ticketNumber 
 Same as ticketSeries — used for PDF labeling 
 
 
 tickets[].price 
 Price paid for this ticket 
 
 
 tickets[].qrCode 
 RSA-signed JWT — used for gate check-in scanning 
 
 
 tickets[].attendanceMode 
 IN_PERSON or ONLINE (relevant for hybrid events) 
 
 
 tickets[].attendee 
 Person this ticket is for 
 
 
 tickets[].attendee.name 
 Attendee full name 
 
 
 tickets[].attendee.email 
 Attendee email 
 
 
 tickets[].attendee.phone 
 Attendee phone 
 
 
 tickets[].buyer 
 Person who purchased this ticket 
 
 
 tickets[].buyer.name 
 Buyer name 
 
 
 tickets[].buyer.email 
 Buyer email (null for AT_DOOR purchases) 
 
 
 tickets[].buyer.buyerType 
 SYSTEM_USER (online) or AT_DOOR (sold at gate) 
 
 
 tickets[].checkIns 
 Full check-in history — one record per day attended 
 
 
 tickets[].checkIns[].checkInTime 
 ZonedDateTime of check-in 
 
 
 tickets[].checkIns[].checkInLocation 
 Where checked in (e.g., Main Gate , VIP Entrance ) 
 
 
 tickets[].checkIns[].checkedInBy 
 Staff/scanner operator name 
 
 
 tickets[].checkIns[].dayName 
 Event day label (e.g., Day 1 - Opening Day ) 
 
 
 tickets[].checkIns[].scannerId 
 Scanner device ID 
 
 
 tickets[].checkIns[].checkInMethod 
 QR_SCAN (default), MANUAL , or NFC 
 
 
 tickets[].hasBeenCheckedIn 
 true if ticket has at least one check-in 
 
 
 tickets[].lastCheckedInAt 
 Most recent check-in time (ZonedDateTime, null if none) 
 
 
 tickets[].lastCheckedInBy 
 Who performed the most recent check-in (null if none) 
 
 
 tickets[].lastCheckInLocation 
 Location of most recent check-in (null if none) 
 
 
 tickets[].lastCheckInDayName 
 Day label of most recent check-in (null if none) 
 
 
 tickets[].status 
 ACTIVE , USED , or CANCELLED 
 
 
 tickets[].validFrom 
 Ticket validity start — ZonedDateTime 
 
 
 tickets[].validUntil 
 Ticket validity end — ZonedDateTime 
 
 
 totalTickets 
 Total ticket count in this booking 
 
 
 checkedInTicketsCount 
 Count of tickets with at least one check-in 
 
 
 subtotal 
 Subtotal before any deductions 
 
 
 total 
 Total amount paid 
 
 
 bookedAt 
 When the booking was created — LocalDateTime 
 
 
 cancelledAt 
 When cancelled — LocalDateTime (null if still active) 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Booking not found: 550e8400-e29b-41d4-a716-446655440000",
 "action_time": "2026-05-23T10:30:45",
 "data": "Booking not found: 550e8400-e29b-41d4-a716-446655440000"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or expired Bearer token 
 403 FORBIDDEN : Authenticated user is not the booking owner, event organizer, or admin 
 404 NOT_FOUND : Booking with given ID does not exist 
 
 
 2. Get My Bookings 
 Purpose : Retrieve a summary list of all bookings for the authenticated user, sorted newest first. 
 Endpoint : GET {base_url}/e-events/booking-orders/my-bookings 
 Access Level : 🔒 Protected (Authenticated Users) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token (format: Bearer <token> ) 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Bookings retrieved successfully",
 "action_time": "2026-05-23T10:30:45",
 "data": [
 {
 "bookingId": "550e8400-e29b-41d4-a716-446655440000",
 "bookingReference": "EVT-A3F4B21C",
 "status": "CONFIRMED",
 "eventTitle": "East African Tech Summit 2025",
 "eventStartDateTime": "2025-12-15T09:00:00",
 "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi",
 "totalTickets": 2,
 "checkedInTickets": 1,
 "total": 200.00,
 "bookedAt": "2025-12-11T10:30:45",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
 },
 {
 "bookingId": "550e8400-e29b-41d4-a716-446655440001",
 "bookingReference": "EVT-B5D2E12F",
 "status": "CONFIRMED",
 "eventTitle": "Dar es Salaam Food Festival",
 "eventStartDateTime": "2025-12-20T11:00:00",
 "eventLocation": "Mlimani City, Sam Nujoma Road, Dar es Salaam",
 "totalTickets": 4,
 "checkedInTickets": 0,
 "total": 150.00,
 "bookedAt": "2025-12-10T14:20:30",
 "formResponseId": null
 }
 ]
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 bookingId 
 Unique booking UUID 
 
 
 bookingReference 
 Short readable code (e.g., EVT-A3F4B21C ) 
 
 
 status 
 CONFIRMED or CANCELLED 
 
 
 eventTitle 
 Event title snapshotted at booking time 
 
 
 eventStartDateTime 
 Event start date/time — LocalDateTime (no timezone) 
 
 
 eventLocation 
 Venue address snapshotted at booking time 
 
 
 totalTickets 
 Total number of tickets in this booking 
 
 
 checkedInTickets 
 Number of tickets with at least one check-in 
 
 
 total 
 Total amount paid 
 
 
 bookedAt 
 When booking was created — LocalDateTime 
 
 
 formResponseId 
 UUID of the applicant form response — null if the event had no form 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "User not found",
 "action_time": "2026-05-23T10:30:45",
 "data": "User not found"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or expired Bearer token 
 404 NOT_FOUND : Authenticated user account not found 
 
 
 3. Download Ticket PDF 
 Purpose : Download or preview the PDF ticket for a specific ticket instance belonging to the authenticated user. 
 Endpoint : GET {base_url}/e-events/booking-orders/tickets/{ticketInstanceId}/pdf 
 Access Level : 🔒 Protected (Ticket Owner) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token (format: Bearer <token> ) 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ticketInstanceId 
 string (UUID) 
 Yes 
 Unique ID of the ticket to download 
 Must be valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Default 
 
 
 
 
 mode 
 string 
 No 
 download prompts a save dialog; inline opens the PDF in the browser tab 
 download 
 
 
 
 Success Response : Binary PDF file (not JSON) 
 Success Response Headers : 
 
 
 
 Header 
 Value 
 
 
 
 
 Content-Type 
 application/pdf 
 
 
 Content-Disposition 
 attachment; filename="ticket-{series}.pdf" or inline; ... by mode 
 
 
 
 PDF Content Includes : 
 
 Event name, date, and venue 
 Attendee name 
 Ticket series and type 
 QR code (RSA-signed JWT) for gate scanning 
 Ticket validity period 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "You don't have permission to access this ticket",
 "action_time": "2026-05-23T10:30:45",
 "data": "You don't have permission to access this ticket"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or expired Bearer token 
 403 FORBIDDEN : Ticket does not belong to the authenticated user 
 404 NOT_FOUND : Ticket with given ID does not exist 
 500 INTERNAL_SERVER_ERROR : PDF generation failed 
 
 
 4. Get Event Orders (Organizer) 
 Purpose : Retrieve a paginated list of all booking orders for a specific event with filtering by status, buyer type, and ticket type, plus aggregated revenue and order stats. 
 Endpoint : GET {base_url}/e-events/booking-orders/event/{eventId} 
 Access Level : 🔒 Protected (Event Organizer or Admin) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer token (format: Bearer <token> ) 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 string (UUID) 
 Yes 
 Target event UUID 
 Must be valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 status 
 string (enum) 
 No 
 Filter by booking status 
 CONFIRMED , CANCELLED 
 — 
 
 
 buyerType 
 string (enum) 
 No 
 Filter by how the ticket was purchased 
 SYSTEM_USER , AT_DOOR 
 — 
 
 
 ticketTypeId 
 string (UUID) 
 No 
 Filter orders that include a specific ticket type 
 Must be valid UUID 
 — 
 
 
 search 
 string 
 No 
 Search by buyer name, email, or booking reference 
 — 
 — 
 
 
 page 
 integer 
 No 
 Zero-based page index 
 Min: 0 
 0 
 
 
 size 
 integer 
 No 
 Number of orders per page 
 Min: 1 
 20 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Event orders retrieved",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 "eventId": "770e8400-e29b-41d4-a716-446655440002",
 "eventTitle": "East African Tech Summit 2025",
 "appliedFilters": {
 "status": "CONFIRMED",
 "buyerType": null,
 "ticketTypeId": null,
 "ticketTypeName": null,
 "search": null
 },
 "stats": {
 "totalOrders": 142,
 "totalTicketsSold": 318,
 "totalRevenue": 47500.00,
 "confirmedOrders": 139,
 "cancelledOrders": 3,
 "refundedOrders": 0,
 "onlineOrders": 128,
 "atDoorOrders": 14
 },
 "orders": [
 {
 "bookingId": "550e8400-e29b-41d4-a716-446655440000",
 "bookingReference": "EVT-A3F4B21C",
 "status": "CONFIRMED",
 "bookedAt": "2025-12-11T10:30:45",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "buyer": {
 "buyerName": "John Doe",
 "buyerEmail": "john@example.com",
 "buyerType": "SYSTEM_USER"
 },
 "totalTickets": 2,
 "checkedInCount": 1,
 "total": 200.00,
 "ticketTypes": [
 { "ticketTypeName": "VIP Pass", "quantity": 1 },
 { "ticketTypeName": "General Admission", "quantity": 1 }
 ]
 },
 {
 "bookingId": "550e8400-e29b-41d4-a716-446655440099",
 "bookingReference": "EVT-C7E1F34A",
 "status": "CONFIRMED",
 "bookedAt": "2025-12-10T18:15:00",
 "formResponseId": null,
 "buyer": {
 "buyerName": "Grace Njeri",
 "buyerEmail": null,
 "buyerType": "AT_DOOR"
 },
 "totalTickets": 1,
 "checkedInCount": 1,
 "total": 50.00,
 "ticketTypes": [
 { "ticketTypeName": "General Admission", "quantity": 1 }
 ]
 }
 ],
 "pagination": {
 "currentPage": 0,
 "pageSize": 20,
 "totalPages": 8,
 "totalElements": 142,
 "hasNext": true,
 "hasPrevious": false
 }
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 eventId 
 UUID of the queried event 
 
 
 eventTitle 
 Event title 
 
 
 appliedFilters 
 Echo of the active filter values (null = not filtered) 
 
 
 appliedFilters.status 
 Active booking status filter 
 
 
 appliedFilters.buyerType 
 Active buyer type filter 
 
 
 appliedFilters.ticketTypeId 
 Active ticket type UUID filter 
 
 
 appliedFilters.ticketTypeName 
 Resolved name of the filtered ticket type 
 
 
 appliedFilters.search 
 Active search string 
 
 
 stats 
 Aggregated stats across ALL orders for the event (not just current page) 
 
 
 stats.totalOrders 
 Total booking count 
 
 
 stats.totalTicketsSold 
 Total individual tickets across all bookings 
 
 
 stats.totalRevenue 
 Sum of all order totals 
 
 
 stats.confirmedOrders 
 Orders with status CONFIRMED 
 
 
 stats.cancelledOrders 
 Orders with status CANCELLED 
 
 
 stats.refundedOrders 
 Orders with status REFUNDED 
 
 
 stats.onlineOrders 
 Orders placed online ( SYSTEM_USER buyer type) 
 
 
 stats.atDoorOrders 
 Orders sold at the gate ( AT_DOOR buyer type) 
 
 
 orders 
 Paginated list of order rows 
 
 
 orders[].bookingId 
 Booking UUID 
 
 
 orders[].bookingReference 
 Short readable code (e.g., EVT-A3F4B21C ) 
 
 
 orders[].status 
 Booking status string 
 
 
 orders[].bookedAt 
 When booking was created — LocalDateTime 
 
 
 orders[].formResponseId 
 UUID of the buyer's applicant form response — null if no form or not yet submitted 
 
 
 orders[].buyer.buyerName 
 Buyer's name 
 
 
 orders[].buyer.buyerEmail 
 Buyer's email (null for AT_DOOR) 
 
 
 orders[].buyer.buyerType 
 SYSTEM_USER or AT_DOOR 
 
 
 orders[].totalTickets 
 Number of tickets in this order 
 
 
 orders[].checkedInCount 
 Number of tickets checked in at least once 
 
 
 orders[].total 
 Order total amount 
 
 
 orders[].ticketTypes 
 Breakdown of ticket types in this order 
 
 
 orders[].ticketTypes[].ticketTypeName 
 Name of the ticket type 
 
 
 orders[].ticketTypes[].quantity 
 How many of this type are in the order 
 
 
 pagination.currentPage 
 Zero-based current page index 
 
 
 pagination.pageSize 
 Number of items per page 
 
 
 pagination.totalPages 
 Total number of pages 
 
 
 pagination.totalElements 
 Total matching orders 
 
 
 pagination.hasNext 
 true if a next page exists 
 
 
 pagination.hasPrevious 
 true if a previous page exists 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "Access denied: you are not the organizer of this event",
 "action_time": "2026-05-23T10:30:45",
 "data": "Access denied: you are not the organizer of this event"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or expired Bearer token 
 403 FORBIDDEN : Authenticated user is not the event organizer or an admin 
 404 NOT_FOUND : Event with given ID does not exist 
 
 
 Ticket Series Generation 
 Format : {TICKET_CODE}-{COUNTER} 
 The ticket code is the first 5 characters of the ticket type name (uppercased, spaces removed). The counter is a zero-padded incrementing integer per ticket type, managed with pessimistic locking to prevent duplicates. 
 Examples : 
 
 VIP Pass → VIP-0001 , VIP-0002 , VIP-0003 
 General Admission → GENER-0001 , GENER-0042 
 Early Bird → EARLY-0001 
 
 The counter never resets — ticket series are globally unique per type forever. 
 
 JWT-Signed QR Codes 
 Each ticket's qrCode field is a full RSA-signed JWT (2048-bit key pair generated when the event is published). The JWT payload contains: 
 
 ticketInstanceId , ticketTypeId , ticketTypeName , ticketSeries 
 eventId , eventName , eventStartDateTime 
 attendeeName , attendeeEmail , attendeePhone , attendanceMode 
 bookingReference 
 eventSchedules — array of day objects for multi-day events: 
 
 [
 {
 "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"
 }
]
 
 
 validFrom , validUntil 
 
 Why JWT? Scanners can verify the ticket offline using the public key without a database lookup. Any tampering breaks the signature. The embedded eventSchedules lets scanners enforce per-day check-in rules without an internet connection. 
 
 Multi-Day Check-Ins 
 For multi-day events, each ticket can be checked in once per day. The checkIns array stores the full history. The hasBeenCheckedIn , lastCheckedInAt , lastCheckedInBy , lastCheckInLocation , and lastCheckInDayName convenience fields reflect the most recent entry. 
 
 dayName in a check-in record must match one of the eventSchedules day names embedded in the JWT 
 A ticket cannot be checked in twice on the same day 
 Ticket status stays ACTIVE across days and transitions to USED after all expected check-ins 
 
 
 Event Snapshots 
 Event details (title, venue, times, organizer contacts) are captured into the booking record at the moment of purchase and never change. This means: 
 
 PDF tickets always show what the customer actually bought 
 Bookings remain accurate even if the organizer later renames or reschedules the event 
 The event and organizer blocks in BookingOrderResponse always reflect booking-time state 
 
 
 Booking Creation Flow 
 Bookings are created automatically by EventPaymentCompletedListener after a successful payment — never through a direct API call. The flow: 
 
 Fetch checkout session, event, and ticket type from the database 
 Generate a unique booking reference ( EVT-{8-char-UUID} ) 
 Create ticket instances (one per quantity unit, tagged with attendee and buyer info) 
 Sign a JWT QR code for each ticket using the event's RSA private key 
 Snapshot current event and organizer details into the booking record 
 Save the booking and mark the checkout session as COMPLETED 
 Publish a BookingCreatedEvent to trigger email notifications to buyer, attendees, and organizer 
 
 The entire flow runs in a single transaction — no partial bookings are created. 
 
 Access Control Rules 
 
 
 
 Actor 
 Endpoint 
 Allowed? 
 
 
 
 
 Booking owner (customer) 
 GET /booking-orders/{id} 
 Yes 
 
 
 Event organizer 
 GET /booking-orders/{id} 
 Yes 
 
 
 Admin (SUPER_ADMIN/STAFF_ADMIN) 
 GET /booking-orders/{id} 
 Yes 
 
 
 Other authenticated user 
 GET /booking-orders/{id} 
 No — 403 
 
 
 Any authenticated user 
 GET /my-bookings 
 Yes (own bookings only) 
 
 
 Ticket owner 
 GET /tickets/{id}/pdf 
 Yes 
 
 
 Other authenticated user 
 GET /tickets/{id}/pdf 
 No — 403 
 
 
 Event organizer 
 GET /booking-orders/event/{id} 
 Yes 
 
 
 Admin 
 GET /booking-orders/event/{id} 
 Yes 
 
 
 Non-organizer user 
 GET /booking-orders/event/{id} 
 No — 403 
 
 
 
 
 Booking and Ticket Status 
 Booking Status : 
 
 
 
 Status 
 Description 
 
 
 
 
 CONFIRMED 
 Active booking, tickets are valid 
 
 
 CANCELLED 
 Booking cancelled (placeholder — cancellation logic not yet implemented) 
 
 
 
 Ticket Instance Status : 
 
 
 
 Status 
 Description 
 
 
 
 
 ACTIVE 
 Ticket ready for use; can be scanned 
 
 
 USED 
 Ticket has been checked in; still scannable for additional days 
 
 
 CANCELLED 
 Ticket cancelled; cannot be used for entry 
 
 
 
 
 Date/Time Formats 
 
 
 
 Type 
 Format 
 Example 
 Used For 
 
 
 
 
 LocalDateTime 
 YYYY-MM-DDTHH:mm:ss 
 2025-12-15T09:00:00 
 bookedAt , cancelledAt , event snapshots 
 
 
 ZonedDateTime 
 YYYY-MM-DDTHH:mm:ss±HH:mm 
 2025-12-15T09:00:00+03:00 
 validFrom , validUntil , checkInTime 
 
 
 
 ZonedDateTime fields carry the event's local timezone offset and display correctly regardless of the viewer's locale. 
 
 Endpoint Summary 
 
 
 
 # 
 Endpoint 
 Access 
 
 
 
 
 1 
 GET /e-events/booking-orders/{bookingId} 
 Owner / Organizer / Admin 
 
 
 2 
 GET /e-events/booking-orders/my-bookings 
 Any authenticated user 
 
 
 3 
 GET /e-events/booking-orders/tickets/{ticketInstanceId}/pdf 
 Ticket owner 
 
 
 4 
 GET /e-events/booking-orders/event/{eventId} 
 Event organizer / Admin

Event Check-In System API
Author : Josh, Lead Backend Team 
 Last Updated : 2025-12-11 
 Version : v1.0 
 Base URL : https://api.nexgate.com/api/v1 
 Short Description : The Event Check-In System API provides secure ticket validation and scanner management for event entry. This API enables organizers to generate registration tokens (like WhatsApp's "Link Device"), scanners to register for events, and perform real-time ticket validation with JWT signature verification. The system supports multi-day events with per-day check-in tracking, device fingerprinting for security, automatic scanner revocation, and comprehensive duplicate detection. 
 Hints : 
 
 Registration Flow : Organizer generates token → Scanner scans QR → Scanner registers → Automatic revocation of old scanners 
 Device Security : Fingerprint validation prevents credential theft 
 JWT Validation : RSA-signed tickets verified offline-capable 
 Multi-Day Events : Separate check-in per event day 
 One Device Rule : One ACTIVE scanner per device across all events 
 Check-In Strategies : 5 strategies (HOURS_BEFORE, SPECIFIC_TIME, ALL_DAY, EXACT_TIME, AS_DAY_START) 
 Duplicate Detection : Prevents same-day re-entry per event day 
 Scanner Stats : Tracks successful/failed scans automatically 
 Auto-Revocation : Old scanners revoked when device registers for new event 
 Organizer Only : Only event organizers can generate tokens and manage scanners 
 
 
 API Overview 
 Registration Token Endpoints 
 
 POST /check-in/tokens/generate - Generate registration token (organizer) 
 GET /check-in/tokens/validate/{token} - Validate registration token 
 
 Scanner Management Endpoints 
 
 POST /check-in/scanners/register - Register scanner device 
 GET /check-in/scanners/event/{eventId} - Get all scanners for event 
 GET /check-in/scanners/event/{eventId}/active - Get active scanners 
 POST /check-in/scanners/{scannerId}/revoke - Revoke scanner 
 
 Ticket Validation Endpoint 
 
 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 : 
 
 Validates user is event organizer 
 Validates event has RSA keys (must be published) 
 Generates token: REG-{8-UUID}-{8-UUID} 
 Sets expiry: 5 minutes (configurable) 
 Returns QR code data for scanner app 
 
 Errors : 
 
 403 FORBIDDEN : Not event organizer 
 404 NOT_FOUND : Event not found 
 422 : Event not published or no RSA keys 
 
 
 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 : 
 
 Validates device fingerprint (10-255 chars) 
 Validates token (not used, not expired) 
 Auto-revokes any ACTIVE scanner with same device fingerprint 
 Generates scanner ID (UUID) 
 Generates JWT credentials (1 year validity) 
 Marks token as used (one-time use) 
 
 Key Rule : One device → One ACTIVE scanner (across all events) 
 Errors : 
 
 400 BAD_REQUEST : Invalid fingerprint, token used/expired 
 404 NOT_FOUND : Token not found 
 
 
 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 : 
 
 Changes status to REVOKED (permanent) 
 Records revocation reason and timestamp 
 Scanner can no longer validate tickets 
 
 
 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 : 
 
 Validate Scanner : Active status, fingerprint match 
 Verify JWT : RSA signature with event's public key 
 Find Current Day : Match current time to event schedules 
 Find Booking : Locate ticket in database 
 Check Duplicate : Already checked in for this day? 
 Create Check-In : Add check-in record for current day 
 Update Stats : Increment scanner counters 
 
 Check-In Strategies : 
 
 HOURS_BEFORE : X hours before event + late grace period 
 SPECIFIC_TIME : Daily window (e.g., 08:00-23:00) 
 ALL_DAY : Anytime on event date (00:00-23:59) 
 EXACT_TIME : Only during event start-end 
 AS_DAY_START : From day start (00:00) until event end + grace 
 
 Validation Statuses : 
 
 VALID : ✅ Entry granted 
 DUPLICATE : ❌ Already checked in for this day 
 INVALID_SIGNATURE : ❌ JWT signature failed 
 EXPIRED : ❌ Ticket validity expired 
 NOT_FOUND : ❌ Ticket not in database 
 REVOKED : ❌ Scanner revoked 
 
 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 : 
 
 One check-in expected 
 Status: ACTIVE → USED after check-in 
 
 Multi-Day Event (e.g., 3-day festival): 
 
 Multiple check-ins allowed (one per day) 
 Each day tracked separately 
 Ticket stays ACTIVE throughout 
 
 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 : 
 
 System finds current time: Saturday 11:00 
 Matches to event schedule: "Day 2 - Saturday" 
 Checks if ticket already checked in for "Day 2" 
 If no → Allow check-in 
 If yes → Reject as duplicate 
 
 
 Check-In Window Strategies 
 1. HOURS_BEFORE (Default) 
 Configuration : 
 
 earlyCheckInHours: 2 (default) 
 lateCheckInMinutes: 30 (default) 
 
 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 : 
 
 checkInOpensAt: "08:00" 
 checkInClosesAt: "23:00" 
 
 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 : 
 
 lateCheckInMinutes: 30 (default) 
 
 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 : 
 
 Scanner generates fingerprint from device hardware 
 Fingerprint sent with every request 
 System validates fingerprint matches registered device 
 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 : 
 
 Scanner receives ticket JWT (QR code) 
 Scanner verifies signature with event's public key 
 Scanner can validate offline (no internet needed) 
 Scanner sends to API for check-in recording 
 
 Benefits : 
 
 Offline validation capability 
 Cannot forge tickets 
 Cannot reuse tokens (duplicate tracking) 
 Tamper-proof (signature validation) 
 
 
 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 : 
 
 Device scanning tickets for multiple events simultaneously 
 Confusion about which event device is working 
 Credential misuse across events 
 
 Allows : 
 
 Device switching between events (auto-handled) 
 Fresh start for each event 
 Clean scanner sessions 
 
 
 Scanner Statistics 
 Auto-Tracked Metrics 
 
 totalScans : All scan attempts 
 successfulScans : Valid entries 
 failedScans : Duplicates, invalid tickets 
 lastScanAt : Most recent scan timestamp 
 lastSyncedAt : Last API contact 
 
 Success Rate Calculation 
 successRate = (successfulScans / totalScans) * 100
 
 Updated Automatically 
 
 Each validation attempt updates counters 
 Successful → successfulScans++, totalScans++ 
 Failed → failedScans++, totalScans++ 
 
 
 Error Codes Summary 
 Registration Token Errors 
 
 400 : Token expired/used, invalid format 
 403 : Not event organizer 
 404 : Token/event not found 
 422 : Event not published, no RSA keys 
 
 Scanner Registration Errors 
 
 400 : Invalid fingerprint (too short/long) 
 400 : Token expired/used 
 404 : Token not found 
 422 : Invalid scanner name 
 
 Ticket Validation Errors 
 
 INVALID_SIGNATURE : JWT signature failed 
 DUPLICATE : Already checked in for this day 
 EXPIRED : Ticket validity expired 
 NOT_FOUND : Ticket not in database 
 REVOKED : Scanner revoked 
 
 
 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 
 
 Registration token: 5 minutes 
 Scanner credentials: 1 year 
 Ticket JWT: Event validity period 
 
 Device Fingerprint 
 
 Min length: 10 characters 
 Max length: 255 characters 
 Format: Stable hash of device properties 
 
 Scanner Name 
 
 Min length: 3 characters 
 Max length: 200 characters 
 Examples: "Gate A", "VIP Entrance", "Main Hall Scanner" 
 
 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 : 
 
 Organizer Only : All endpoints restricted to event organizers 
 Auto-Calculations : Revenue, attendance, sell-out rates computed automatically 
 Escrow Tracking : Separate tracking for held vs released funds 
 Multi-Event : Aggregates data across all organizer's events 
 Time Filters : Filter by status, date range, year 
 Pagination : Event lists paginated (default 20 per page) 
 Top Performer : Identifies highest revenue event 
 Trend Analysis : Monthly/yearly revenue patterns 
 Real-Time : Updates reflect latest bookings and check-ins 
 
 
 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 : 
 
 Aggregates ALL organizer's events 
 Calculates escrow (PUBLISHED, HAPPENING events) 
 Calculates released (COMPLETED events) 
 Identifies top performer by revenue 
 Real-time metrics from latest data 
 
 Metrics Included : 
 
 Event counts by status 
 Total tickets sold 
 Total revenue (all time) 
 Money in escrow (upcoming/ongoing) 
 Money released (completed) 
 Refunds (placeholder) 
 Top performing event 
 
 
 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 : 
 
 Lists organizer's events with revenue details 
 Filters by status and date range 
 Sorted by event date (newest first) 
 Paginated results 
 Includes capacity and sell-out metrics 
 
 
 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 : 
 
 Validates organizer owns event 
 Calculates comprehensive metrics 
 Shows financial breakdown 
 Displays ticket sales vs capacity 
 Tracks attendance from check-ins 
 Provides event timeline 
 
 Metrics Sections : 
 
 Financials : Revenue, escrow, released, average price 
 Tickets : Capacity, sold, remaining, sell-out % 
 Attendance : Total, checked-in, no-shows, rate 
 Timeline : Key dates from creation to completion 
 
 Errors : 
 
 403 FORBIDDEN : Not event organizer 
 404 NOT_FOUND : Event not found 
 
 
 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 : 
 
 Shows all 12 months for specified year 
 Data per month: events, tickets, revenue, escrow, released 
 Calculates average attendance and sell-out rates 
 
 YEARLY Period : 
 
 Shows all years organizer has events 
 Data per year: total events, tickets, revenue 
 Year-over-year comparison 
 
 Each Period Includes : 
 
 Event count 
 Tickets sold 
 Total revenue 
 Money in escrow 
 Money released 
 Average attendance rate 
 Average sell-out rate 
 
 
 Key Calculations 
 Escrow vs Released 
 In Escrow (Not yet paid to organizer): 
 
 PUBLISHED events (upcoming) 
 HAPPENING events (ongoing) 
 Formula: Sum of booking totals for non-completed events 
 
 Released (Paid to organizer): 
 
 COMPLETED events only 
 Formula: Sum of booking totals for completed events 
 
 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 : 
 
 Total tickets: 500 
 Checked-in: 462 
 Attendance: 92.4% 
 
 Sell-Out Percentage 
 Sell-Out % = (Tickets Sold / Total Capacity) × 100
 
 Example : 
 
 Capacity: 1000 
 Sold: 500 
 Sell-out: 50% 
 
 Average Ticket Price 
 Average Price = Total Revenue / Total Tickets
 
 Example : 
 
 Revenue: 1,500,000 TZS 
 Tickets: 500 
 Average: 3,000 TZS 
 
 
 Event Status Flow 
 DRAFT → PUBLISHED → HAPPENING → COMPLETED
 ↓ ↓
CANCELLED (Revenue Released)
 
 Financial Impact by Status : 
 
 DRAFT : No bookings, no revenue 
 PUBLISHED : Bookings allowed, revenue in escrow 
 HAPPENING : Ongoing, revenue still in escrow 
 COMPLETED : Revenue released to organizer 
 CANCELLED : Refunds processed (if applicable) 
 
 
 Top Performer Logic 
 Selection Criteria : Highest total revenue 
 Metrics Included : 
 
 Event ID and title 
 Total revenue (highest wins) 
 Tickets sold 
 Attendance rate 
 
 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 
 
 200 OK : Successful request 
 401 UNAUTHORIZED : Authentication required 
 403 FORBIDDEN : Not event organizer 
 404 NOT_FOUND : Event not found 
 
 Date Formats 
 
 Event Date : LocalDateTime (2025-12-15T09:00:00) 
 Query Params : ISO Date (2025-01-01) 
 
 Currency 
 
 All amounts in TZS (Tanzanian Shilling) 
 Format: 1500000.00 (2 decimals) 
 
 Percentage Format 
 
 Format: 92.4 (1 decimal) 
 Range: 0.0 - 100.0 
 
 Pagination 
 
 Zero-indexed pages (0, 1, 2...) 
 Default size: 20 
 Max size: 100 (recommended) 
 
 
 Conclusion 
 The Organizer Analytics API provides comprehensive insights with: 
 ✅ Collection Summary : Total revenue, escrow, and top performers 
✅ Event Revenue : Filterable, paginated event list 
✅ Event Performance : Deep dive into individual event metrics 
✅ Revenue Trends : Monthly/yearly pattern analysis 
✅ Real-Time : Auto-calculated from latest bookings 
✅ Organizer-Focused : Restricted to event owners only

Events  Applicant Form API
Author : Josh S. Sakweli, Backend Lead Team
 Last Updated : 2026-05-22
 Version : v1.2 
 Base URL : https://your-api-domain.com/api/v1/e-events/applicant-form 
 Short Description : The Applicant Form API allows event organizers to attach an optional custom multi-page form to their event. When enabled, attendees are prompted to fill in the form as part of the registration flow. The form is fully scoped to the event — all endpoints use eventId as the primary key, so the organizer never needs to track a separate formId . The entire feature is optional; if never enabled, the event registration proceeds with no form step. 
 Hints : 
 
 All endpoints require a valid Bearer token. 
 Form setup and management (enable, pages, fields, options) require the authenticated user to be the event organizer . Any other user gets 403 . 
 Attendee submission endpoints ( /start , /pages/{pageId}/save , /submit , /my-response ) require the event to be PUBLISHED and within its registration window ( registrationOpensAt → registrationClosesAt ). 
 enableForm creates the underlying form with no pages . Pages must be added by the organizer via the page endpoints. The form must have at least one page before the event can be published. 
 Use GET /events/{eventId}/full-form to load the complete form state (all pages, fields, and options) in a single call — intended for the form builder UI. 
 startSubmission is idempotent — if the attendee already has a draft response it returns the existing one instead of creating a new one. 
 savePage accepts a ?moveToNextPage=false/true query parameter that controls validation behaviour. When false (default), answers are written to the draft with no validation — use this for background auto-save. When true , the page is validated first; if any field fails, errors are returned and nothing is saved; if all fields pass, answers are saved, the page is marked complete, and the index advances to the next page. This is the "Next" button action. 
 submit requires every page to have been completed via ?moveToNextPage=true at least once. Pages that were only auto-saved (without passing "Next" validation) will block submission with a PAGE_INCOMPLETE error. This ensures every page was validated before the form is accepted. 
 Delete endpoints accept a ?hard=false query parameter. Soft delete ( hard=false , default) preserves historical response data. Hard delete ( hard=true ) permanently removes the record. 
 DROPDOWN , RADIO , and CHECKBOX field types require options to be added separately after the field is created. 
 The HEADER field type is a display-only section divider — it has no required flag and stores no answer data. 
 
 
 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: 
 
 A form has one or more pages displayed in displayOrder . 
 Each page has one or more fields displayed in displayOrder . 
 Fields of type DROPDOWN , RADIO , or CHECKBOX must have options added after the field is created — no other field type has options. 
 HEADER is the only field type that stores no answer and cannot be marked required. It acts purely as a section label between other fields. 
 Every other field type ( TEXT , TEXTAREA , EMAIL , PHONE , NUMBER , URL , DATE , TIME , DATETIME , RATING , FILE ) stores exactly one answer per attendee. 
 
 
 Organizer Workflow 
 [Organizer]
 │
 │ POST /events/{eventId}/enable
 ▼
 ┌─────────────────────────────────────┐
 │ Form ENABLED (empty) │
 │ - Form created internally │
 │ - No pages yet (pages: []) │
 │ - Config saved (displayTime etc.) │
 └──────────────┬──────────────────────┘
 │
 │ Load builder state (on re-open):
 │ GET /events/{eventId}/full-form
 │
 │ Build the form structure:
 │
 │ POST /events/{eventId}/pages
 │ POST /events/{eventId}/pages/{pageId}/fields
 │ POST /events/{eventId}/fields/{fieldId}/options
 │ (DROPDOWN / RADIO / CHECKBOX only)
 │
 │ (Optional bulk / clone shortcuts)
 │ POST /events/{eventId}/pages/bulk
 │ POST /events/{eventId}/pages/{pageId}/fields/bulk
 │ POST /events/{eventId}/pages/{pageId}/clone
 │ POST /events/{eventId}/fields/{fieldId}/clone
 │
 │ Preview before publishing:
 │ GET /events/{eventId}/preview/metadata
 │ GET /events/{eventId}/preview/pages/{pageNumber}
 │ POST /events/{eventId}/preview/pages/{pageNumber}/validate
 ▼
 ┌─────────────────────────────────────┐
 │ Form ready — event can publish │
 │ ⚠ Publish blocked if 0 pages │
 └─────────────────────────────────────┘
 
 Attendee Submission Journey 
 [Attendee — event must be PUBLISHED and within registration window]
 │
 │ POST /events/{eventId}/start
 ▼
 ┌──────────────────────────────────────────────────┐
 │ Response created (DRAFT) │
 │ or existing draft returned (idempotent) │
 │ status: DRAFT, completedPageIds: [], index: 0 │
 └──────────────┬───────────────────────────────────┘
 │
 │ ┌──────────────────────────────────────────────────────────┐
 │ │ For each page (repeat until all pages completed): │
 │ │ │
 │ │ [Background auto-save while typing] │
 │ │ PUT /pages/{pageId}/save?moveToNextPage=false │
 │ │ → No validation. Saves whatever is there. Always 200. │
 │ │ → completedPageIds unchanged. │
 │ │ │
 │ │ [User clicks "Next"] │
 │ │ PUT /pages/{pageId}/save?moveToNextPage=true │
 │ │ ├─ Validation FAILS → returns errors, nothing │
 │ │ │ saved, page stays, success: false │
 │ │ └─ Validation PASSES → answers saved, page │
 │ │ added to completedPageIds, index advances │
 │ └──────────────────────────────────────────────────────────┘
 │
 │ POST /events/{eventId}/submit
 │ ├─ Any page NOT in completedPageIds → 422 PAGE_INCOMPLETE
 │ └─ All pages complete + required fields present → SUBMITTED
 ▼
 ┌──────────────────────────────────────────────────┐
 │ Response SUBMITTED │
 │ status: SUBMITTED, completionTimeSeconds set │
 └──────────────────────────────────────────────────┘
 
 Response Status Flow 
 DRAFT ──────────────────────────────► SUBMITTED
 │ │
 │ (withdraw — not in this API) │ (organizer action — outside scope)
 ▼ ▼
 WITHDRAWN UNDER_REVIEW → APPROVED / REJECTED
 
 
 Standard Response Format 
 All API responses follow a consistent structure using the Globe Response Builder pattern. 
 Success Response Structure 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Operation completed successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { }
}
 
 Error Response Structure 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Error description",
 "action_time": "2025-02-17T10:30:45",
 "data": "Error description"
}
 
 Standard Response Fields 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 success 
 boolean 
 true for successful operations, false for errors 
 
 
 httpStatus 
 string 
 HTTP status name (OK, CREATED, BAD_REQUEST, etc.) 
 
 
 message 
 string 
 Human-readable description of the result 
 
 
 action_time 
 string 
 ISO 8601 timestamp of when the response was generated 
 
 
 data 
 object / string 
 Response payload on success; error detail on failure 
 
 
 
 
 HTTP Method Badge Standards 
 
 GET — Green ( #28a745 ) — Safe, read-only operations 
 POST — Blue ( #007bff ) — Create new resources 
 PUT — Yellow ( #ffc107 , black text) — Update / replace 
 PATCH — Orange ( #fd7e14 ) — Partial update 
 DELETE — Red ( #dc3545 ) — Remove resource 
 
 
 Shared Response Object Definitions 
 A. ApplicantFormSetupResponse 
 Returned only by the Enable Form endpoint (Endpoint 1). 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 config 
 object 
 The saved EventApplicantFormEntity — see config fields below 
 
 
 config.id 
 UUID 
 Config record ID 
 
 
 config.formId 
 UUID 
 Internal Form Builder form ID (not needed for subsequent API calls) 
 
 
 config.displayTime 
 string 
 BEFORE_CHECKOUT or AFTER_CHECKOUT 
 
 
 config.isRequiredOnline 
 boolean 
 Whether online buyers must complete the form 
 
 
 config.applyToAtDoor 
 boolean 
 Whether walk-in attendees see the form 
 
 
 config.createdAt 
 ZonedDateTime 
 Config creation timestamp 
 
 
 config.updatedAt 
 ZonedDateTime 
 Last update timestamp 
 
 
 form 
 object 
 Full FormResponse — see FormResponse definition below 
 
 
 
 B. EventApplicantFormEntity (Config Object) 
 Returned by the Update Settings endpoint. 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 id 
 UUID 
 Config record ID 
 
 
 formId 
 UUID 
 Internal Form Builder form ID 
 
 
 displayTime 
 string 
 BEFORE_CHECKOUT or AFTER_CHECKOUT 
 
 
 isRequiredOnline 
 boolean 
 Whether online buyers must complete the form 
 
 
 applyToAtDoor 
 boolean 
 Whether walk-in attendees see the form 
 
 
 createdAt 
 ZonedDateTime 
 ISO 8601 with offset 
 
 
 updatedAt 
 ZonedDateTime 
 ISO 8601 with offset 
 
 
 
 C. FormResponse (Full Form Object) 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 formId 
 UUID 
 Form identifier (Form Builder internal ID) 
 
 
 title 
 string 
 Auto-set to "Attendee Questions - {event title}" on enable 
 
 
 description 
 string 
 Form description 
 
 
 settings 
 object 
 Form open/close settings (see FormSettings below) 
 
 
 settings.acceptResponses 
 boolean 
 Whether the form is accepting responses 
 
 
 settings.allowMultipleSubmissions 
 boolean 
 Whether a user can submit more than once 
 
 
 settings.responseStartTime 
 Instant 
 UTC datetime when responses open 
 
 
 settings.responseDeadline 
 Instant 
 UTC datetime when responses close 
 
 
 settings.allowSaveDraft 
 boolean 
 Whether respondents can save progress 
 
 
 coverPage 
 object 
 Optional cover page config 
 
 
 createdBy 
 string 
 Username of creator 
 
 
 createdAt 
 LocalDateTime 
 Creation timestamp 
 
 
 pages[] 
 array 
 Ordered list of PageResponse objects 
 
 
 
 D. PageResponse 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 pageId 
 UUID 
 Page identifier 
 
 
 title 
 string 
 Page heading shown to attendee 
 
 
 description 
 string 
 Subheading or instruction text 
 
 
 displayOrder 
 integer 
 1-based position within the form 
 
 
 actionButtonText 
 string 
 Label for the next/submit button 
 
 
 fields[] 
 array 
 Ordered list of FieldResponse objects (soft-deleted excluded) 
 
 
 
 E. FieldResponse 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 fieldId 
 UUID 
 Field identifier 
 
 
 type 
 string 
 TEXT , TEXTAREA , EMAIL , PHONE , NUMBER , URL , DATE , TIME , DATETIME , DROPDOWN , RADIO , CHECKBOX , FILE , RATING , HEADER 
 
 
 label 
 string 
 Field label. For HEADER type this is the section title 
 
 
 description 
 string 
 Helper text shown below the field 
 
 
 placeholder 
 string 
 Input placeholder text 
 
 
 displayOrder 
 integer 
 1-based order within the page 
 
 
 required 
 boolean 
 Whether the field must be answered. Always false for HEADER 
 
 
 validation 
 object 
 See FieldValidation below 
 
 
 options[] 
 array 
 OptionResponse entries — only for DROPDOWN , RADIO , CHECKBOX 
 
 
 
 FieldValidation Object 
 
 
 
 Field 
 Applicable To 
 Description 
 
 
 
 
 minLength / maxLength 
 TEXT , TEXTAREA 
 Character count constraints 
 
 
 pattern / patternMessage 
 TEXT 
 Regex pattern and custom error message 
 
 
 min / max 
 NUMBER 
 Numeric range 
 
 
 minDate / maxDate 
 DATE 
 Date range ( YYYY-MM-DD ) 
 
 
 minSelections / maxSelections 
 CHECKBOX 
 Selection count constraints 
 
 
 maxSizeMb / accept 
 FILE 
 File size limit and accepted MIME types 
 
 
 
 F. OptionResponse 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 optionId 
 UUID 
 Option identifier 
 
 
 label 
 string 
 Display label shown to attendee 
 
 
 displayOrder 
 integer 
 1-based order within the field 
 
 
 
 G. FormResponseObject (Attendee Submission) 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 responseId 
 UUID 
 Unique response identifier 
 
 
 formId 
 UUID 
 Internal form ID 
 
 
 submittedBy 
 string 
 Username of the attendee 
 
 
 status 
 string 
 DRAFT , SUBMITTED , UNDER_REVIEW , APPROVED , REJECTED , WITHDRAWN 
 
 
 completedPageIds 
 array 
 UUIDs of pages marked complete 
 
 
 currentPageIndex 
 integer 
 0-based index of current page 
 
 
 startedAt 
 LocalDateTime 
 When the attendee started the form 
 
 
 submittedAt 
 LocalDateTime 
 When the response was submitted 
 
 
 completionTimeSeconds 
 integer 
 Total seconds from start to submit 
 
 
 answers[] 
 array 
 See AnswerResponse below 
 
 
 
 AnswerResponse 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 answerId 
 UUID 
 Answer identifier 
 
 
 fieldId 
 UUID 
 Field ID (null if field was hard-deleted) 
 
 
 fieldLabel 
 string 
 Label snapshot — preserved even if field is later deleted 
 
 
 fieldType 
 string 
 Type snapshot — preserved even if field is later deleted 
 
 
 fieldDeleted 
 boolean 
 true when the source field has been soft-deleted 
 
 
 value 
 any 
 Submitted value. String for text/single-choice, array of strings for checkbox, number for NUMBER/RATING 
 
 
 answeredAt 
 LocalDateTime 
 When this answer was saved 
 
 
 fileUrl / fileName / fileSize / fileType 
 various 
 File upload metadata (FILE fields only) 
 
 
 
 H. BulkOperationResult 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 successCount 
 integer 
 Number of items successfully processed 
 
 
 failureCount 
 integer 
 Number of items that failed 
 
 
 errors[] 
 array 
 Error message per failed item 
 
 
 createdFields[] 
 array 
 FieldResponse objects (bulk field operations) 
 
 
 createdPages[] 
 array 
 PageResponse objects (bulk page operations) 
 
 
 
 I. BulkDeleteResult 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 successCount 
 integer 
 Number of items successfully deleted 
 
 
 failureCount 
 integer 
 Number of items that failed 
 
 
 errors[] 
 array 
 Error message per failed item 
 
 
 deletedIds[] 
 array 
 UUIDs of successfully deleted items 
 
 
 
 J. SavePageResponse 
 Returned by the Save Page Answers endpoint (Endpoint 27). The shape of the response is the same whether the save succeeded or validation failed — the client always reads saved and isValid to decide what to do next. 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 eventId 
 UUID 
 The event ID 
 
 
 saved 
 boolean 
 true if answers were actually written to the database. false if validation failed and nothing was saved 
 
 
 savedAt 
 LocalDateTime 
 Timestamp of the save. null when saved is false 
 
 
 isValid 
 boolean 
 true if all fields on the page passed validation. Mirrors saved 
 
 
 errors 
 array / null 
 null when isValid is true . Array of FieldValidationError when validation failed 
 
 
 errors[].fieldId 
 UUID 
 The field that failed validation 
 
 
 errors[].fieldLabel 
 string 
 Label of the failing field (e.g. "Email" ) 
 
 
 errors[].errorCode 
 string 
 Machine-readable error type: REQUIRED , INVALID_FORMAT , INVALID_TYPE , VALIDATION_FAILED 
 
 
 errors[].message 
 string 
 Human-readable error description (e.g. "must be valid email" ) 
 
 
 
 
 Note : totalFieldsOnPage , answeredFieldsOnPage , and overallProgress are reserved fields in the response object but are not currently populated — they will be null and omitted from the JSON ( @JsonInclude(NON_NULL) ). 
 
 K. FormAnalytics 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 formId 
 UUID 
 Form identifier 
 
 
 formTitle 
 string 
 Form title 
 
 
 stats.totalStarted 
 integer 
 Total responses started (including drafts) 
 
 
 stats.totalDrafts 
 integer 
 Responses still in DRAFT 
 
 
 stats.totalSubmitted 
 integer 
 Responses with SUBMITTED status 
 
 
 stats.totalWithdrawn 
 integer 
 Withdrawn responses 
 
 
 stats.completionRate 
 double 
 Submitted / totalStarted × 100 
 
 
 stats.dropOffRate 
 double 
 Inverse of completion rate 
 
 
 stats.avgCompletionTimeSeconds 
 double 
 Mean time from start to submit 
 
 
 stats.fastestTimeSeconds 
 integer 
 Fastest submission time 
 
 
 stats.slowestTimeSeconds 
 integer 
 Slowest submission time 
 
 
 fieldAnalytics[] 
 array 
 Per-field breakdown 
 
 
 fieldAnalytics[].fieldId 
 UUID 
 Field ID 
 
 
 fieldAnalytics[].fieldLabel 
 string 
 Field label snapshot 
 
 
 fieldAnalytics[].fieldType 
 string 
 Field type snapshot 
 
 
 fieldAnalytics[].fieldDeleted 
 boolean 
 Whether field is soft-deleted 
 
 
 fieldAnalytics[].totalResponses 
 integer 
 Total answers for this field 
 
 
 fieldAnalytics[].choiceDistribution[] 
 array 
 { option, count, percentage } — choice fields 
 
 
 fieldAnalytics[].numericStats 
 object 
 { min, max, avg, median } — NUMBER/RATING fields 
 
 
 fieldAnalytics[].textResponses[] 
 array 
 Raw text answers — TEXT/TEXTAREA fields 
 
 
 dailySubmissions[] 
 array 
 { date, count } — submissions per day 
 
 
 
 
 Standard Error Types 
 Application-Level Exceptions (400–499) 
 
 400 BAD_REQUEST — Form already enabled for this event, field type does not support options, already submitted 
 401 UNAUTHORIZED — Missing, expired, or malformed Bearer token 
 403 FORBIDDEN — Authenticated but not the event organizer; or registration window not open/already closed 
 404 NOT_FOUND — Event not found, form not enabled, page/field/option not found, no response found 
 422 UNPROCESSABLE_ENTITY — Bean validation failures; or submit called when one or more pages were not completed via ?moveToNextPage=true ( PAGE_INCOMPLETE error type); or required field still unanswered at submit time ( REQUIRED error type). For save validation failures ( ?moveToNextPage=true ), the response returns HTTP 200 with success: false — not a 422 — because the client needs to display inline errors without treating it as a hard failure 
 
 Server-Level Exceptions (500+) 
 
 500 INTERNAL_SERVER_ERROR — Unexpected runtime error 
 
 
 Shared Error Response Examples 
 401 — Unauthorized: 
 {
 "success": false,
 "httpStatus": "UNAUTHORIZED",
 "message": "Token has expired",
 "action_time": "2025-02-17T10:30:45",
 "data": "Token has expired"
}
 
 403 — Forbidden (not organizer): 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "Only organizer can manage form",
 "action_time": "2025-02-17T10:30:45",
 "data": "Only organizer can manage form"
}
 
 403 — Forbidden (registration closed): 
 {
 "success": false,
 "httpStatus": "FORBIDDEN",
 "message": "Registration closed",
 "action_time": "2025-02-17T10:30:45",
 "data": "Registration closed"
}
 
 404 — Form not enabled: 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Form not enabled",
 "action_time": "2025-02-17T10:30:45",
 "data": "Form not enabled"
}
 
 
 Endpoints 
 
 Form Setup 
 
 1. Enable Applicant Form 
 Purpose : Enables the applicant form for an event. Internally creates a Form Builder form titled "Attendee Questions - {event title}" with no pages. Pages are added separately by the organizer. Only the event organizer can call this. Returns the empty form structure alongside the config. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/enable 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event to enable the form for 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 displayTime 
 string 
 No 
 When attendees see the form in the UI flow 
 Enum: BEFORE_CHECKOUT , AFTER_CHECKOUT . Defaults to BEFORE_CHECKOUT 
 
 
 isRequiredOnline 
 boolean 
 No 
 Whether online ticket buyers must complete the form before proceeding 
 Defaults to false 
 
 
 applyToAtDoor 
 boolean 
 No 
 Whether at-door / walk-in attendees also see the form 
 Defaults to false 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Applicant form enabled",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "config": {
 "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false,
 "createdAt": "2025-02-17T10:30:45+03:00",
 "updatedAt": null
 },
 "form": {
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
 "description": null,
 "settings": null,
 "coverPage": null,
 "createdBy": "amina.hassan",
 "createdAt": "2025-02-17T10:30:45",
 "pages": []
 }
 }
}
 
 Success Response Fields : data is an ApplicantFormSetupResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer, or form is already enabled for this event 
 
 
 404 
 Event not found 
 
 
 
 
 2. Update Form Settings 
 Purpose : Updates the form configuration for the event — displayTime , isRequiredOnline , and applyToAtDoor . Does not affect the form structure (pages/fields). All fields are optional. 
 Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/settings 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "displayTime": "AFTER_CHECKOUT",
 "isRequiredOnline": false,
 "applyToAtDoor": true
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 displayTime 
 string 
 No 
 Updated display time 
 Enum: BEFORE_CHECKOUT , AFTER_CHECKOUT 
 
 
 isRequiredOnline 
 boolean 
 No 
 Updated required flag for online buyers 
 — 
 
 
 applyToAtDoor 
 boolean 
 No 
 Updated at-door flag 
 — 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Form settings updated",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "displayTime": "AFTER_CHECKOUT",
 "isRequiredOnline": false,
 "applyToAtDoor": true,
 "createdAt": "2025-02-17T10:30:45+03:00",
 "updatedAt": "2025-02-17T11:00:00+03:00"
 }
}
 
 Success Response Fields : data is an EventApplicantFormEntity config object . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 3. Disable Form 
 Purpose : Disables and permanently removes the applicant form from the event. The underlying Form Builder form is soft-deleted. Existing submitted responses are preserved. This action cannot be undone — to re-enable a form, call Endpoint 1 again which will create a fresh form. 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/disable 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Applicant form disabled",
 "action_time": "2025-02-17T10:30:45",
 "data": null
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 4. Get Full Form 
 Purpose : Returns the complete form structure — all pages, fields, and options — in a single call. Intended for the form builder UI to load the current state when the organizer opens the builder. Only the event organizer can call this. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/full-form 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Form retrieved",
 "action_time": "2026-05-21T10:30:45",
 "data": {
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
 "description": null,
 "settings": null,
 "coverPage": null,
 "createdBy": "amina.hassan",
 "createdAt": "2026-05-21T10:30:45",
 "pages": [
 {
 "pageId": "1a2b3c4d-...",
 "title": "Personal Details",
 "description": null,
 "displayOrder": 1,
 "actionButtonText": "Next",
 "fields": [
 {
 "fieldId": "c3d4e5f6-...",
 "type": "TEXT",
 "label": "Full Name",
 "description": "As it appears on your ID.",
 "placeholder": "e.g. Amina Hassan",
 "displayOrder": 1,
 "required": true,
 "validation": { "minLength": 2, "maxLength": 100 },
 "options": []
 },
 {
 "fieldId": "d4e5f6a7-...",
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "displayOrder": 2,
 "required": true,
 "validation": null,
 "options": [
 { "optionId": "a1b2c3d4-...", "label": "Small (S)", "displayOrder": 1 },
 { "optionId": "b2c3d4e5-...", "label": "Medium (M)", "displayOrder": 2 }
 ]
 }
 ]
 },
 {
 "pageId": "2b3c4d5e-...",
 "title": "Dietary Requirements",
 "displayOrder": 2,
 "actionButtonText": "Submit",
 "fields": []
 }
 ]
 }
}
 
 Success Response Fields : data is a FormResponse with all pages, fields, and options fully nested. Soft-deleted items are excluded. Pages and fields are ordered by displayOrder . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 Page Management 
 
 4. Add Page 
 Purpose : Appends a new blank page to the form. displayOrder is auto-assigned as the next position. Fields are added to the page separately. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "title": "Emergency Contact",
 "description": "In case we need to reach someone on your behalf.",
 "actionButtonText": "Next"
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 title 
 string 
 Yes 
 Page heading shown to attendee 
 Max: 255 characters 
 
 
 description 
 string 
 No 
 Subheading or instruction text 
 Max: 500 characters 
 
 
 actionButtonText 
 string 
 No 
 Label for the page's action button 
 Max: 50 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Page added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "pageId": "2b3c4d5e-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
 "title": "Emergency Contact",
 "description": "In case we need to reach someone on your behalf.",
 "displayOrder": 2,
 "actionButtonText": "Next",
 "fields": []
 }
}
 
 Success Response Fields : data is a PageResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 422 
 title is blank 
 
 
 
 
 5. Update Page 
 Purpose : Updates the title, description, or action button label of an existing page. 
 Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "title": "Your Background",
 "actionButtonText": "Continue"
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 title 
 string 
 No 
 Updated page title 
 Max: 255 characters 
 
 
 description 
 string 
 No 
 Updated description 
 Max: 500 characters 
 
 
 actionButtonText 
 string 
 No 
 Updated button label 
 Max: 50 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "PageResponse object" }
}
 
 Success Response Fields : data is a PageResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or page not found 
 
 
 
 
 6. Delete Page 
 Purpose : Deletes a page. Pass ?hard=false (default) for soft delete — the page is hidden but historical answer data is preserved. Pass ?hard=true to permanently remove the page and all its fields. 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page to delete 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete, false = soft delete 
 — 
 false 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page deleted",
 "action_time": "2025-02-17T10:30:45",
 "data": null
}
 
 
 When hard=true , message is "Page permanently deleted" . 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or page not found 
 
 
 
 
 7. Bulk Add Pages (with Fields) 
 Purpose : Creates multiple pages in one call. Each page can optionally include an inline list of fields. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "pages": [
 {
 "title": "Personal Details",
 "actionButtonText": "Next",
 "fields": [
 { "type": "TEXT", "label": "Full Name", "required": true },
 { "type": "EMAIL", "label": "Email", "required": true }
 ]
 },
 {
 "title": "Dietary Requirements",
 "actionButtonText": "Submit",
 "fields": [
 { "type": "DROPDOWN", "label": "Dietary preference", "required": false }
 ]
 }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 pages 
 array 
 Yes 
 Pages to create 
 Min 1 item 
 
 
 pages[].title 
 string 
 Yes 
 Page title 
 Max: 255 characters 
 
 
 pages[].description 
 string 
 No 
 Page description 
 Max: 500 characters 
 
 
 pages[].actionButtonText 
 string 
 No 
 Button label 
 Max: 50 characters 
 
 
 pages[].fields 
 array 
 No 
 Fields to create inline 
 See CreateField below 
 
 
 pages[].fields[].type 
 string 
 Yes 
 Field type 
 See FieldType enum 
 
 
 pages[].fields[].label 
 string 
 Yes 
 Field label 
 Max: 255 characters 
 
 
 pages[].fields[].description 
 string 
 No 
 Helper text 
 Max: 500 characters 
 
 
 pages[].fields[].placeholder 
 string 
 No 
 Placeholder text 
 Max: 255 characters 
 
 
 pages[].fields[].required 
 boolean 
 No 
 Required flag 
 Defaults to false 
 
 
 pages[].fields[].validation 
 object 
 No 
 Validation rules 
 See FieldValidation 
 
 
 
 
 Note : For DROPDOWN , RADIO , and CHECKBOX fields created via bulk, options must still be added separately using the option endpoints after this call. 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "2 pages added, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": [],
 "createdPages": [
 { "...": "PageResponse for page 1" },
 { "...": "PageResponse for page 2" }
 ]
 }
}
 
 Success Response Fields : data is a BulkOperationResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 8. Bulk Delete Pages 
 Purpose : Deletes multiple pages in one call. Supports both soft delete (default) and hard delete via ?hard query param. Items are processed individually — a failure on one does not stop the rest. 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete 
 — 
 false 
 
 
 
 Request JSON Sample : 
 {
 "ids": ["pageId-1", "pageId-2"]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ids 
 array 
 Yes 
 UUIDs of pages to delete 
 Min 1 item 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "2 pages deleted, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": [],
 "deletedIds": ["pageId-1", "pageId-2"]
 }
}
 
 Success Response Fields : data is a BulkDeleteResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found. Individual page errors reported in data.errors[] 
 
 
 
 
 9. Clone Page 
 Purpose : Creates an exact copy of an existing page including all its active fields and options. The clone is appended at the end of the form with "(Copy)" added to the title. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/clone 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page to clone 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Page cloned successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "PageResponse for the cloned page" }
}
 
 Success Response Fields : data is a PageResponse for the new clone. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or page not found 
 
 
 
 
 Field Management 
 
 10. Add Field 
 Purpose : Adds a single field to a page. displayOrder is auto-assigned. For DROPDOWN , RADIO , and CHECKBOX types, options can be passed inline in the same request or added separately via the Add Option endpoint.
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields 
 Access Level : 🔒 Protected (Event organizer only)
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page to add the field to 
 Must be a valid UUID 
 
 
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 type 
 string 
 Yes 
 Field type 
 Enum: TEXT , TEXTAREA , EMAIL , PHONE , NUMBER , URL , DATE , TIME , DATETIME , DROPDOWN , RADIO , CHECKBOX , FILE , RATING , HEADER 
 
 
 label 
 string 
 Yes 
 Field label (or section title for HEADER ) 
 Max: 255 characters 
 
 
 description 
 string 
 No 
 Helper text 
 Max: 500 characters 
 
 
 placeholder 
 string 
 No 
 Input placeholder 
 Max: 255 characters 
 
 
 required 
 boolean 
 No 
 Required flag. Forced false for HEADER 
 Defaults to false 
 
 
 validation 
 object 
 No 
 Validation rules 
 See FieldValidation 
 
 
 options 
 array 
 No 
 Inline options for DROPDOWN , RADIO , CHECKBOX . Ignored for other types. Blank labels are skipped 
 — 
 
 
 options[].label 
 string 
 Yes (if options provided) 
 Option label 
 Max: 255 characters 
 
 
 
 
 Example 1 — TEXT field with validation 
 Request : 
 {
 "type": "TEXT",
 "label": "Full Name",
 "description": "As it appears on your ID.",
 "placeholder": "e.g. Amina Hassan",
 "required": true,
 "validation": {
 "minLength": 2,
 "maxLength": 100
 }
}
 
 Response : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "fieldId": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
 "type": "TEXT",
 "label": "Full Name",
 "description": "As it appears on your ID.",
 "placeholder": "e.g. Amina Hassan",
 "displayOrder": 1,
 "required": true,
 "validation": { "minLength": 2, "maxLength": 100 },
 "options": []
 }
}
 
 
 Example 2 — DROPDOWN field with inline options 
 Request : 
 {
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "description": "Select your preferred size.",
 "placeholder": "Choose a size",
 "required": true,
 "options": [
 { "label": "Small (S)" },
 { "label": "Medium (M)" },
 { "label": "Large (L)" },
 { "label": "Extra Large (XL)" }
 ]
}
 
 Response : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "fieldId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "description": "Select your preferred size.",
 "placeholder": "Choose a size",
 "displayOrder": 2,
 "required": true,
 "validation": null,
 "options": [
 { "optionId": "a1b2c3d4-...", "label": "Small (S)", "displayOrder": 1 },
 { "optionId": "b2c3d4e5-...", "label": "Medium (M)", "displayOrder": 2 },
 { "optionId": "c3d4e5f6-...", "label": "Large (L)", "displayOrder": 3 },
 { "optionId": "d4e5f6a7-...", "label": "Extra Large (XL)", "displayOrder": 4 }
 ]
 }
}
 
 
 Example 3 — RADIO field with inline options 
 Request : 
 {
 "type": "RADIO",
 "label": "What is your attendance mode?",
 "description": "Select how you will attend the event.",
 "required": true,
 "options": [
 { "label": "In Person" },
 { "label": "Online" }
 ]
}
 
 Response : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "fieldId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
 "type": "RADIO",
 "label": "What is your attendance mode?",
 "description": "Select how you will attend the event.",
 "placeholder": null,
 "displayOrder": 3,
 "required": true,
 "validation": null,
 "options": [
 { "optionId": "e5f6a7b8-...", "label": "In Person", "displayOrder": 1 },
 { "optionId": "f6a7b8c9-...", "label": "Online", "displayOrder": 2 }
 ]
 }
}
 
 
 Example 4 — CHECKBOX field with inline options and validation 
 Request : 
 {
 "type": "CHECKBOX",
 "label": "Which sessions will you attend?",
 "description": "Select all that apply. You may choose up to 3.",
 "required": false,
 "validation": {
 "minSelections": 1,
 "maxSelections": 3
 },
 "options": [
 { "label": "Morning Session (9am - 12pm)" },
 { "label": "Afternoon Session (1pm - 4pm)" },
 { "label": "Evening Session (6pm - 9pm)" }
 ]
}
 
 Response : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "fieldId": "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c",
 "type": "CHECKBOX",
 "label": "Which sessions will you attend?",
 "description": "Select all that apply. You may choose up to 3.",
 "placeholder": null,
 "displayOrder": 4,
 "required": false,
 "validation": { "minSelections": 1, "maxSelections": 3 },
 "options": [
 { "optionId": "g7a8b9c0-...", "label": "Morning Session (9am - 12pm)", "displayOrder": 1 },
 { "optionId": "h8b9c0d1-...", "label": "Afternoon Session (1pm - 4pm)", "displayOrder": 2 },
 { "optionId": "i9c0d1e2-...", "label": "Evening Session (6pm - 9pm)", "displayOrder": 3 }
 ]
 }
}
 
 
 Example 5 — HEADER field (section divider) 
 Request : 
 {
 "type": "HEADER",
 "label": "Emergency Contact Information"
}
 
 Response : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "fieldId": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
 "type": "HEADER",
 "label": "Emergency Contact Information",
 "description": null,
 "placeholder": null,
 "displayOrder": 5,
 "required": false,
 "validation": null,
 "options": []
 }
}
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or page not found 
 
 
 422 
 type is null, or label is blank 
 
 
 
 
 11. Update Field 
 Purpose : Updates the metadata of an existing field. All fields are optional. Field type cannot be changed — if a different type is needed, delete the field and create a new one. Options are managed via dedicated option endpoints.
 Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId} 
 Access Level : 🔒 Protected (Event organizer only)
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 fieldId 
 UUID 
 Yes 
 The field to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "label": "Legal Full Name",
 "required": true,
 "validation": {
 "minLength": 3,
 "maxLength": 150
 }
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 label 
 string 
 No 
 Updated label 
 Max: 255 characters 
 
 
 description 
 string 
 No 
 Updated helper text 
 Max: 500 characters 
 
 
 placeholder 
 string 
 No 
 Updated placeholder 
 Max: 255 characters 
 
 
 required 
 boolean 
 No 
 Updated required flag. Forced false if type is HEADER 
 — 
 
 
 validation 
 object 
 No 
 Updated validation rules (full replacement) 
 See FieldValidation 
 
 
 
 
 ⚠️ Note: type cannot be changed via this endpoint. Attempting to pass a different type will return a 400 error with the message: "Field type cannot be changed. Delete this field and create a new one with the correct type." To change the field type, delete this field and create a new one. 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Field updated",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "FieldResponse object" }
}
 
 Success Response Fields : data is a FieldResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or field not found 
 
 
 400 
 Attempting to change field type 
 
 
 
 
 12. Delete Field 
 Purpose : Deletes a field. Soft delete (default) preserves historical answer snapshots. Hard delete permanently removes the field record. 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 fieldId 
 UUID 
 Yes 
 The field to delete 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete 
 — 
 false 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Field deleted",
 "action_time": "2025-02-17T10:30:45",
 "data": null
}
 
 
 When hard=true , message is "Field permanently deleted" . 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or field not found 
 
 
 
 
 13. Bulk Add Fields 
 Purpose : Adds multiple fields to a page in one call. Fields are appended after existing fields in array order. For DROPDOWN , RADIO , and CHECKBOX types, options can be passed inline per field.
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk 
 Access Level : 🔒 Protected (Event organizer only)
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page to add fields to 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "fields": [
 { "type": "EMAIL", "label": "Email Address", "required": true },
 { "type": "PHONE", "label": "Phone Number", "required": false },
 { "type": "HEADER", "label": "Travel Details" },
 {
 "type": "DROPDOWN",
 "label": "Arrival Method",
 "required": true,
 "options": [
 { "label": "Flight" },
 { "label": "Bus" },
 { "label": "Car" }
 ]
 }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 fields 
 array 
 Yes 
 Fields to create 
 Min 1 item 
 
 
 fields[].type 
 string 
 Yes 
 Field type 
 See FieldType enum 
 
 
 fields[].label 
 string 
 Yes 
 Field label 
 Max: 255 characters 
 
 
 fields[].description 
 string 
 No 
 Helper text 
 Max: 500 characters 
 
 
 fields[].placeholder 
 string 
 No 
 Placeholder text 
 Max: 255 characters 
 
 
 fields[].required 
 boolean 
 No 
 Required flag 
 Defaults to false 
 
 
 fields[].validation 
 object 
 No 
 Validation rules 
 See FieldValidation 
 
 
 fields[].options 
 array 
 No 
 Inline options for DROPDOWN , RADIO , CHECKBOX . Ignored for other types. Blank labels are skipped 
 — 
 
 
 fields[].options[].label 
 string 
 Yes (if options provided) 
 Option label 
 Max: 255 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "4 fields added, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 4,
 "failureCount": 0,
 "errors": [],
 "createdFields": [ "[ FieldResponse objects ]" ]
 }
}
 
 Success Response Fields : data is a BulkOperationResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or page not found 
 
 
 
 
 14. Bulk Delete Fields 
 Purpose : Deletes multiple fields from a page in one call. Supports soft and hard delete via ?hard . 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page containing the fields 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete 
 — 
 false 
 
 
 
 Request JSON Sample : 
 {
 "ids": ["fieldId-1", "fieldId-2"]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ids 
 array 
 Yes 
 UUIDs of fields to delete 
 Min 1 item 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "2 fields deleted, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": [],
 "deletedIds": ["fieldId-1", "fieldId-2"]
 }
}
 
 Success Response Fields : data is a BulkDeleteResult . 
 Possible Error Responses : Same as Endpoint 8 (401, 403, 404). 
 
 15. Bulk Update Fields 
 Purpose : Updates required flag and/or validation rules across multiple fields at once. 
 Endpoint : PATCH /api/v1/e-events/applicant-form/events/{eventId}/fields/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "fieldIds": ["fieldId-1", "fieldId-2"],
 "required": true,
 "validation": { "maxLength": 200 }
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 fieldIds 
 array 
 Yes 
 UUIDs of fields to update 
 Min 1 item 
 
 
 required 
 boolean 
 No 
 New required value for all listed fields 
 — 
 
 
 validation 
 object 
 No 
 New validation rules for all listed fields (full replacement) 
 See FieldValidation 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "2 fields updated, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": [],
 "createdFields": [ "[ Updated FieldResponse objects ]" ]
 }
}
 
 Success Response Fields : data is a BulkOperationResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 One or more field IDs not found — reported in data.errors[] 
 
 
 
 
 16. Clone Field 
 Purpose : Creates a copy of a field (with its options) and appends it to a target page. Label gets "(Copy)" appended. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/clone 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 fieldId 
 UUID 
 Yes 
 The field to clone 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 targetPageId 
 UUID 
 Yes 
 The page to clone the field into 
 Must be a valid UUID 
 — 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field cloned successfully",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "FieldResponse for the cloned field" }
}
 
 Success Response Fields : data is a FieldResponse for the new clone. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found, field not found, or target page not found 
 
 
 
 
 Option Management 
 
 17. Add Option 
 Purpose : Adds a choice option to a DROPDOWN , RADIO , or CHECKBOX field. Calling this on any other field type returns 400 . 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 fieldId 
 UUID 
 Yes 
 The field to add an option to 
 Must be DROPDOWN , RADIO , or CHECKBOX type 
 
 
 
 Request JSON Sample : 
 {
 "label": "By Car"
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 label 
 string 
 Yes 
 Display label for this option 
 Max: 255 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Option added",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "optionId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
 "label": "By Car",
 "displayOrder": 1
 }
}
 
 Success Response Fields : data is an OptionResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 400 
 Field type does not support options 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or field not found 
 
 
 422 
 label is blank 
 
 
 
 
 18. Update Option 
 Purpose : Updates the label of an existing option. 
 Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/options/{optionId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 optionId 
 UUID 
 Yes 
 The option to update 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "label": "Public Transport"
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 label 
 string 
 No 
 Updated label 
 Max: 255 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Option updated",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "optionId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
 "label": "Public Transport",
 "displayOrder": 1
 }
}
 
 Success Response Fields : data is an OptionResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or option not found 
 
 
 
 
 19. Delete Option 
 Purpose : Deletes an option. Soft delete (default) preserves previously selected answer values. Hard delete permanently removes the option record. 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/options/{optionId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 optionId 
 UUID 
 Yes 
 The option to delete 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete 
 — 
 false 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Option deleted",
 "action_time": "2025-02-17T10:30:45",
 "data": null
}
 
 
 When hard=true , message is "Option permanently deleted" . 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or option not found 
 
 
 
 
 20. Bulk Delete Options 
 Purpose : Deletes multiple options from a field in one call. Supports soft and hard delete via ?hard . 
 Endpoint : DELETE /api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 fieldId 
 UUID 
 Yes 
 The field containing the options 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 hard 
 boolean 
 No 
 true = permanent delete 
 — 
 false 
 
 
 
 Request JSON Sample : 
 {
 "ids": ["optionId-1", "optionId-2"]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 ids 
 array 
 Yes 
 UUIDs of options to delete 
 Min 1 item 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "2 options deleted, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": [],
 "deletedIds": ["optionId-1", "optionId-2"]
 }
}
 
 Success Response Fields : data is a BulkDeleteResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or field not found. Individual errors in data.errors[] 
 
 
 
 
 21. Bulk Update Options 
 Purpose : Updates the labels of multiple options in one call. 
 Endpoint : PATCH /api/v1/e-events/applicant-form/events/{eventId}/options/bulk 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Request JSON Sample : 
 {
 "options": [
 { "optionId": "optionId-1", "label": "Train" },
 { "optionId": "optionId-2", "label": "Bus" }
 ]
}
 
 Request Body Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 options 
 array 
 Yes 
 List of option updates 
 Min 1 item 
 
 
 options[].optionId 
 UUID 
 Yes 
 The option to update 
 Must be a valid UUID 
 
 
 options[].label 
 string 
 No 
 New label 
 Max: 255 characters 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "2 options updated, 0 failed",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "successCount": 2,
 "failureCount": 0,
 "errors": []
 }
}
 
 Success Response Fields : data is a BulkOperationResult . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 An option ID not found — reported in data.errors[] 
 
 
 
 
 Preview 
 
 23. Get Preview Metadata 
 Purpose : Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used by the attendee to render a progress indicator before filling. Also returns the attendee's current response state ( responseId , currentPageIndex , completedPageIds ) if they have an existing draft. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/metadata 
 Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Metadata retrieved",
 "data": {
 "formId": "uuid",
 "formTitle": "Attendee Questions - Tech Summit 2025",
 "totalPages": 3,
 "pageNumbers": [
 { "pageNumber": 1, "pageTitle": "Personal Info" },
 { "pageNumber": 2, "pageTitle": "Experience" },
 { "pageNumber": 3, "pageTitle": "Preferences" }
 ],
 "responseId": "uuid",
 "responseStatus": "DRAFT",
 "currentPageIndex": 1,
 "completedPageIds": ["page-uuid-1"]
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 formId 
 Internal form ID 
 
 
 formTitle 
 Form title 
 
 
 totalPages 
 Total active pages 
 
 
 pageNumbers[].pageNumber 
 1-based page number 
 
 
 pageNumbers[].pageTitle 
 Page heading 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 24. Preview Page by Number 
 Purpose : Returns a specific page by its 1-based position number, including all its active fields and options. Used by attendees to load each page as they fill the form. Also accessible to the organizer for previewing before publishing. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber} 
 Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageNumber 
 integer 
 Yes 
 1-based page position 
 Must be ≥ 1 and ≤ total active pages 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Preview page retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "PageResponse object" }
}
 
 Success Response Fields : data is a PageResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found, form not enabled, or pageNumber out of range 
 
 
 
 
 25. Validate Preview Page Answers 
 Purpose : Runs validation on a set of answers for a specific page number without creating or saving any data. Lets the organizer test field validation rules before the form goes live. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}/validate 
 Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageNumber 
 integer 
 Yes 
 1-based page number to validate against 
 Must be ≥ 1 and ≤ total active pages 
 
 
 
 Request JSON Sample : 
 {
 "fieldId-fullname": { "value": "" },
 "fieldId-email": { "value": "not-an-email" }
}
 
 Request Body : A flat map of fieldId (UUID) → AnswerValue . AnswerValue has value (any), plus optional fileUrl , fileName , fileSize , fileType for FILE fields. 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Validation complete",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "valid": false,
 "errors": [
 {
 "pageId": "1a2b3c4d-...",
 "pageTitle": "Attendee Information",
 "fieldId": "fieldId-fullname",
 "fieldLabel": "Full Name",
 "errorMessage": "Full Name is required",
 "errorType": "REQUIRED"
 },
 {
 "pageId": "1a2b3c4d-...",
 "pageTitle": "Attendee Information",
 "fieldId": "fieldId-email",
 "fieldLabel": "Email",
 "errorMessage": "Email must be valid email",
 "errorType": "INVALID_FORMAT"
 }
 ]
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data.valid 
 true if all fields passed validation 
 
 
 data.errors[].pageId 
 Page UUID 
 
 
 data.errors[].pageTitle 
 Page title 
 
 
 data.errors[].fieldId 
 Field UUID 
 
 
 data.errors[].fieldLabel 
 Field label 
 
 
 data.errors[].errorMessage 
 Human-readable error 
 
 
 data.errors[].errorType 
 REQUIRED , INVALID_FORMAT , INVALID_TYPE , VALIDATION_FAILED 
 
 
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found, form not enabled, or pageNumber out of range 
 
 
 
 
 Attendee Submission 
 
 26. Start Submission 
 Purpose : Initiates the attendee's form response session. Creates a new DRAFT response tied to this event's form. If the attendee already has a draft or submitted response for this event's form, the existing response is returned — this endpoint is idempotent. Requires the event to be PUBLISHED and within its registration window. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/start 
 Access Level : 🔒 Protected (Any authenticated attendee) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The published event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Form submission started",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "responseId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "submittedBy": "john.doe",
 "status": "DRAFT",
 "completedPageIds": [],
 "currentPageIndex": 0,
 "startedAt": "2025-02-17T10:30:45",
 "submittedAt": null,
 "completionTimeSeconds": null,
 "answers": []
 }
}
 
 Success Response Fields : data is a FormResponseObject . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Event not published, registration not yet open, or registration already closed 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 27. Save Page Answers 
 Purpose : Saves the attendee's answers for a specific page. Behaviour is controlled by the ?moveToNextPage query parameter which maps directly to two distinct client actions — background auto-save and the "Next" button. 
 Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/save 
 Access Level : 🔒 Protected (Attendee — response owner) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 Content-Type 
 string 
 Yes 
 application/json 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 pageId 
 UUID 
 Yes 
 The page being answered 
 Must be a valid UUID belonging to this event's form 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Default 
 
 
 
 
 moveToNextPage 
 boolean 
 No 
 Controls validation and page advancement. See behaviour table below 
 false 
 
 
 
 moveToNextPage Behaviour 
 
 
 
 Value 
 Intended Use 
 Validation 
 What Happens on Success 
 What Happens on Failure 
 
 
 
 
 false 
 Background auto-save while the user is typing 
 None — answers are stored as-is regardless of format or required state 
 Answers written. completedPageIds unchanged. Always success: true 
 N/A — cannot fail 
 
 
 true 
 User clicks "Next" to advance to the next page 
 Full — all fields on the page are validated (required + format + type) 
 Answers written, page added to completedPageIds , currentPageIndex advances. success: true 
 Nothing saved, nothing advanced. success: false with errors[] in data 
 
 
 
 
 Key rule : A page only enters completedPageIds through a successful ?moveToNextPage=true call. submit will reject the form if any page is missing from completedPageIds . 
 
 Request Body : A flat Map<UUID, AnswerValue> where each key is a fieldId . 
 
 
 
 Key 
 Type 
 Description 
 
 
 
 
 {fieldId} 
 UUID (map key) 
 The field being answered 
 
 
 .value 
 any 
 Answer value. See answer value types below 
 
 
 .fileUrl 
 string 
 FILE fields — uploaded file URL 
 
 
 .fileName 
 string 
 FILE fields — original file name 
 
 
 .fileSize 
 long 
 FILE fields — file size in bytes 
 
 
 .fileType 
 string 
 FILE fields — MIME type 
 
 
 
 Answer Value Types 
 
 
 
 Field Type 
 Expected Value 
 
 
 
 
 TEXT , TEXTAREA , EMAIL , PHONE , URL 
 String 
 
 
 NUMBER 
 Number (integer or decimal) 
 
 
 DATE 
 String — YYYY-MM-DD 
 
 
 TIME 
 String — HH:mm 
 
 
 DATETIME 
 String — ISO 8601 datetime 
 
 
 DROPDOWN , RADIO 
 String — UUID of selected option 
 
 
 CHECKBOX 
 Array of strings — UUIDs of selected options 
 
 
 RATING 
 Integer — 1 to 5 
 
 
 FILE 
 null — metadata goes in fileUrl , fileName , etc. 
 
 
 HEADER 
 Omit entirely — no answer needed 
 
 
 
 
 Example 1 — Auto-save ( moveToNextPage=false ) 
 Request : 
 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false
 
 {
 "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
 "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" }
}
 
 Response — always 200 OK , always success: true , no validation applied: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page saved",
 "action_time": "2026-05-22T14:30:00",
 "data": {
 "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
 "saved": true,
 "savedAt": "2026-05-22T14:30:00",
 "isValid": true,
 "errors": null
 }
}
 
 
 Example 2 — User clicks "Next", validation fails ( moveToNextPage=true ) 
 Request : 
 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true
 
 {
 "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
 "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" },
 "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "" },
 "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" }
}
 
 Response — 200 OK but success: false . Nothing saved. Client shows inline errors and keeps user on the same page: 
 {
 "success": false,
 "httpStatus": "OK",
 "message": "Validation failed",
 "action_time": "2026-05-22T14:31:00",
 "data": {
 "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
 "saved": false,
 "savedAt": null,
 "isValid": false,
 "errors": [
 {
 "fieldId": "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e",
 "fieldLabel": "Email",
 "errorCode": "INVALID_FORMAT",
 "message": "must be valid email"
 },
 {
 "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42",
 "fieldLabel": "Phone Number",
 "errorCode": "REQUIRED",
 "message": "Phone Number is required"
 }
 ]
 }
}
 
 
 Example 3 — User clicks "Next", validation passes ( moveToNextPage=true ) 
 Request : 
 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true
 
 {
 "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" },
 "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "graphereddy@gmail.com" },
 "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "0627489964" },
 "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" }
}
 
 Response — 200 OK , success: true . Page written, added to completedPageIds , index advances: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page saved",
 "action_time": "2026-05-22T14:32:00",
 "data": {
 "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c",
 "saved": true,
 "savedAt": "2026-05-22T14:32:00",
 "isValid": true,
 "errors": null
 }
}
 
 
 Error Codes in errors[].errorCode 
 
 
 
 errorCode 
 Meaning 
 
 
 
 
 REQUIRED 
 Field is marked required and no value was provided 
 
 
 INVALID_FORMAT 
 Value format is wrong (e.g. bad email, invalid phone pattern) 
 
 
 INVALID_TYPE 
 Value is the wrong data type for the field (e.g. passing a string to a DROPDOWN expecting a UUID) 
 
 
 VALIDATION_FAILED 
 Value fails a custom rule (minLength, maxLength, min, max, minSelections, etc.) 
 
 
 
 Success Response Fields : data is a SavePageResponse . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Registration window closed or event not published 
 
 
 404 
 Event not found, form not enabled, or no active draft found — call /start first 
 
 
 
 
 28. Get My Response 
 Purpose : Returns the authenticated attendee's current response for the event's form — whether still in draft or already submitted. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/my-response 
 Access Level : 🔒 Protected (Any authenticated attendee) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Response retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full FormResponseObject" }
}
 
 Success Response Fields : data is a FormResponseObject . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 404 
 No response found for this attendee on this event's form 
 
 
 
 
 29. Submit 
 Purpose : Finalises and submits the attendee's response. The server performs a two-stage validation before accepting: first it checks that every page is in completedPageIds (meaning the attendee clicked "Next" and passed validation for each page), then it checks that all required fields still have answers. If either check fails the response is rejected with 422 and a list of field-level errors in data . On success, status transitions to SUBMITTED and completionTimeSeconds is recorded. 
 Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/submit 
 Access Level : 🔒 Protected (Attendee — response owner) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Form submitted successfully",
 "action_time": "2026-05-22T14:35:00",
 "data": {
 "responseId": "8a067ebe-5eff-496c-a7eb-e3f8ad853b9f",
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "submittedBy": "john.doe",
 "status": "SUBMITTED",
 "completedPageIds": ["a64a2f29-428e-41fc-8d14-043841d857a2"],
 "currentPageIndex": 1,
 "startedAt": "2026-05-22T14:30:00",
 "submittedAt": "2026-05-22T14:35:00",
 "completionTimeSeconds": 312,
 "answers": [ "..." ]
 }
}
 
 Success Response Fields : data is a FormResponseObject . data.status will be "SUBMITTED" . 
 Error Response — page not completed via "Next" ( 422 ) : 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Please complete all required pages",
 "action_time": "2026-05-22T14:34:00",
 "data": [
 {
 "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2",
 "pageTitle": "Personal Info",
 "fieldId": null,
 "fieldLabel": null,
 "errorType": "PAGE_INCOMPLETE",
 "errorMessage": "Page not completed — please review all fields and click Next"
 }
 ]
}
 
 Error Response — required field still unanswered ( 422 ) : 
 {
 "success": false,
 "httpStatus": "UNPROCESSABLE_ENTITY",
 "message": "Please complete all required pages",
 "action_time": "2026-05-22T14:34:00",
 "data": [
 {
 "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2",
 "pageTitle": "Personal Info",
 "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42",
 "fieldLabel": "Phone Number",
 "errorType": "REQUIRED",
 "errorMessage": "This field is required"
 }
 ]
}
 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Already submitted, or registration window closed 
 
 
 404 
 Event not found, form not enabled, or no draft response found 
 
 
 422 
 One or more pages not in completedPageIds ( PAGE_INCOMPLETE ); or a required field has no answer ( REQUIRED ). Error list is in data[] 
 
 
 
 
 Organizer Response Management 
 
 30. Get Response by ID 
 Purpose : Retrieves a specific attendee response by ID. Available to the event organizer only. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses/{responseId} 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 responseId 
 UUID 
 Yes 
 The response to retrieve 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Response retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": { "...": "Full FormResponseObject" }
}
 
 Success Response Fields : data is a FormResponseObject . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or response not found 
 
 
 
 
 31. Get All Responses 
 Purpose : Returns a paginated list of all attendee responses for the event's form. For organizer review and export. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 page 
 integer 
 No 
 Page number (0-based) 
 Min: 0 
 0 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 20 
 
 
 
 
 Note : This endpoint uses 0-based pagination ( page=0 is the first page), matching the Spring Pageable default passed from the controller. 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Responses retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "content": [ "[ FormResponseObject entries ]" ],
 "totalElements": 48,
 "totalPages": 3,
 "first": true,
 "last": false,
 "empty": false
 }
}
 
 Success Response Fields : data.content[] contains FormResponseObject entries. 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 32. Get Analytics 
 Purpose : Returns full submission analytics for the event's form — stats overview, per-field distributions, and daily submission trends. 
 Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/analytics 
 Access Level : 🔒 Protected (Event organizer only) 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 The event 
 Must be a valid UUID 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Analytics retrieved",
 "action_time": "2025-02-17T10:30:45",
 "data": {
 "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
 "formTitle": "Attendee Questions - Dar es Salaam Jazz Festival 2025",
 "stats": {
 "totalStarted": 120,
 "totalDrafts": 18,
 "totalSubmitted": 95,
 "totalWithdrawn": 7,
 "completionRate": 79.2,
 "dropOffRate": 20.8,
 "avgCompletionTimeSeconds": 312.0,
 "fastestTimeSeconds": 45,
 "slowestTimeSeconds": 1890
 },
 "fieldAnalytics": [
 {
 "fieldId": "c3d4e5f6-...",
 "fieldLabel": "Full Name",
 "fieldType": "TEXT",
 "fieldDeleted": false,
 "totalResponses": 95,
 "uniqueResponses": 95,
 "textResponses": ["Amina Hassan", "John Doe", "..."]
 },
 {
 "fieldId": "e5f6a7b8-...",
 "fieldLabel": "Arrival method",
 "fieldType": "DROPDOWN",
 "fieldDeleted": false,
 "totalResponses": 95,
 "choiceDistribution": [
 { "option": "By Car", "count": 42, "percentage": 44.2 },
 { "option": "Public Transport", "count": 35, "percentage": 36.8 },
 { "option": "On Foot", "count": 18, "percentage": 18.9 }
 ]
 }
 ],
 "dailySubmissions": [
 { "date": "2025-07-15", "count": 12 },
 { "date": "2025-07-16", "count": 28 }
 ]
 }
}
 
 Success Response Fields : data is a FormAnalytics . 
 Possible Error Responses : 
 
 
 
 Status 
 Scenario 
 
 
 
 
 401 
 No or expired token 
 
 
 403 
 Not the event organizer 
 
 
 404 
 Event not found or form not enabled 
 
 
 
 
 Quick Reference — Endpoint Summary 
 
 
 
 # 
 Method 
 Path 
 Auth 
 Who 
 Description 
 
 
 
 
 Form Setup 
 
 
 
 
 
 
 
 1 
 POST 
 /events/{eventId}/enable 
 🔒 
 Organizer 
 Enable form (empty — no pages) 
 
 
 2 
 PUT 
 /events/{eventId}/settings 
 🔒 
 Organizer 
 Update form display settings 
 
 
 3 
 DELETE 
 /events/{eventId}/disable 
 🔒 
 Organizer 
 Disable and remove form 
 
 
 4 
 GET 
 /events/{eventId}/full-form 
 🔒 
 Organizer 
 Load full form (all pages + fields) for builder 
 
 
 Page Management 
 
 
 
 
 
 
 
 5 
 POST 
 /events/{eventId}/pages 
 🔒 
 Organizer 
 Add a page 
 
 
 6 
 PUT 
 /events/{eventId}/pages/{pageId} 
 🔒 
 Organizer 
 Update a page 
 
 
 7 
 DELETE 
 /events/{eventId}/pages/{pageId}?hard=false 
 🔒 
 Organizer 
 Delete a page (soft or hard) 
 
 
 8 
 POST 
 /events/{eventId}/pages/bulk 
 🔒 
 Organizer 
 Bulk add pages with fields 
 
 
 9 
 DELETE 
 /events/{eventId}/pages/bulk?hard=false 
 🔒 
 Organizer 
 Bulk delete pages 
 
 
 10 
 POST 
 /events/{eventId}/pages/{pageId}/clone 
 🔒 
 Organizer 
 Clone a page 
 
 
 Field Management 
 
 
 
 
 
 
 
 11 
 POST 
 /events/{eventId}/pages/{pageId}/fields 
 🔒 
 Organizer 
 Add a field to a page 
 
 
 12 
 PUT 
 /events/{eventId}/fields/{fieldId} 
 🔒 
 Organizer 
 Update a field 
 
 
 13 
 DELETE 
 /events/{eventId}/fields/{fieldId}?hard=false 
 🔒 
 Organizer 
 Delete a field (soft or hard) 
 
 
 14 
 POST 
 /events/{eventId}/pages/{pageId}/fields/bulk 
 🔒 
 Organizer 
 Bulk add fields 
 
 
 15 
 DELETE 
 /events/{eventId}/pages/{pageId}/fields/bulk?hard=false 
 🔒 
 Organizer 
 Bulk delete fields 
 
 
 16 
 PATCH 
 /events/{eventId}/fields/bulk 
 🔒 
 Organizer 
 Bulk update fields 
 
 
 17 
 POST 
 /events/{eventId}/fields/{fieldId}/clone 
 🔒 
 Organizer 
 Clone a field 
 
 
 Option Management 
 
 
 
 
 
 
 
 18 
 POST 
 /events/{eventId}/fields/{fieldId}/options 
 🔒 
 Organizer 
 Add option to a field 
 
 
 19 
 PUT 
 /events/{eventId}/options/{optionId} 
 🔒 
 Organizer 
 Update an option 
 
 
 20 
 DELETE 
 /events/{eventId}/options/{optionId}?hard=false 
 🔒 
 Organizer 
 Delete an option (soft or hard) 
 
 
 21 
 DELETE 
 /events/{eventId}/fields/{fieldId}/options/bulk?hard=false 
 🔒 
 Organizer 
 Bulk delete options 
 
 
 22 
 PATCH 
 /events/{eventId}/options/bulk 
 🔒 
 Organizer 
 Bulk update option labels 
 
 
 Preview 
 
 
 
 
 
 
 
 23 
 GET 
 /events/{eventId}/preview/metadata 
 🔒 
 Organizer + Attendee* 
 Form page metadata + attendee progress 
 
 
 24 
 GET 
 /events/{eventId}/preview/pages/{pageNumber} 
 🔒 
 Organizer + Attendee* 
 Load a page by number (fields + options) 
 
 
 25 
 POST 
 /events/{eventId}/preview/pages/{pageNumber}/validate 
 🔒 
 Organizer + Attendee* 
 Validate answers without saving 
 
 
 Attendee Submission 
 
 
 
 
 
 
 
 26 
 POST 
 /events/{eventId}/start 
 🔒 
 Attendee 
 Start form / get existing draft 
 
 
 27 
 PUT 
 /events/{eventId}/pages/{pageId}/save?moveToNextPage=false 
 🔒 
 Attendee 
 Auto-save answers (no validation) 
 
 
 27 
 PUT 
 /events/{eventId}/pages/{pageId}/save?moveToNextPage=true 
 🔒 
 Attendee 
 "Next" — validate page and advance if clean 
 
 
 28 
 GET 
 /events/{eventId}/my-response 
 🔒 
 Attendee 
 Get my response 
 
 
 29 
 POST 
 /events/{eventId}/submit 
 🔒 
 Attendee 
 Submit form (requires all pages completed via "Next") 
 
 
 Organizer — Responses & Analytics 
 
 
 
 
 
 
 
 30 
 GET 
 /events/{eventId}/responses/{responseId} 
 🔒 
 Organizer 
 Get response by ID 
 
 
 31 
 GET 
 /events/{eventId}/responses 
 🔒 
 Organizer 
 Get all responses (paginated) 
 
 
 32 
 GET 
 /events/{eventId}/analytics 
 🔒 
 Organizer 
 Get form analytics 
 
 
 
 
 * Attendee access to preview endpoints requires the event to be PUBLISHED . 
 
 
 All paths are prefixed with /api/v1/e-events/applicant-form . 
 
 
 Data Format Standards 
 
 
 
 Concern 
 Standard 
 
 
 
 
 Timestamps 
 ZonedDateTime — ISO 8601 with offset: 2025-02-17T10:30:45+03:00 
 
 
 Local timestamps 
 LocalDateTime — ISO 8601 no offset: 2025-02-17T10:30:45 
 
 
 Dates 
 YYYY-MM-DD — 2025-07-18 
 
 
 Times 
 HH:mm — 18:00 
 
 
 IDs 
 UUID v4 — 3fa85f64-5717-4562-b3fc-2c963f66afa6 
 
 
 Pagination (responses) 
 0-based page param, Spring Page wrapper 
 
 
 Enums 
 Uppercase: BEFORE_CHECKOUT , SUBMITTED , DROPDOWN , REQUIRED

Attendance Analytics API
Author : Josh S. Sakweli, Backend Lead Team
 Last Updated : 2026-05-23
 Version : v2.0 
 Base URL : https://api.nexgate.com/api/v1 
 Short Description : The Attendance Management API provides comprehensive attendee tracking and analytics for event organizers. Organizers can view overall attendance statistics with per-day and per-ticket-type breakdowns, browse a unified attendance list (present, absent, and partially attended in one view) with rich filtering, and drill into the full check-in history for any individual ticket. The system supports both single-day and multi-day events. 
 Hints : 
 
 Organizer Only : All endpoints restricted to the event organizer 
 Unified List : Present and absent tickets are in one list — use ?status=PRESENT|ABSENT|PARTIALLY_ATTENDED to filter 
 Ticket-Based Rows : Each row in the list is one ticket, not one person — one buyer can appear multiple times 
 Attendee Number : Unique human-readable ticket ID = bookingReference + "-" + ticketSeries (e.g. EVT-A3F4B21C-VIP-0042 ) 
 Buyer vs Attendee : buyer = who paid, attendee = who holds the ticket — can be different people 
 Buyer Types : SYSTEM_USER = online purchase with account, AT_DOOR = walk-in name only (no account) 
 Day Filtering : ?dayNumber is optional — omit for overall view, provide for per-day status 
 Partial Attendance : PARTIALLY_ATTENDED only applies when no dayNumber filter is set on multi-day events 
 Pagination : Default 20 per page 
 Form Response : formResponseId on each attendee/ticket row links to the buyer's applicant form submission — null if the event had no form or the form was not yet submitted 
 
 
 Standard Response Format 
 All API responses follow a consistent structure using our Globe Response Builder pattern: 
 Success Response Structure 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Operation completed successfully",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 }
}
 
 Error Response Structure 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Error description",
 "action_time": "2026-05-23T10:30:45",
 "data": "Error description"
}
 
 Standard Response Fields 
 
 
 
 Field 
 Type 
 Description 
 
 
 
 
 success 
 boolean 
 true for successful operations, false for errors 
 
 
 httpStatus 
 string 
 HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.) 
 
 
 message 
 string 
 Human-readable message describing the result 
 
 
 action_time 
 string 
 ISO 8601 timestamp of when the response was generated 
 
 
 data 
 object 
 Response payload for success, error string for failures 
 
 
 
 
 HTTP Method Badge Standards 
 
 GET - GET - Green (Safe, read-only operations) 
 POST - POST - Blue (Create new resources) 
 PUT - PUT - Yellow (Update/replace entire resource) 
 PATCH - PATCH - Orange (Partial updates) 
 DELETE - DELETE - Red (Remove resources) 
 
 
 Endpoints 
 1. Get Attendance Summary 
 Purpose : Returns overall attendance statistics for an event including per-day and per-ticket-type breakdowns. Always covers the full event — no filtering applied. 
 Endpoint : GET /e-events/attendance/{eventId}/summary 
 Access Level : 🔒 Protected — Event Organizer Only 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 Event identifier 
 Must be a valid UUID of an existing event owned by the authenticated organizer 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendance summary retrieved",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "eventTitle": "East African Tech Summit 2026",
 "totalDays": 3,
 "eventSchedule": [
 { "dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2026-12-15" },
 { "dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2026-12-16" },
 { "dayNumber": 3, "dayName": "Day 3 - Closing", "date": "2026-12-17" }
 ],
 "overallStats": {
 "totalTickets": 500,
 "present": 310,
 "partiallyAttended": 45,
 "absent": 145,
 "attendanceRate": 71.0,
 "byDay": [
 {
 "dayNumber": 1,
 "dayName": "Day 1 - Opening",
 "date": "2026-12-15",
 "totalTickets": 500,
 "checkedIn": 400,
 "absent": 100,
 "attendanceRate": 80.0,
 "status": "COMPLETED"
 },
 {
 "dayNumber": 2,
 "dayName": "Day 2 - Conference",
 "date": "2026-12-16",
 "totalTickets": 500,
 "checkedIn": 355,
 "absent": 145,
 "attendanceRate": 71.0,
 "status": "ONGOING"
 },
 {
 "dayNumber": 3,
 "dayName": "Day 3 - Closing",
 "date": "2026-12-17",
 "totalTickets": 500,
 "checkedIn": 0,
 "absent": 500,
 "attendanceRate": 0.0,
 "status": "UPCOMING"
 }
 ]
 },
 "byTicketType": [
 {
 "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
 "ticketTypeName": "VIP Pass",
 "totalSold": 100,
 "present": 75,
 "partiallyAttended": 10,
 "absent": 15,
 "attendanceRate": 85.0,
 "byDay": [
 {
 "dayNumber": 1,
 "dayName": "Day 1 - Opening",
 "checkedIn": 90,
 "absent": 10,
 "attendanceRate": 90.0
 }
 ]
 }
 ]
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data.eventId 
 Event UUID 
 
 
 data.eventTitle 
 Event name 
 
 
 data.totalDays 
 Number of event days 
 
 
 data.eventSchedule 
 List of days with dayNumber , dayName , date 
 
 
 data.overallStats.totalTickets 
 Total tickets sold 
 
 
 data.overallStats.present 
 Tickets checked in for all days 
 
 
 data.overallStats.partiallyAttended 
 Tickets checked in for some days (multi-day only) 
 
 
 data.overallStats.absent 
 Tickets never checked in 
 
 
 data.overallStats.attendanceRate 
 ((present + partiallyAttended) / total) × 100 , 2 dp 
 
 
 data.overallStats.byDay[].status 
 COMPLETED , ONGOING , or UPCOMING 
 
 
 data.byTicketType 
 Same stats broken down per ticket type 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Event not found",
 "action_time": "2026-05-23T10:30:45",
 "data": "Event not found"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or invalid token 
 403 FORBIDDEN : Authenticated user is not the event organizer 
 404 NOT_FOUND : Event does not exist 
 
 
 2. Get Attendance List 
 Purpose : Returns a paginated, filterable list of all tickets for an event. Each row is one ticket. Present and absent tickets are unified in one list — use the status filter to narrow down. 
 Endpoint : GET /e-events/attendance/{eventId}/list 
 Access Level : 🔒 Protected — Event Organizer Only 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 Event identifier 
 Must be a valid UUID of an existing event owned by the authenticated organizer 
 
 
 
 Query Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 Default 
 
 
 
 
 status 
 enum 
 No 
 Filter by attendance status 
 PRESENT , ABSENT , PARTIALLY_ATTENDED 
 All 
 
 
 dayNumber 
 integer 
 No 
 Filter for a specific event day 
 Must be between 1 and totalDays 
 None (overall) 
 
 
 ticketTypeId 
 UUID 
 No 
 Filter by ticket type 
 Must belong to this event 
 All types 
 
 
 buyerType 
 enum 
 No 
 Filter by how the ticket was purchased 
 SYSTEM_USER , AT_DOOR 
 All 
 
 
 search 
 string 
 No 
 Search by attendee name, email, or phone 
 Partial match, case-insensitive 
 None 
 
 
 page 
 integer 
 No 
 Page number (0-indexed) 
 Min: 0 
 0 
 
 
 size 
 integer 
 No 
 Items per page 
 Min: 1 
 20 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendance list retrieved",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "eventTitle": "East African Tech Summit 2026",
 "appliedFilters": {
 "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
 "ticketTypeName": "VIP Pass",
 "status": "ALL",
 "dayNumber": 1,
 "dayName": "Day 1 - Opening",
 "buyerType": "ALL",
 "search": null
 },
 "summary": {
 "totalTickets": 100,
 "present": 90,
 "absent": 10,
 "partiallyAttended": null,
 "attendanceRate": 90.0
 },
 "attendees": [
 {
 "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa8",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "attendeeNumber": "EVT-A3F4B21C-VIP-0042",
 "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
 "ticketType": "VIP Pass",
 "ticketSeries": "VIP-0042",
 "attendeeName": "John Doe",
 "attendeeEmail": "john@example.com",
 "attendeePhone": "+255712345678",
 "buyer": {
 "buyerName": "Jane Doe",
 "buyerEmail": "jane@example.com",
 "buyerId": "3fa85f64-5717-4562-b3fc-2c963f66afa9",
 "buyerType": "SYSTEM_USER"
 },
 "bookingReference": "EVT-A3F4B21C",
 "pricePaid": 150.00,
 "attendanceStatus": "PRESENT",
 "checkInTime": "2026-12-15T09:15:00+03:00",
 "checkInLocation": "Main Gate",
 "checkedInBy": "Scanner Operator 1",
 "scannerId": "3fa85f64-5717-4562-b3fc-2c963f66afb0"
 },
 {
 "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afb1",
 "formResponseId": null,
 "attendeeNumber": "EVT-B5D2E12F-GENE-0101",
 "ticketType": "General Admission",
 "ticketSeries": "GENE-0101",
 "attendeeName": "Ali Hassan",
 "attendeeEmail": null,
 "attendeePhone": null,
 "buyer": {
 "buyerName": "Ali Hassan",
 "buyerEmail": null,
 "buyerId": null,
 "buyerType": "AT_DOOR"
 },
 "bookingReference": "EVT-B5D2E12F",
 "pricePaid": 50.00,
 "attendanceStatus": "ABSENT",
 "checkInTime": null,
 "checkInLocation": null,
 "checkedInBy": null,
 "scannerId": null
 }
 ],
 "pagination": {
 "currentPage": 0,
 "pageSize": 20,
 "totalPages": 5,
 "totalElements": 100,
 "hasNext": true,
 "hasPrevious": false
 }
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data.appliedFilters 
 Echo of all active filters so the client knows what was applied 
 
 
 data.summary.totalTickets 
 Total tickets matching the ticket type filter (unaffected by status/search) 
 
 
 data.summary.present 
 Tickets with PRESENT status under current filters 
 
 
 data.summary.absent 
 Tickets with ABSENT status under current filters 
 
 
 data.summary.partiallyAttended 
 null when dayNumber is active; count when viewing overall multi-day 
 
 
 data.summary.attendanceRate 
 ((present + partiallyAttended) / total) × 100 , 2 dp 
 
 
 data.attendees[].ticketInstanceId 
 Unique ticket instance UUID 
 
 
 data.attendees[].formResponseId 
 UUID of the buyer's applicant form response — null if the event had no form 
 
 
 data.attendees[].attendeeNumber 
 Unique readable ID = bookingReference + "-" + ticketSeries 
 
 
 data.attendees[].attendanceStatus 
 PRESENT , ABSENT , or PARTIALLY_ATTENDED 
 
 
 data.attendees[].buyer.buyerType 
 SYSTEM_USER (online) or AT_DOOR (walk-in) 
 
 
 data.attendees[].buyer.buyerId 
 Populated for SYSTEM_USER , null for AT_DOOR 
 
 
 data.attendees[].checkInTime 
 null when attendanceStatus is ABSENT 
 
 
 data.pagination 
 Standard pagination envelope 
 
 
 
 Notes : 
 
 When dayNumber is provided: attendanceStatus is PRESENT or ABSENT for that specific day only 
 When dayNumber is omitted: attendanceStatus reflects the overall across all days ( PARTIALLY_ATTENDED possible) 
 summary reflects the current filter scope, not the whole event 
 partiallyAttended in summary is null when a dayNumber filter is active 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Invalid day 5. Valid: 1-3",
 "action_time": "2026-05-23T10:30:45",
 "data": "Invalid day 5. Valid: 1-3"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or invalid token 
 403 FORBIDDEN : Authenticated user is not the event organizer 
 404 NOT_FOUND : Event not found / day number out of range / ticket type not found 
 
 
 3. Get Attendee Detail 
 Purpose : Returns the full check-in history for a single ticket across all event days. Shows per-day status including upcoming days. 
 Endpoint : GET /e-events/attendance/{eventId}/attendees/{ticketInstanceId} 
 Access Level : 🔒 Protected — Event Organizer Only 
 Authentication : Bearer Token 
 Request Headers : 
 
 
 
 Header 
 Type 
 Required 
 Description 
 
 
 
 
 Authorization 
 string 
 Yes 
 Bearer <token> 
 
 
 
 Path Parameters : 
 
 
 
 Parameter 
 Type 
 Required 
 Description 
 Validation 
 
 
 
 
 eventId 
 UUID 
 Yes 
 Event identifier 
 Must be owned by the authenticated organizer 
 
 
 ticketInstanceId 
 UUID 
 Yes 
 Individual ticket instance identifier 
 Must belong to a booking under this event 
 
 
 
 Success Response JSON Sample : 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendee detail retrieved",
 "action_time": "2026-05-23T10:30:45",
 "data": {
 "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa8",
 "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
 "attendeeNumber": "EVT-A3F4B21C-VIP-0042",
 "attendeeName": "John Doe",
 "attendeeEmail": "john@example.com",
 "attendeePhone": "+255712345678",
 "ticketType": "VIP Pass",
 "ticketSeries": "VIP-0042",
 "bookingReference": "EVT-A3F4B21C",
 "pricePaid": 150.00,
 "overallStatus": "PARTIALLY_ATTENDED",
 "daysAttended": 2,
 "daysTotal": 3,
 "checkInsByDay": [
 {
 "dayNumber": 1,
 "dayName": "Day 1 - Opening",
 "dayDate": "2026-12-15",
 "status": "CHECKED_IN",
 "checkInTime": "2026-12-15T09:15:00+03:00",
 "checkInLocation": "Main Gate",
 "checkedInBy": "Scanner Operator 1",
 "scannerId": "3fa85f64-5717-4562-b3fc-2c963f66afb0"
 },
 {
 "dayNumber": 2,
 "dayName": "Day 2 - Conference",
 "dayDate": "2026-12-16",
 "status": "NOT_CHECKED_IN",
 "checkInTime": null,
 "checkInLocation": null,
 "checkedInBy": null,
 "scannerId": null
 },
 {
 "dayNumber": 3,
 "dayName": "Day 3 - Closing",
 "dayDate": "2026-12-17",
 "status": "UPCOMING",
 "checkInTime": null,
 "checkInLocation": null,
 "checkedInBy": null,
 "scannerId": null
 }
 ]
 }
}
 
 Success Response Fields : 
 
 
 
 Field 
 Description 
 
 
 
 
 data.ticketInstanceId 
 Unique ticket instance UUID 
 
 
 data.formResponseId 
 UUID of the buyer's applicant form response — null if the event had no form 
 
 
 data.attendeeNumber 
 Unique readable ID = bookingReference + "-" + ticketSeries 
 
 
 data.overallStatus 
 PRESENT (all days), PARTIALLY_ATTENDED (some), ABSENT (none) 
 
 
 data.daysAttended 
 Count of days this ticket was checked in 
 
 
 data.daysTotal 
 Total number of event days 
 
 
 data.checkInsByDay[].status 
 CHECKED_IN , NOT_CHECKED_IN , or UPCOMING 
 
 
 data.checkInsByDay[].checkInTime 
 null when not checked in or day is upcoming 
 
 
 data.checkInsByDay[].checkedInBy 
 Name of the scanner operator who processed the check-in 
 
 
 
 Error Response JSON Sample : 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Ticket not found",
 "action_time": "2026-05-23T10:30:45",
 "data": "Ticket not found"
}
 
 Standard Error Types : 
 
 401 UNAUTHORIZED : Missing or invalid token 
 403 FORBIDDEN : Authenticated user is not the event organizer 
 404 NOT_FOUND : Event not found or ticket instance does not belong to this event 
 
 
 Quick Reference Guide 
 Attendance Status Values 
 
 
 
 Value 
 Meaning 
 Available when 
 
 
 
 
 PRESENT 
 Checked in (all days if no day filter) 
 Always 
 
 
 ABSENT 
 Never checked in 
 Always 
 
 
 PARTIALLY_ATTENDED 
 Checked in some days but not all 
 No dayNumber filter, multi-day events only 
 
 
 
 Day Status Values 
 
 
 
 Value 
 Meaning 
 
 
 
 
 CHECKED_IN 
 Ticket was scanned on that day 
 
 
 NOT_CHECKED_IN 
 Day passed, ticket not scanned 
 
 
 UPCOMING 
 Day has not started yet 
 
 
 
 Buyer Type Values 
 
 
 
 Value 
 buyerEmail 
 buyerId 
 Meaning 
 
 
 
 
 SYSTEM_USER 
 Populated 
 Populated 
 Bought online, has platform account 
 
 
 AT_DOOR 
 null 
 null 
 Walk-in sale, name only captured at door 
 
 
 
 Common HTTP Status Codes 
 
 200 OK : Successful request 
 401 UNAUTHORIZED : Authentication required or token invalid 
 403 FORBIDDEN : Not the event organizer 
 404 NOT_FOUND : Event, ticket, or day number not found 
 500 INTERNAL_SERVER_ERROR : Unexpected server error

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 : 
 
 One Feedback Per User : Each user can only submit one feedback per event 
 Attendee Only : Event organizers cannot review their own events 
 5-Star Rating : Required rating from 1-5 stars 
 Optional Comments : Text reviews up to 1000 characters 
 Public Viewing : Anyone can view event feedback (paginated) 
 Chronological Order : Newest feedback first 
 Escrow Integration : Feedback may influence escrow release (future) 
 Simple Model : Focus on rating quality over complex metrics 
 
 
 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 : 
 
 Validates user is authenticated 
 Validates event exists 
 Checks user is NOT the event organizer 
 Checks user hasn't already submitted feedback 
 Creates feedback record 
 Returns created feedback 
 
 Validation Rules : 
 
 ✅ Rating must be 1-5 (integer) 
 ✅ Comment optional, max 1000 chars 
 ✅ One feedback per user per event 
 ✅ Organizers cannot review own events 
 ✅ Event must exist 
 
 Standard Error Types : 
 
 400 BAD_REQUEST : Invalid rating (not 1-5) 
 401 UNAUTHORIZED : Not authenticated 
 403 FORBIDDEN : Organizer trying to review own event 
 404 NOT_FOUND : Event not found 
 409 CONFLICT : Already submitted feedback 
 
 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 : 
 
 Returns paginated list of feedback 
 Sorted by creation date (newest first) 
 No authentication required (public access) 
 Validates event exists 
 
 Pagination Details : 
 
 Zero-indexed pages (0, 1, 2...) 
 Default page size: 20 
 Max page size: 100 (recommended) 
 Includes total elements and pages 
 
 Standard Error Types : 
 
 404 NOT_FOUND : Event not found 
 
 
 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 
 Recommended Comment Content 
 Good Comments Include : 
 
 ✅ What you enjoyed 
 ✅ Specific highlights (speakers, venue, activities) 
 ✅ Suggestions for improvement 
 ✅ Overall experience summary 
 
 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 : 
 
 ❌ Offensive language 
 ❌ Personal attacks 
 ❌ Spam or promotional content 
 ❌ Off-topic comments 
 
 Character Limit 
 
 Maximum: 1000 characters 
 Recommended: 100-500 characters 
 Minimum: None (optional field) 
 
 
 Access Control 
 Who Can Submit Feedback? 
 Allowed : 
 
 ✅ Any authenticated user 
 ✅ Attendees (purchased tickets) 
 ✅ Non-attendees (also allowed currently) 
 
 Not Allowed : 
 
 ❌ Event organizers (for their own events) 
 ❌ Users who already submitted feedback 
 ❌ Unauthenticated users 
 
 Future Restriction (Recommended): 
 
 Only users who purchased tickets 
 Requires checking booking history 
 Prevents fake reviews 
 
 Who Can View Feedback? 
 Anyone (Public access): 
 
 ✅ No authentication required 
 ✅ Potential attendees researching events 
 ✅ Event organizers viewing their reviews 
 ✅ Platform admins 
 
 
 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 
 
 Only show "Verified Attendee" if user has booking 
 Increases trust in reviews 
 Current: Anyone can review (not implemented) 
 
 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 
 
 Users can mark reviews as helpful 
 Sort by most helpful 
 Bubble up quality reviews 
 
 4. Organizer Response 
 
 Organizers can reply to feedback 
 Shows engagement and care 
 Builds trust with future attendees 
 
 5. Escrow Integration 
 
 Average rating influences escrow release 
 Events <3 stars may require manual review 
 Automatic release for 4+ stars 
 Current: Not implemented 
 
 
 Escrow Release Logic (Future) 
 How Feedback May Affect Escrow 
 High Ratings (4-5 stars average): 
 
 ✅ Automatic escrow release 
 Indicates successful event 
 No manual review needed 
 
 Medium Ratings (3-3.9 stars average): 
 
 ⚠️ Manual review triggered 
 Platform checks for issues 
 May contact organizer 
 Usually released after review 
 
 Low Ratings (<3 stars average): 
 
 ❌ Escrow release delayed 
 Investigation required 
 May require organizer explanation 
 Possible refunds if serious issues 
 
 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 
 
 200 OK : Feedbacks retrieved successfully 
 201 CREATED : Feedback submitted successfully 
 400 BAD_REQUEST : Invalid rating value 
 401 UNAUTHORIZED : Not authenticated 
 403 FORBIDDEN : Organizer reviewing own event 
 404 NOT_FOUND : Event not found 
 409 CONFLICT : Already submitted feedback 
 
 Rating Range 
 
 Minimum : 1 star (Poor) 
 Maximum : 5 stars (Excellent) 
 Type : Integer only (no half stars) 
 
 Comment Length 
 
 Minimum : 0 (optional) 
 Maximum : 1000 characters 
 Recommended : 100-500 characters 
 
 Pagination 
 
 Default Page : 0 (first page) 
 Default Size : 20 items 
 Max Size : 100 (recommended) 
 Sort Order : Newest first (createdAt DESC) 
 
 Data Formats 
 
 Event ID : UUID format 
 User ID : UUID format 
 Created At : ISO 8601 timestamp (Instant) 
 Rating : Integer (1-5) 
 
 
 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 : 
 
 Event must belong to the authenticated organizer 
 Event must have ended OR pastRefundDeadline = true (otherwise only admin can claim) 
 claimableAmount must be > 0 
 No existing PENDING claim for this event 
 
 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 : 
 
 claimableAmount must be > 0 
 No existing PENDING claim for this event 
 Claim is capped at claimableAmount (never full totalRevenue ) 
 
 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

Exproling

Attendee Questions API Guide
What Is It? 
 A customizable questionnaire system that event organizers attach to their events to collect information from attendees during registration. Think of it like Google Forms, but built specifically for events and integrated into the ticket purchase flow. 
 
 The Two Sides 
 Organizer Side (Building the Form) 
 When an organizer creates an event, they can optionally enable "Attendee Questions." The system automatically creates an empty form with one page. The organizer then builds their questionnaire by: 
 
 
 Adding pages - to organize questions into logical sections (e.g., "Personal Info", "Dietary Needs", "Travel Details") 
 
 
 Adding fields to pages - choosing from various field types like text, email, phone, dropdowns, checkboxes, ratings, file uploads, etc. 
 
 
 Adding options - for choice-based fields (dropdown, radio, checkbox), the organizer adds the available choices 
 
 
 Configuring settings - deciding when to show the form (before or after checkout), whether it's required, and if walk-in attendees should also complete it 
 
 
 The organizer can reorder pages, fields, and options. They can edit labels, add descriptions, set validation rules (minimum length, date ranges, file size limits), and mark fields as required or optional. 
 
 Attendee Side (Filling the Form) 
 When someone registers for a published event, they see the questionnaire. The system works page-by-page: 
 
 
 View a page - attendee sees one page of questions at a time with navigation showing progress (Page 1 of 3) 
 
 
 Fill and save - as the attendee completes a page and moves to the next, their answers are validated and saved automatically. If any answer is invalid (wrong format, too short, invalid selection), the page won't save and they see specific error messages for each problematic field. 
 
 
 Submit - when ready, the attendee clicks submit. The system checks ALL pages to ensure every required field is answered and all answers are valid. If everything passes, the submission is finalized. If not, they see a summary showing which pages have problems. 
 
 
 The key principle: validate before saving, never store invalid data . This keeps the database clean and gives immediate feedback. 
 
 The Analytics Side (Viewing Results) 
 Once attendees start submitting, the organizer can view comprehensive analytics: 
 
 
 Summary statistics - total submissions, completion rates, average time to complete, submission trends over time, and which pages have the highest drop-off rates 
 
 
 Field-by-field breakdown - for each question, see response rates and detailed analysis. For choice fields, see how many people selected each option with percentages. For numeric fields, see averages, ranges, and distributions. For text fields, see common words and length statistics. 
 
 
 Spreadsheet view - all responses in a table format, like Google Forms' response spreadsheet, with each row being one submission and each column being one question 
 
 
 Export - download all data as CSV or Excel for external analysis 
 
 
 
 How Is It Useful? 
 For Event Organizers 
 Logistics planning: 
 
 Collect t-shirt sizes to order the right quantities 
 Gather dietary requirements for catering 
 Know arrival times for transportation planning 
 Get emergency contact information for safety 
 
 Attendee profiling: 
 
 Ask about interests for personalized experiences 
 Collect company/role info for networking features 
 Understand how attendees heard about the event for marketing 
 
 Compliance and safety: 
 
 Collect required documents (ID uploads) 
 Gather health declarations 
 Get consent for photography/recording 
 Verify age requirements 
 
 Engagement: 
 
 Ask what sessions attendees plan to attend 
 Collect questions for Q&A panels 
 Gather song requests for performers 
 Get feedback on previous events 
 
 For Attendees 
 Smoother experience: 
 
 Complete registration in one flow instead of separate forms 
 Save progress and return later (draft mode) 
 Clear validation messages prevent submission errors 
 Know exactly what's required vs optional 
 
 Personalization: 
 
 Their preferences are captured for a tailored experience 
 Dietary needs are communicated without awkward conversations 
 Special requirements are noted in advance 
 
 
 Real-World Example 
 Music Festival Scenario: 
 An organizer creates a 3-day festival. They enable attendee questions with two pages: 
 Page 1: Essential Info 
 
 Emergency contact name (required, text) 
 Emergency contact phone (required, phone validation) 
 T-shirt size (required, dropdown: S/M/L/XL) 
 Camping or day pass? (required, radio) 
 
 Page 2: Preferences 
 
 Dietary restrictions (optional, checkbox: vegetarian, vegan, gluten-free, halal, kosher) 
 Other dietary notes (optional, textarea) 
 Which headliner are you most excited for? (optional, dropdown with artist names) 
 
 When 500 people register, the organizer sees: 
 
 65% chose Medium or Large t-shirts 
 23% have dietary restrictions (42% of those are vegetarian) 
 Artist X is the most anticipated headliner with 45% of votes 
 Page 2 has 15% drop-off (people skipping optional questions) 
 
 They export the data to: 
 
 Order t-shirts: 50 S, 180 M, 170 L, 100 XL 
 Tell catering: 115 vegetarian meals, 45 vegan, 60 gluten-free 
 Adjust sound check priority based on headliner popularity 
 
 
 Why This Design? 
 Page-by-page saves prevent data loss. If someone fills 3 pages then loses internet, they don't lose everything. 
 Strict validation before saving means the database only contains valid, usable data. No garbage entries to clean up later. 
 Empty-body submit means the final submission is just a validation check, not a data transfer. Everything is already saved from auto-saves. 
 Parallel page validation on submit makes the final check fast even with many pages. 
 Separation of draft and published means organizers can freely edit their form while building the event, but once published and people start responding, the structure is stable. 
 
 Summary 
 It's a form builder tailored for events. Organizers build custom questionnaires, attendees fill them page-by-page with auto-save and validation, and organizers get rich analytics to plan better events. The technical design prioritizes data integrity, user experience, and actionable insights. 
 System Flow 
 ┌─────────────────────────────────────────────────────────────────────────────┐
│ ORGANIZER FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Create Event Draft │
│ POST /api/v1/e-events/drafts │
│ ↓ │
│ 2. Enable Attendee Questions │
│ PUT /api/v1/e-events/drafts/{draftId}/attendee-questions │
│ ↓ │
│ 3. Add Pages (optional - default page created automatically) │
│ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages │
│ ↓ │
│ 4. Add Fields to Pages │
│ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields │
│ ↓ │
│ 5. Add Options to Choice Fields (dropdown/radio/checkbox) │
│ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options │
│ ↓ │
│ 6. Publish Event │
│ POST /api/v1/e-events/drafts/{draftId}/publish │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ ATTENDEE FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. View Questionnaire (check if registration open) │
│ GET /api/v1/e-events/{eventId}/attendee-questions │
│ ↓ │
│ 2. Get Each Page │
│ GET /api/v1/e-events/{eventId}/attendee-questions/pages/{pageNumber} │
│ ↓ │
│ 3. Save Page Answers (auto-save per page) │
│ PATCH /api/v1/e-events/{eventId}/attendee-questions/responses/pages/{n}│
│ ↓ │
│ 4. Submit Final Response │
│ POST /api/v1/e-events/{eventId}/attendee-questions/responses/submit │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ ANALYTICS FLOW (Organizer Only) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/summary │
│ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields │
│ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields/{id} │
│ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/responses │
│ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/csv │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
 
 
 Authentication 
 All endpoints require JWT authentication except public event viewing. 
 Header: Authorization: Bearer {jwt_token}
 
 
 PART 1: ORGANIZER ENDPOINTS 
 Base URL 
 /api/v1/e-events/drafts/{draftId}/attendee-questions
 
 
 1.1 Enable Attendee Questions 
 Creates a new questionnaire for the event draft. Automatically creates an empty form with one default page. 
 Endpoint: 
 PUT /api/v1/e-events/drafts/{draftId}/attendee-questions
 
 Request: 
 {
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false
}
 
 Request Fields: 
 
 
 
 Field 
 Type 
 Required 
 Description 
 
 
 
 
 displayTime 
 enum 
 No 
 When to show form: BEFORE_CHECKOUT or AFTER_CHECKOUT . Default: BEFORE_CHECKOUT 
 
 
 isRequiredOnline 
 boolean 
 No 
 Must complete before checkout? Default: false 
 
 
 applyToAtDoor 
 boolean 
 No 
 Show to walk-in attendees? Default: false 
 
 
 
 Response (201 Created): 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Attendee questions enabled",
 "data": {
 "id": "550e8400-e29b-41d4-a716-446655440000",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "formId": "770e8400-e29b-41d4-a716-446655440002",
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false,
 "createdAt": "2025-01-23T10:30:00Z",
 "updatedAt": "2025-01-23T10:30:00Z"
 }
}
 
 Error Responses: 
 
 
 
 Status 
 Message 
 
 
 
 
 404 
 Draft not found 
 
 
 403 
 You don't have access to this draft 
 
 
 403 
 Attendee questions already enabled. Use update instead. 
 
 
 
 
 1.2 Update Attendee Questions Settings 
 Endpoint: 
 PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions
 
 Request: 
 {
 "displayTime": "AFTER_CHECKOUT",
 "isRequiredOnline": false
}
 
 All fields optional - only updates provided fields. 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Settings updated",
 "data": {
 "id": "550e8400-e29b-41d4-a716-446655440000",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "formId": "770e8400-e29b-41d4-a716-446655440002",
 "displayTime": "AFTER_CHECKOUT",
 "isRequiredOnline": false,
 "applyToAtDoor": false,
 "createdAt": "2025-01-23T10:30:00Z",
 "updatedAt": "2025-01-23T10:35:00Z"
 }
}
 
 
 1.3 Get Attendee Questions (with full form) 
 Endpoint: 
 GET /api/v1/e-events/drafts/{draftId}/attendee-questions
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendee questions retrieved",
 "data": {
 "id": "550e8400-e29b-41d4-a716-446655440000",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "formId": "770e8400-e29b-41d4-a716-446655440002",
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false,
 "createdAt": "2025-01-23T10:30:00Z",
 "updatedAt": "2025-01-23T10:30:00Z",
 "form": {
 "formId": "770e8400-e29b-41d4-a716-446655440002",
 "title": "Attendee Questions - Music Festival 2025",
 "description": "Custom questions for event attendees",
 "pages": [
 {
 "pageId": "880e8400-e29b-41d4-a716-446655440003",
 "title": "Personal Information",
 "description": "Basic attendee details",
 "displayOrder": 1,
 "fields": [
 {
 "fieldId": "990e8400-e29b-41d4-a716-446655440004",
 "type": "TEXT",
 "label": "Emergency Contact Name",
 "description": "Who should we contact in case of emergency?",
 "placeholder": "Full name",
 "displayOrder": 1,
 "required": true,
 "validation": {
 "minLength": 2,
 "maxLength": 100
 },
 "options": []
 },
 {
 "fieldId": "aa0e8400-e29b-41d4-a716-446655440005",
 "type": "PHONE",
 "label": "Emergency Contact Phone",
 "displayOrder": 2,
 "required": true,
 "options": []
 },
 {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "displayOrder": 3,
 "required": true,
 "options": [
 {"optionId": "opt1", "label": "Small", "displayOrder": 1},
 {"optionId": "opt2", "label": "Medium", "displayOrder": 2},
 {"optionId": "opt3", "label": "Large", "displayOrder": 3},
 {"optionId": "opt4", "label": "X-Large", "displayOrder": 4}
 ]
 }
 ]
 },
 {
 "pageId": "cc0e8400-e29b-41d4-a716-446655440007",
 "title": "Dietary Requirements",
 "displayOrder": 2,
 "fields": [
 {
 "fieldId": "dd0e8400-e29b-41d4-a716-446655440008",
 "type": "CHECKBOX",
 "label": "Dietary Restrictions",
 "description": "Select all that apply",
 "displayOrder": 1,
 "required": false,
 "options": [
 {"optionId": "opt5", "label": "Vegetarian", "displayOrder": 1},
 {"optionId": "opt6", "label": "Vegan", "displayOrder": 2},
 {"optionId": "opt7", "label": "Gluten-free", "displayOrder": 3},
 {"optionId": "opt8", "label": "Halal", "displayOrder": 4},
 {"optionId": "opt9", "label": "Kosher", "displayOrder": 5}
 ]
 },
 {
 "fieldId": "ee0e8400-e29b-41d4-a716-446655440009",
 "type": "TEXTAREA",
 "label": "Other Dietary Notes",
 "placeholder": "Any allergies or special requirements?",
 "displayOrder": 2,
 "required": false
 }
 ]
 }
 ]
 }
 }
}
 
 Response when not configured (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendee questions not configured",
 "data": null
}
 
 
 1.4 Disable Attendee Questions 
 Permanently deletes the form and all configuration. 
 Endpoint: 
 DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Attendee questions disabled"
}
 
 
 Pages Management 
 1.5 Add Page 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages
 
 Request: 
 {
 "title": "Travel Information",
 "description": "Help us plan your arrival"
}
 
 Response (201 Created): 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Page added",
 "data": {
 "pageId": "ff0e8400-e29b-41d4-a716-446655440010",
 "title": "Travel Information",
 "description": "Help us plan your arrival",
 "displayOrder": 3
 }
}
 
 
 1.6 Update Page 
 Endpoint: 
 PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}
 
 Request: 
 {
 "title": "Travel & Accommodation",
 "description": "Updated description"
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page updated",
 "data": {
 "pageId": "ff0e8400-e29b-41d4-a716-446655440010",
 "title": "Travel & Accommodation",
 "description": "Updated description",
 "displayOrder": 3
 }
}
 
 
 1.7 Delete Page 
 Endpoint: 
 DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page deleted"
}
 
 
 1.8 Reorder Pages 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/reorder
 
 Request: 
 {
 "orderedIds": [
 "cc0e8400-e29b-41d4-a716-446655440007",
 "880e8400-e29b-41d4-a716-446655440003",
 "ff0e8400-e29b-41d4-a716-446655440010"
 ]
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Pages reordered"
}
 
 
 Fields Management 
 1.9 Add Field 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields
 
 Request (Text Field): 
 {
 "type": "TEXT",
 "label": "Company Name",
 "description": "Your current employer",
 "placeholder": "Enter company name",
 "required": false,
 "validation": {
 "minLength": 2,
 "maxLength": 100
 }
}
 
 Request (Dropdown Field): 
 {
 "type": "DROPDOWN",
 "label": "How did you hear about us?",
 "required": true
}
 
 Request (Rating Field): 
 {
 "type": "RATING",
 "label": "How excited are you for this event?",
 "required": true,
 "validation": {
 "min": 1,
 "max": 5
 }
}
 
 Request (File Upload Field): 
 {
 "type": "FILE",
 "label": "Upload ID Document",
 "description": "Government-issued ID for verification",
 "required": true,
 "validation": {
 "maxSizeMb": 5,
 "accept": "image/*,.pdf"
 }
}
 
 Available Field Types: 
 
 
 
 Type 
 Description 
 Has Options 
 
 
 
 
 TEXT 
 Single line text 
 No 
 
 
 TEXTAREA 
 Multi-line text 
 No 
 
 
 EMAIL 
 Email with validation 
 No 
 
 
 PHONE 
 Phone number 
 No 
 
 
 NUMBER 
 Numeric input 
 No 
 
 
 DATE 
 Date picker 
 No 
 
 
 TIME 
 Time picker 
 No 
 
 
 DATETIME 
 Date and time 
 No 
 
 
 DROPDOWN 
 Single select dropdown 
 Yes 
 
 
 RADIO 
 Single select radio buttons 
 Yes 
 
 
 CHECKBOX 
 Multi-select checkboxes 
 Yes 
 
 
 RATING 
 Star rating 
 No 
 
 
 SCALE 
 Linear scale (1-10) 
 No 
 
 
 SLIDER 
 Slider input 
 No 
 
 
 FILE 
 File upload 
 No 
 
 
 URL 
 URL with validation 
 No 
 
 
 CURRENCY 
 Money input 
 No 
 
 
 SECTION_HEADER 
 Display only - section title 
 No 
 
 
 PARAGRAPH 
 Display only - text block 
 No 
 
 
 
 Response (201 Created): 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Field added",
 "data": {
 "fieldId": "110e8400-e29b-41d4-a716-446655440011",
 "type": "DROPDOWN",
 "label": "How did you hear about us?",
 "displayOrder": 1,
 "required": true,
 "options": []
 }
}
 
 
 1.10 Update Field 
 Endpoint: 
 PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}
 
 Request: 
 {
 "label": "How did you discover this event?",
 "required": false,
 "description": "Help us improve our marketing"
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Field updated",
 "data": {
 "fieldId": "110e8400-e29b-41d4-a716-446655440011",
 "type": "DROPDOWN",
 "label": "How did you discover this event?",
 "description": "Help us improve our marketing",
 "displayOrder": 1,
 "required": false,
 "options": []
 }
}
 
 
 1.11 Delete Field 
 Endpoint: 
 DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Field deleted"
}
 
 
 1.12 Reorder Fields 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields/reorder
 
 Request: 
 {
 "orderedIds": [
 "bb0e8400-e29b-41d4-a716-446655440006",
 "990e8400-e29b-41d4-a716-446655440004",
 "aa0e8400-e29b-41d4-a716-446655440005"
 ]
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Fields reordered"
}
 
 
 Options Management (for DROPDOWN, RADIO, CHECKBOX) 
 1.13 Add Option 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options
 
 Request: 
 {
 "label": "Social Media"
}
 
 Response (201 Created): 
 {
 "success": true,
 "httpStatus": "CREATED",
 "message": "Option added",
 "data": {
 "optionId": "220e8400-e29b-41d4-a716-446655440012",
 "label": "Social Media",
 "displayOrder": 1
 }
}
 
 
 1.14 Update Option 
 Endpoint: 
 PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/options/{optionId}
 
 Request: 
 {
 "label": "Social Media (Facebook, Instagram, etc.)"
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Option updated",
 "data": {
 "optionId": "220e8400-e29b-41d4-a716-446655440012",
 "label": "Social Media (Facebook, Instagram, etc.)",
 "displayOrder": 1
 }
}
 
 
 1.15 Delete Option 
 Endpoint: 
 DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/options/{optionId}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Option deleted"
}
 
 
 1.16 Reorder Options 
 Endpoint: 
 POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options/reorder
 
 Request: 
 {
 "orderedIds": [
 "220e8400-e29b-41d4-a716-446655440012",
 "330e8400-e29b-41d4-a716-446655440013",
 "440e8400-e29b-41d4-a716-446655440014"
 ]
}
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Options reordered"
}
 
 
 PART 2: ATTENDEE ENDPOINTS (Public) 
 Base URL 
 /api/v1/e-events/{eventId}/attendee-questions
 
 Note: These endpoints require the event to be PUBLISHED. 
 
 2.1 Get Questionnaire (First Page) 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions
 
 Response - No questionnaire configured: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "No questionnaire for this event",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "hasForm": false,
 "registrationOpen": false,
 "message": "No questionnaire for this event"
 }
}
 
 Response - Registration not yet open: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Registration opens on 2025-02-01",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "hasForm": true,
 "registrationOpen": false,
 "message": "Registration opens on 2025-02-01",
 "registrationOpensAt": "2025-02-01T09:00:00Z",
 "registrationClosesAt": "2025-02-15T18:00:00Z",
 "displayTime": "BEFORE_CHECKOUT",
 "isRequired": true
 }
}
 
 Response - Registration closed: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Registration closed on 2025-02-15",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "hasForm": true,
 "registrationOpen": false,
 "message": "Registration closed on 2025-02-15",
 "registrationOpensAt": "2025-02-01T09:00:00Z",
 "registrationClosesAt": "2025-02-15T18:00:00Z"
 }
}
 
 Response - Registration open (returns page 1): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Questionnaire retrieved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "hasForm": true,
 "registrationOpen": true,
 "registrationOpensAt": "2025-02-01T09:00:00Z",
 "registrationClosesAt": "2025-02-15T18:00:00Z",
 "displayTime": "BEFORE_CHECKOUT",
 "isRequired": true,
 "currentPage": 1,
 "totalPages": 2,
 "formTitle": "Attendee Questions - Music Festival 2025",
 "formDescription": "Please complete this questionnaire",
 "page": {
 "pageId": "880e8400-e29b-41d4-a716-446655440003",
 "title": "Personal Information",
 "description": "Basic attendee details",
 "displayOrder": 1,
 "fields": [
 {
 "fieldId": "990e8400-e29b-41d4-a716-446655440004",
 "type": "TEXT",
 "label": "Emergency Contact Name",
 "description": "Who should we contact in case of emergency?",
 "placeholder": "Full name",
 "displayOrder": 1,
 "required": true,
 "validation": {
 "minLength": 2,
 "maxLength": 100
 },
 "options": []
 },
 {
 "fieldId": "aa0e8400-e29b-41d4-a716-446655440005",
 "type": "PHONE",
 "label": "Emergency Contact Phone",
 "displayOrder": 2,
 "required": true,
 "options": []
 },
 {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "displayOrder": 3,
 "required": true,
 "options": [
 {"optionId": "opt1", "label": "Small", "displayOrder": 1},
 {"optionId": "opt2", "label": "Medium", "displayOrder": 2},
 {"optionId": "opt3", "label": "Large", "displayOrder": 3},
 {"optionId": "opt4", "label": "X-Large", "displayOrder": 4}
 ]
 }
 ]
 }
 }
}
 
 
 2.2 Get Specific Page 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/pages/{pageNumber}
 
 Example: 
 GET /api/v1/e-events/660e8400-e29b-41d4-a716-446655440001/attendee-questions/pages/2
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page 2 of 2",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "hasForm": true,
 "registrationOpen": true,
 "currentPage": 2,
 "totalPages": 2,
 "page": {
 "pageId": "cc0e8400-e29b-41d4-a716-446655440007",
 "title": "Dietary Requirements",
 "displayOrder": 2,
 "fields": [
 {
 "fieldId": "dd0e8400-e29b-41d4-a716-446655440008",
 "type": "CHECKBOX",
 "label": "Dietary Restrictions",
 "description": "Select all that apply",
 "displayOrder": 1,
 "required": false,
 "options": [
 {"optionId": "opt5", "label": "Vegetarian", "displayOrder": 1},
 {"optionId": "opt6", "label": "Vegan", "displayOrder": 2},
 {"optionId": "opt7", "label": "Gluten-free", "displayOrder": 3},
 {"optionId": "opt8", "label": "Halal", "displayOrder": 4},
 {"optionId": "opt9", "label": "Kosher", "displayOrder": 5}
 ]
 },
 {
 "fieldId": "ee0e8400-e29b-41d4-a716-446655440009",
 "type": "TEXTAREA",
 "label": "Other Dietary Notes",
 "placeholder": "Any allergies or special requirements?",
 "displayOrder": 2,
 "required": false
 }
 ]
 }
 }
}
 
 Error - Page not found: 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "Page not found. Valid pages: 1 to 2"
}
 
 
 2.3 Save Page Answers (Auto-Save) 
 Saves answers for a single page. Validates ALL fields on the page before saving. If any field is invalid, nothing is saved. 
 Endpoint: 
 PATCH /api/v1/e-events/{eventId}/attendee-questions/responses/pages/{pageNumber}
 
 Request: 
 {
 "answers": {
 "990e8400-e29b-41d4-a716-446655440004": {
 "fieldId": "990e8400-e29b-41d4-a716-446655440004",
 "value": "John Doe"
 },
 "aa0e8400-e29b-41d4-a716-446655440005": {
 "fieldId": "aa0e8400-e29b-41d4-a716-446655440005",
 "value": "+255712345678"
 },
 "bb0e8400-e29b-41d4-a716-446655440006": {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "value": "Large"
 }
 }
}
 
 Answer Format by Field Type: 
 
 
 
 Field Type 
 Value Format 
 Example 
 
 
 
 
 TEXT, TEXTAREA, EMAIL, PHONE, URL 
 string 
 "John Doe" 
 
 
 NUMBER, CURRENCY, RATING, SCALE, SLIDER 
 number 
 42 or 4.5 
 
 
 DATE 
 string (YYYY-MM-DD) 
 "2025-02-15" 
 
 
 TIME 
 string (HH:MM) 
 "14:30" 
 
 
 DATETIME 
 string (ISO) 
 "2025-02-15T14:30:00" 
 
 
 DROPDOWN, RADIO 
 string (selected label) 
 "Large" 
 
 
 CHECKBOX 
 array of strings 
 ["Vegetarian", "Gluten-free"] 
 
 
 FILE 
 object 
 See below 
 
 
 
 File Upload Answer: 
 {
 "fieldId": "file-field-id",
 "value": null,
 "fileUrl": "https://storage.example.com/uploads/id-doc.pdf",
 "fileName": "id-doc.pdf",
 "fileSize": 1048576,
 "fileType": "application/pdf"
}
 
 Response - Success (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Page 1 saved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "pageNumber": 1,
 "saved": true,
 "savedAt": "2025-01-23T10:45:00Z",
 "isValid": true,
 "errors": [],
 "totalFieldsOnPage": 3,
 "answeredFieldsOnPage": 3,
 "overallProgress": {
 "totalPages": 2,
 "completedPages": 1,
 "totalFields": 5,
 "answeredFields": 3,
 "completionPercentage": 60
 }
 }
}
 
 Response - Validation Failed (400 Bad Request): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Validation failed - page not saved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "pageNumber": 1,
 "saved": false,
 "isValid": false,
 "errors": [
 {
 "fieldId": "aa0e8400-e29b-41d4-a716-446655440005",
 "fieldLabel": "Emergency Contact Phone",
 "errorCode": "INVALID_PHONE",
 "message": "Please enter a valid phone number"
 },
 {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "fieldLabel": "T-Shirt Size",
 "errorCode": "INVALID_OPTION",
 "message": "Selected option is not valid"
 }
 ],
 "totalFieldsOnPage": 3,
 "answeredFieldsOnPage": 3,
 "overallProgress": {
 "totalPages": 2,
 "completedPages": 0,
 "totalFields": 5,
 "answeredFields": 0,
 "completionPercentage": 0
 }
 }
}
 
 Validation Error Codes: 
 
 
 
 Code 
 Description 
 
 
 
 
 REQUIRED 
 Required field is missing 
 
 
 INVALID_EMAIL 
 Invalid email format 
 
 
 INVALID_PHONE 
 Invalid phone number 
 
 
 INVALID_NUMBER 
 Not a valid number 
 
 
 INVALID_DATE 
 Invalid date format 
 
 
 INVALID_TIME 
 Invalid time format 
 
 
 INVALID_DATETIME 
 Invalid datetime format 
 
 
 INVALID_URL 
 Invalid URL format 
 
 
 INVALID_OPTION 
 Selected option not in list 
 
 
 INVALID_SELECTION 
 Invalid selection format 
 
 
 TOO_SHORT 
 Text too short (minLength) 
 
 
 TOO_LONG 
 Text too long (maxLength) 
 
 
 BELOW_MIN 
 Number below minimum 
 
 
 ABOVE_MAX 
 Number above maximum 
 
 
 DATE_TOO_EARLY 
 Date before minDate 
 
 
 DATE_TOO_LATE 
 Date after maxDate 
 
 
 TOO_FEW_SELECTIONS 
 Not enough checkboxes 
 
 
 TOO_MANY_SELECTIONS 
 Too many checkboxes 
 
 
 PATTERN_MISMATCH 
 Doesn't match regex 
 
 
 FILE_TOO_LARGE 
 File exceeds maxSizeMb 
 
 
 INVALID_FILE_TYPE 
 File type not accepted 
 
 
 
 
 2.4 Submit Final Response 
 Submits the form. No request body needed - validates all saved pages and submits if all valid. 
 Endpoint: 
 POST /api/v1/e-events/{eventId}/attendee-questions/responses/submit
 
 Response - Success (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Submission successful",
 "data": {
 "responseId": "550e8400-e29b-41d4-a716-446655440099",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "success": true,
 "status": "SUBMITTED",
 "submittedAt": "2025-01-23T10:50:00Z",
 "pageValidations": [
 {
 "pageNumber": 1,
 "pageTitle": "Personal Information",
 "isValid": true,
 "hasAnswers": true,
 "totalFields": 3,
 "answeredFields": 3,
 "requiredFields": 3,
 "requiredAnswered": 3,
 "errors": []
 },
 {
 "pageNumber": 2,
 "pageTitle": "Dietary Requirements",
 "isValid": true,
 "hasAnswers": true,
 "totalFields": 2,
 "answeredFields": 1,
 "requiredFields": 0,
 "requiredAnswered": 0,
 "errors": []
 }
 ],
 "totalPages": 2,
 "validPages": 2,
 "invalidPages": 0,
 "allPagesValid": true
 }
}
 
 Response - Validation Failed (400 Bad Request): 
 {
 "success": false,
 "httpStatus": "BAD_REQUEST",
 "message": "Validation failed - 1 page(s) have errors",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "success": false,
 "status": "DRAFT",
 "pageValidations": [
 {
 "pageNumber": 1,
 "pageTitle": "Personal Information",
 "isValid": false,
 "hasAnswers": true,
 "totalFields": 3,
 "answeredFields": 2,
 "requiredFields": 3,
 "requiredAnswered": 2,
 "errors": [
 {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "fieldLabel": "T-Shirt Size",
 "errorCode": "REQUIRED",
 "message": "T-Shirt Size is required"
 }
 ]
 },
 {
 "pageNumber": 2,
 "pageTitle": "Dietary Requirements",
 "isValid": true,
 "hasAnswers": false,
 "totalFields": 2,
 "answeredFields": 0,
 "requiredFields": 0,
 "requiredAnswered": 0,
 "errors": []
 }
 ],
 "totalPages": 2,
 "validPages": 1,
 "invalidPages": 1,
 "allPagesValid": false
 }
}
 
 Error - No draft found: 
 {
 "success": false,
 "httpStatus": "NOT_FOUND",
 "message": "No draft found. Please save your answers first."
}
 
 
 2.5 Get Submission Status 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/responses/status
 
 Response - No submission started: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "No submission started",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "success": false,
 "status": null,
 "totalPages": 2,
 "validPages": 0,
 "invalidPages": 0,
 "allPagesValid": false
 }
}
 
 Response - Draft in progress: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Status: DRAFT",
 "data": {
 "responseId": "550e8400-e29b-41d4-a716-446655440099",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "success": false,
 "status": "DRAFT",
 "submittedAt": null,
 "pageValidations": [
 {
 "pageNumber": 1,
 "pageTitle": "Personal Information",
 "isValid": true,
 "hasAnswers": true,
 "totalFields": 3,
 "answeredFields": 3,
 "requiredFields": 3,
 "requiredAnswered": 3,
 "errors": []
 },
 {
 "pageNumber": 2,
 "pageTitle": "Dietary Requirements",
 "isValid": true,
 "hasAnswers": false,
 "totalFields": 2,
 "answeredFields": 0,
 "requiredFields": 0,
 "requiredAnswered": 0,
 "errors": []
 }
 ],
 "totalPages": 2,
 "validPages": 2,
 "invalidPages": 0,
 "allPagesValid": true
 }
}
 
 Response - Already submitted: 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Status: SUBMITTED",
 "data": {
 "responseId": "550e8400-e29b-41d4-a716-446655440099",
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "success": true,
 "status": "SUBMITTED",
 "submittedAt": "2025-01-23T10:50:00Z",
 "totalPages": 2,
 "validPages": 2,
 "invalidPages": 0,
 "allPagesValid": true
 }
}
 
 
 2.6 Withdraw Submission 
 Endpoint: 
 DELETE /api/v1/e-events/{eventId}/attendee-questions/responses
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Submission withdrawn"
}
 
 
 PART 3: ANALYTICS ENDPOINTS (Organizer Only) 
 Base URL 
 /api/v1/e-events/{eventId}/attendee-questions/analytics
 
 Note: Only the event organizer can access analytics. 
 
 3.1 Get Summary Statistics 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/summary
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Summary retrieved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "eventTitle": "Music Festival 2025",
 "totalStarted": 250,
 "totalDrafts": 45,
 "totalSubmitted": 180,
 "totalWithdrawn": 25,
 "completionRate": 72.0,
 "dropOffRate": 28.0,
 "averageCompletionTimeSeconds": 185.5,
 "fastestCompletionSeconds": 45,
 "slowestCompletionSeconds": 890,
 "submissionTrend": [
 {"date": "2025-01-01", "count": 0},
 {"date": "2025-01-02", "count": 5},
 {"date": "2025-01-03", "count": 12},
 {"date": "2025-01-04", "count": 8},
 {"date": "2025-01-05", "count": 15},
 {"date": "2025-01-06", "count": 22},
 {"date": "2025-01-07", "count": 18}
 ],
 "pageDropOff": [
 {
 "pageNumber": 1,
 "pageTitle": "Personal Information",
 "started": 250,
 "completed": 220,
 "dropOffRate": 12.0
 },
 {
 "pageNumber": 2,
 "pageTitle": "Dietary Requirements",
 "started": 220,
 "completed": 180,
 "dropOffRate": 18.2
 }
 ],
 "firstSubmissionAt": "2025-01-02T09:15:00",
 "lastSubmissionAt": "2025-01-23T10:50:00",
 "generatedAt": "2025-01-23T11:00:00"
 }
}
 
 
 3.2 Get All Fields Analytics 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Fields analytics retrieved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "totalResponses": 180,
 "fields": [
 {
 "fieldId": "990e8400-e29b-41d4-a716-446655440004",
 "label": "Emergency Contact Name",
 "type": "TEXT",
 "pageNumber": 1,
 "required": true,
 "responseCount": 180,
 "skippedCount": 0,
 "responseRate": 100.0,
 "textStats": {
 "averageLength": 15,
 "minLength": 5,
 "maxLength": 45,
 "commonWords": ["john", "jane", "mary", "james", "david"]
 }
 },
 {
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "label": "T-Shirt Size",
 "type": "DROPDOWN",
 "pageNumber": 1,
 "required": true,
 "responseCount": 180,
 "skippedCount": 0,
 "responseRate": 100.0,
 "optionBreakdown": [
 {"value": "Medium", "count": 65, "percentage": 36.1},
 {"value": "Large", "count": 58, "percentage": 32.2},
 {"value": "Small", "count": 35, "percentage": 19.4},
 {"value": "X-Large", "count": 22, "percentage": 12.2}
 ]
 },
 {
 "fieldId": "dd0e8400-e29b-41d4-a716-446655440008",
 "label": "Dietary Restrictions",
 "type": "CHECKBOX",
 "pageNumber": 2,
 "required": false,
 "responseCount": 95,
 "skippedCount": 85,
 "responseRate": 52.8,
 "optionBreakdown": [
 {"value": "Vegetarian", "count": 42, "percentage": 44.2},
 {"value": "Gluten-free", "count": 28, "percentage": 29.5},
 {"value": "Vegan", "count": 18, "percentage": 18.9},
 {"value": "Halal", "count": 12, "percentage": 12.6},
 {"value": "Kosher", "count": 5, "percentage": 5.3}
 ]
 }
 ]
 }
}
 
 
 3.3 Get Single Field Analytics 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields/{fieldId}?page=0&size=20
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Field analytics retrieved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "fieldId": "bb0e8400-e29b-41d4-a716-446655440006",
 "label": "T-Shirt Size",
 "type": "DROPDOWN",
 "required": true,
 "totalResponses": 180,
 "responseCount": 180,
 "skippedCount": 0,
 "responseRate": 100.0,
 "optionBreakdown": [
 {"value": "Medium", "count": 65, "percentage": 36.1},
 {"value": "Large", "count": 58, "percentage": 32.2},
 {"value": "Small", "count": 35, "percentage": 19.4},
 {"value": "X-Large", "count": 22, "percentage": 12.2}
 ],
 "responses": [
 {
 "responseId": "resp-001",
 "value": "Large",
 "submittedAt": "2025-01-23T10:50:00"
 },
 {
 "responseId": "resp-002",
 "value": "Medium",
 "submittedAt": "2025-01-23T10:48:00"
 }
 ],
 "totalResponseCount": 180,
 "page": 0,
 "pageSize": 20
 }
}
 
 
 3.4 Get All Responses (Spreadsheet View) 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/responses?page=0&size=20
 
 Response (200 OK): 
 {
 "success": true,
 "httpStatus": "OK",
 "message": "Responses retrieved",
 "data": {
 "eventId": "660e8400-e29b-41d4-a716-446655440001",
 "totalResponses": 180,
 "page": 0,
 "pageSize": 20,
 "totalPages": 9,
 "columns": [
 {"fieldId": "990e8400-e29b-41d4-a716-446655440004", "label": "Emergency Contact Name", "type": "TEXT", "pageNumber": 1},
 {"fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "label": "Emergency Contact Phone", "type": "PHONE", "pageNumber": 1},
 {"fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "label": "T-Shirt Size", "type": "DROPDOWN", "pageNumber": 1},
 {"fieldId": "dd0e8400-e29b-41d4-a716-446655440008", "label": "Dietary Restrictions", "type": "CHECKBOX", "pageNumber": 2},
 {"fieldId": "ee0e8400-e29b-41d4-a716-446655440009", "label": "Other Dietary Notes", "type": "TEXTAREA", "pageNumber": 2}
 ],
 "responses": [
 {
 "responseId": "resp-001",
 "submittedById": "user-001",
 "submittedByName": "kibuti",
 "submittedByEmail": "kibuti@example.com",
 "status": "SUBMITTED",
 "startedAt": "2025-01-23T10:40:00",
 "submittedAt": "2025-01-23T10:50:00",
 "completionTimeSeconds": 600,
 "answers": {
 "990e8400-e29b-41d4-a716-446655440004": "John Doe",
 "aa0e8400-e29b-41d4-a716-446655440005": "+255712345678",
 "bb0e8400-e29b-41d4-a716-446655440006": "Large",
 "dd0e8400-e29b-41d4-a716-446655440008": ["Vegetarian", "Gluten-free"],
 "ee0e8400-e29b-41d4-a716-446655440009": "No nuts please"
 }
 },
 {
 "responseId": "resp-002",
 "submittedById": "user-002",
 "submittedByName": "john_doe",
 "submittedByEmail": "john@example.com",
 "status": "SUBMITTED",
 "startedAt": "2025-01-23T10:30:00",
 "submittedAt": "2025-01-23T10:45:00",
 "completionTimeSeconds": 900,
 "answers": {
 "990e8400-e29b-41d4-a716-446655440004": "Jane Smith",
 "aa0e8400-e29b-41d4-a716-446655440005": "+255798765432",
 "bb0e8400-e29b-41d4-a716-446655440006": "Medium",
 "dd0e8400-e29b-41d4-a716-446655440008": null,
 "ee0e8400-e29b-41d4-a716-446655440009": null
 }
 }
 ]
 }
}
 
 
 3.5 Export to CSV 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/csv
 
 Response: Downloads a CSV file 
 CSV Content: 
 Response ID,Submitted By,Email,Submitted At,Completion Time (seconds),Emergency Contact Name,Emergency Contact Phone,T-Shirt Size,Dietary Restrictions,Other Dietary Notes
resp-001,kibuti,kibuti@example.com,2025-01-23T10:50:00,600,John Doe,+255712345678,Large,Vegetarian; Gluten-free,No nuts please
resp-002,john_doe,john@example.com,2025-01-23T10:45:00,900,Jane Smith,+255798765432,Medium,,
 
 
 3.6 Export to Excel 
 Endpoint: 
 GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/excel
 
 Response: Downloads an Excel (.xlsx) file 
 
 PART 4: COMPLETE ENDPOINTS REFERENCE 
 Organizer Endpoints (Draft Events) 
 
 
 
 Method 
 Endpoint 
 Description 
 
 
 
 
 PUT 
 /drafts/{draftId}/attendee-questions 
 Enable questionnaire 
 
 
 PATCH 
 /drafts/{draftId}/attendee-questions 
 Update settings 
 
 
 GET 
 /drafts/{draftId}/attendee-questions 
 Get with full form 
 
 
 DELETE 
 /drafts/{draftId}/attendee-questions 
 Disable (delete) 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/pages 
 Add page 
 
 
 PATCH 
 /drafts/{draftId}/attendee-questions/pages/{pageId} 
 Update page 
 
 
 DELETE 
 /drafts/{draftId}/attendee-questions/pages/{pageId} 
 Delete page 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/pages/reorder 
 Reorder pages 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/pages/{pageId}/fields 
 Add field 
 
 
 PATCH 
 /drafts/{draftId}/attendee-questions/fields/{fieldId} 
 Update field 
 
 
 DELETE 
 /drafts/{draftId}/attendee-questions/fields/{fieldId} 
 Delete field 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/pages/{pageId}/fields/reorder 
 Reorder fields 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/fields/{fieldId}/options 
 Add option 
 
 
 PATCH 
 /drafts/{draftId}/attendee-questions/options/{optionId} 
 Update option 
 
 
 DELETE 
 /drafts/{draftId}/attendee-questions/options/{optionId} 
 Delete option 
 
 
 POST 
 /drafts/{draftId}/attendee-questions/fields/{fieldId}/options/reorder 
 Reorder options 
 
 
 
 Attendee Endpoints (Published Events) 
 
 
 
 Method 
 Endpoint 
 Description 
 
 
 
 
 GET 
 /{eventId}/attendee-questions 
 Get form (page 1) 
 
 
 GET 
 /{eventId}/attendee-questions/pages/{n} 
 Get specific page 
 
 
 PATCH 
 /{eventId}/attendee-questions/responses/pages/{n} 
 Save page (auto-save) 
 
 
 POST 
 /{eventId}/attendee-questions/responses/submit 
 Submit (no body) 
 
 
 GET 
 /{eventId}/attendee-questions/responses/status 
 Get my status 
 
 
 DELETE 
 /{eventId}/attendee-questions/responses 
 Withdraw 
 
 
 
 Analytics Endpoints (Organizer Only) 
 
 
 
 Method 
 Endpoint 
 Description 
 
 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/summary 
 Overall stats 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/fields 
 All fields breakdown 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/fields/{fieldId} 
 Single field detail 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/responses 
 Spreadsheet view 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/export/csv 
 Download CSV 
 
 
 GET 
 /{eventId}/attendee-questions/analytics/export/excel 
 Download Excel 
 
 
 
 
 PART 5: INSOMNIA TESTING GUIDE 
 Setup Environment Variables 
 base_url: http://localhost:8080/api/v1
jwt_token: <your_token>
draft_id: <your_draft_id>
event_id: <your_published_event_id>
page_id: <page_uuid>
field_id: <field_uuid>
option_id: <option_uuid>
 
 Testing Flow 
 Step 1: Create Event Draft 
 POST {{base_url}}/e-events/drafts
Authorization: Bearer {{jwt_token}}
Content-Type: application/json

{
 "title": "Test Music Festival 2025",
 "categoryId": "{{category_id}}",
 "eventFormat": "IN_PERSON",
 "description": "Test event for questionnaire"
}
 
 Step 2: Enable Attendee Questions 
 PUT {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions
Authorization: Bearer {{jwt_token}}
Content-Type: application/json

{
 "displayTime": "BEFORE_CHECKOUT",
 "isRequiredOnline": true,
 "applyToAtDoor": false
}
 
 Step 3: Get Questionnaire (see default page) 
 GET {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions
Authorization: Bearer {{jwt_token}}
 
 Step 4: Add Text Field 
 POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/pages/{{page_id}}/fields
Authorization: Bearer {{jwt_token}}
Content-Type: application/json

{
 "type": "TEXT",
 "label": "Emergency Contact Name",
 "placeholder": "Full name",
 "required": true,
 "validation": {
 "minLength": 2,
 "maxLength": 100
 }
}
 
 Step 5: Add Dropdown Field 
 POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/pages/{{page_id}}/fields
Authorization: Bearer {{jwt_token}}
Content-Type: application/json

{
 "type": "DROPDOWN",
 "label": "T-Shirt Size",
 "required": true
}
 
 Step 6: Add Options to Dropdown 
 POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/fields/{{field_id}}/options
Authorization: Bearer {{jwt_token}}
Content-Type: application/json

{"label": "Small"}
 
 Repeat for: Medium, Large, X-Large 
 Step 7: Publish Event 
 POST {{base_url}}/e-events/drafts/{{draft_id}}/publish
Authorization: Bearer {{jwt_token}}
 
 Step 8: (As Attendee) Get Questionnaire 
 GET {{base_url}}/e-events/{{event_id}}/attendee-questions
Authorization: Bearer {{attendee_token}}
 
 Step 9: (As Attendee) Save Page 1 
 PATCH {{base_url}}/e-events/{{event_id}}/attendee-questions/responses/pages/1
Authorization: Bearer {{attendee_token}}
Content-Type: application/json

{
 "answers": {
 "{{text_field_id}}": {
 "fieldId": "{{text_field_id}}",
 "value": "John Doe"
 },
 "{{dropdown_field_id}}": {
 "fieldId": "{{dropdown_field_id}}",
 "value": "Large"
 }
 }
}
 
 Step 10: (As Attendee) Submit 
 POST {{base_url}}/e-events/{{event_id}}/attendee-questions/responses/submit
Authorization: Bearer {{attendee_token}}
 
 Step 11: (As Organizer) View Analytics 
 GET {{base_url}}/e-events/{{event_id}}/attendee-questions/analytics/summary
Authorization: Bearer {{jwt_token}}
 
 Step 12: (As Organizer) Export CSV 
 GET {{base_url}}/e-events/{{event_id}}/attendee-questions/analytics/export/csv
Authorization: Bearer {{jwt_token}}
 
 
 This guide covers the complete Attendee Questions API. Ready to implement and test!