Events API Events Management API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2025-02-20 Version : v1.1 Base URL : https://your-api-domain.com/api/v1/e-events Short Description : The Event Core API provides full lifecycle management for events on the NextGate platform — from creating drafts and configuring schedules, to publishing, managing live events, and discovery. It is used by event organizers to build, publish, and manage events step-by-step, and by all authenticated users to browse, search, and filter published events. Hints : 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 → REGISTRATION_SETUPS → TICKETS → REVIEW . 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 └──────┬──────┘ │ PATCH /drafts/{id}/basic-info (optional update) │ │ PATCH /draft/{id}/schedule ▼ ┌──────────────┐ │ SCHEDULE │ days[ date, startTime, endTime ], timezone └──────┬───────┘ │ │ PATCH /draft/{id}/location ▼ ┌──────────────────┐ │ LOCATION DETAILS │ venue (IN_PERSON/HYBRID) or virtualDetails (ONLINE/HYBRID) └──────┬───────────┘ or skip entirely (TBA format) │ │ PATCH /drafts/{id}/registration ▼ ┌───────────────────────┐ │ REGISTRATION SETUPS │ registrationOpensAt, registrationClosesAt └──────┬────────────────┘ │ │ (Ticket creation via Tickets API — required before publish) ▼ ┌──────────────┐ │ TICKETS │ at least one active ticket required └──────┬───────┘ │ │ (Optional enrichment) │ PATCH /drafts/{id}/highlights │ PATCH /drafts/{id}/faqs │ PATCH /drafts/{id}/lineup │ PATCH /drafts/{id}/agenda │ POST /draft/{id}/products/{productId} │ POST /draft/{id}/shops/{shopId} │ │ PATCH /{eventId}/publish ▼ ┌───────────────┐ │ PUBLISHED │ RSA keys generated, category count incremented, │ │ event visible in public feed └───────────────┘ Event Status Flow DRAFT ◄──────────────────────► PUBLISHED │ (unpublish │ │ if 0 tickets sold) │ (unpublish — only if no tickets sold) │ │ │ (discardDraft) │ ▼ ▼ [deleted] CANCELLED ◄─── (cancel from any non-terminal status) │ [terminal state] PUBLISHED ──► (system / scheduled job) ──► HAPPENING ──► COMPLETED Status Rules : 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 currentStage string Current event creation stage completedStages[] array List of completed stage names completionPercentage integer 0–100 completion percentage canPublish boolean Whether all required stages are completed createdAt ZonedDateTime Creation timestamp updatedAt ZonedDateTime Last update timestamp createdBy string Username of creator updatedBy string Username of last editor EventDayInfo Field Type Description id UUID Day entity ID date string Date in YYYY-MM-DD format startTime string Start time in HH:mm:ss endTime string End time in HH:mm:ss description string Optional day description dayOrder integer Display order (1-based) HighlightEntry Field Type Description type string AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM title string Display title for this highlight value string Short value (e.g., "18+") description string Longer explanation FaqEntry Field Type Description question string The FAQ question answer string The answer order integer Display order LineupEntry Field Type Description entryType string PLATFORM_USER or CUSTOM userId UUID Platform user ID (only when entryType=PLATFORM_USER ) name string Display name (auto-enriched from user profile when PLATFORM_USER ) role string HEADLINER , PERFORMER , SPEAKER , DJ , HOST , PANELIST , MODERATOR , GUEST title string Professional title (e.g., "Lead Vocalist") bio string Short biography image string Profile/headshot image URL performanceDay integer Which day number of the event they perform performanceTime string Time of performance order integer Display order AgendaDay Field Type Description dayNumber integer Day number (1-based) date string Date in YYYY-MM-DD format sessions[] array See AgendaSession definition below AgendaSession Field Type Description startTime string Session start time ( HH:mm ) endTime string Session end time ( HH:mm ) title string Session title description string Session description type string GENERAL , PERFORMANCE , CEREMONY , PANEL , WORKSHOP , NETWORKING , MEAL , BREAK location string Sub-location within the event venue presenterType string PLATFORM_USER or CUSTOM presenterId UUID Platform user ID (when presenterType=PLATFORM_USER ) presenterName string Presenter name (auto-enriched from user profile when PLATFORM_USER ) presenterTitle string Professional title presenterBio string Short biography presenterImage string Headshot image URL TicketSummaryInfo Field Type Description id UUID Ticket type ID name string Ticket type name (e.g., "VIP", "General Admission") price BigDecimal Ticket price (0.00 for free) totalTickets integer Total slots allocated ticketsSold integer Number sold ticketsAvailable integer Remaining slots isSoldOut boolean True when available = 0 attendanceMode string Ticket attendance mode enum value status string Ticket status enum value isOnSale boolean Whether the ticket is currently on sale B. EventSummaryResponse (Lightweight List Object) Returned by all paginated list and search endpoints. Field Type Description id UUID Event ID title string Event title slug string URL slug shortDescription string First 150 characters of description categoryId UUID Category ID categoryName string Category display name eventFormat string IN_PERSON , ONLINE , HYBRID , TBA eventVisibility string PUBLIC , PRIVATE , UNLISTED status string Event status startDateTime ZonedDateTime Start date/time with offset endDateTime ZonedDateTime End date/time with offset timezone string IANA timezone locationSummary string Human-readable location (e.g., "Dar es Salaam, TZ", "Online Event", "Location To Be Announced") thumbnail string Thumbnail URL pricing.minPrice BigDecimal Lowest available ticket price pricing.maxPrice BigDecimal Highest available ticket price pricing.isFree boolean True when all tickets are free pricing.hasPaidTickets boolean True when at least one paid ticket exists organizerId UUID Organizer ID organizerName string Organizer full name organizerUsername string Organizer username stats.totalTickets integer Sum of all ticket slots stats.ticketsSold integer Total tickets sold stats.ticketsAvailable integer Remaining tickets stats.isSoldOut boolean True when no tickets remain stats.attendeeCount integer Same as ticketsSold createdAt ZonedDateTime Creation timestamp C. Standard Paginated Response Wrapper All list endpoints return data inside a Spring Page wrapper. { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ ], "pageable": { "pageNumber": 0, "pageSize": 10 }, "totalElements": 42, "totalPages": 5, "last": false, "first": true, "empty": false } } Note : All list endpoints accept page (1-based, default 1 ) and size (default 10 ) as query parameters. Spring internally converts to 0-based before querying. HTTP Method Badge Standards 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 Content-Type string Yes application/json Request JSON Sample : { "title": "Dar es Salaam Jazz Festival 2025", "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "eventFormat": "IN_PERSON", "eventVisibility": "PUBLIC", "description": "An annual celebration of jazz and live music at the heart of the city.", "media": { "banner": "https://cdn.example.com/banners/jazz-2025.jpg", "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg", "gallery": [] } } Request Body Parameters : Parameter Type Required Description Validation title string Yes Event title Min: 3, Max: 200 characters categoryId UUID Yes ID of an active event category Must exist and be active eventFormat string Yes Event format Enum: IN_PERSON , ONLINE , HYBRID , TBA eventVisibility string No Who can see the event Enum: PUBLIC , PRIVATE , UNLISTED . Defaults to PUBLIC description string No Full event description Max: 5000 characters media object No Media URLs for the event See MediaRequest below media.banner string No Banner image URL Max: 500 characters media.thumbnail string No Thumbnail image URL Max: 500 characters media.gallery array No List of gallery image URLs — Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Event draft created", "action_time": "2025-02-17T10:30:45", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Dar es Salaam Jazz Festival 2025", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "description": "An annual celebration of jazz...", "category": { "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "categoryName": "Music & Concerts", "categorySlug": "music-concerts" }, "eventFormat": "IN_PERSON", "eventVisibility": "PUBLIC", "status": "DRAFT", "currentStage": "BASIC_INFO", "completedStages": ["BASIC_INFO"], "completionPercentage": 20, "canPublish": false, "schedule": null, "venue": null, "virtualDetails": null, "media": { "banner": "https://cdn.example.com/banners/jazz-2025.jpg", "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg", "gallery": [] }, "highlights": null, "faqs": null, "lineup": null, "agenda": null, "linkedProducts": [], "linkedShops": [], "tickets": [], "organizer": { "organizerId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "organizerName": "Amina Hassan", "organizerUsername": "amina.hassan" }, "createdAt": "2025-02-17T10:30:45+03:00", "updatedAt": null, "createdBy": "amina.hassan", "updatedBy": null } } Success Response Fields : See Shared Response Object A — EventResponse . Possible Error Responses : Status Scenario 401 No or expired token — see Shared Error: 401 404 Category ID not found 422 Missing required fields (title, categoryId, eventFormat) — see Shared Error: 422 2. Get My Drafts Purpose : Returns a paginated list of all DRAFT events owned by the authenticated organizer. Endpoint : GET /api/v1/e-events/drafts Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Drafts retrieved", "action_time": "2025-02-17T10:30:45", "data": { "content": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Dar es Salaam Jazz Festival 2025", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "shortDescription": "An annual celebration of jazz and live music...", "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "categoryName": "Music & Concerts", "eventFormat": "IN_PERSON", "eventVisibility": "PUBLIC", "status": "DRAFT", "startDateTime": null, "endDateTime": null, "timezone": null, "locationSummary": null, "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg", "pricing": { "isFree": true, "hasPaidTickets": false }, "organizerName": "Amina Hassan", "organizerUsername": "amina.hassan", "stats": { "totalTickets": 0, "ticketsSold": 0, "ticketsAvailable": 0, "isSoldOut": false }, "createdAt": "2025-02-17T10:30:45+03:00" } ], "totalElements": 3, "totalPages": 1, "first": true, "last": true, "empty": false } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Pagination fields follow Standard Paginated Response Wrapper . Possible Error Responses : Status Scenario 401 No or expired token — see Shared Error: 401 3. Get Draft by ID Purpose : Retrieves the full detail of a specific draft. Only the draft's organizer can access it. Endpoint : GET /api/v1/e-events/drafts/{draftId} Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 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 Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "title": "Dar es Salaam Jazz Festival 2025 — Updated", "description": "The biggest jazz event in East Africa returns for its 10th edition with over 30 artists.", "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "eventVisibility": "PUBLIC", "eventFormat": "IN_PERSON", "media": { "banner": "https://cdn.example.com/banners/jazz-2025-v2.jpg", "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v2.jpg", "gallery": ["https://cdn.example.com/gallery/img1.jpg"] } } Request Body Parameters : Parameter Type Required Description Validation title string No Updated event title Min: 3, Max: 200 characters description string No Updated description Min: 15, Max: 5000 characters categoryId UUID No New category Must exist and be active eventVisibility string No Visibility change Enum: PUBLIC , PRIVATE , UNLISTED eventFormat string No Format change Enum: IN_PERSON , ONLINE , HYBRID , TBA media object No Updated media See media fields in endpoint 1 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Basic info updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Note that completedStages will now include "BASIC_INFO" and currentStage will be "SCHEDULE" . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft or category not found 422 Validation failure on provided fields 6. Update Draft — Schedule Purpose : Sets the event's day-by-day schedule. Supports multi-day events. Each day must have a unique date in chronological order. Advances currentStage to LOCATION_DETAILS . The overall startDateTime and endDateTime on the event are derived automatically from the first and last day. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/schedule Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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. 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 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. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 422 Missing required venue/virtual fields for the event format 8. Update Draft — Registration Config Purpose : Sets the registration window (open and close dates) for the event. The window must be valid relative to itself and cannot close after the event ends. Advances currentStage to TICKETS . Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/registration Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "registrationOpensAt": "2025-05-01T08:00:00+03:00", "registrationClosesAt": "2025-07-17T23:59:00+03:00", "ctaLabel":"Get ticket" } Request Body Parameters : Parameter Type Required Description Validation registrationOpensAt ZonedDateTime Yes When registration opens ISO 8601 with timezone offset; must be before registrationClosesAt registrationClosesAt ZonedDateTime Yes When registration closes ISO 8601 with timezone offset; must not be after the event's endDateTime Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Registration config updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . completedStages will include "REGISTRATION_SETUPS" and currentStage will advance to "TICKETS" . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 422 registrationOpensAt is after registrationClosesAt , or registrationClosesAt is after event end 9. Update Draft — Highlights Purpose : Replaces the full list of event highlights (key attendee-facing info such as age restriction, dress code, parking, etc.). Sending an empty array clears all highlights. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/highlights Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "highlights": [ { "type": "AGE_RESTRICTION", "title": "Age Limit", "value": "18+", "description": "This event is strictly for adults aged 18 and above." }, { "type": "DRESS_CODE", "title": "Dress Code", "value": "Smart Casual", "description": "No sportswear or flip flops allowed." } ] } Request Body Parameters : Parameter Type Required Description Validation highlights array Yes Full list of highlights Empty array is valid (clears existing) highlights[].type string Yes Highlight category Enum: AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM highlights[].title string Yes Display title — highlights[].value string No Short value summary — highlights[].description string No Longer explanation — Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Highlights updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.highlights will reflect the new list. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 10. Update Draft — FAQs Purpose : Replaces the full list of frequently asked questions for the event. Sending an empty array clears all FAQs. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/faqs Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "faqs": [ { "question": "Is this event suitable for children?", "answer": "No. This event is strictly 18+ only.", "order": 1 }, { "question": "Is parking available?", "answer": "Yes, free parking is available on site.", "order": 2 } ] } Request Body Parameters : Parameter Type Required Description Validation faqs array Yes Full list of FAQs Empty array is valid (clears existing) faqs[].question string Yes FAQ question text — faqs[].answer string Yes FAQ answer text — faqs[].order integer No Display order — Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "FAQs updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.faqs will reflect the new list. Possible Error Responses : Same as Endpoint 9 (401, 403, 404). 11. Update Draft — Lineup Purpose : Replaces the full event lineup. Supports both platform users (whose profile data is auto-enriched) and custom entries for external performers or speakers. Sending an empty array clears the lineup. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/lineup Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "lineup": [ { "entryType": "PLATFORM_USER", "userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "role": "HEADLINER", "performanceDay": 1, "performanceTime": "21:00", "order": 1 }, { "entryType": "CUSTOM", "name": "DJ Afrobeat", "role": "DJ", "title": "International Guest DJ", "bio": "Award-winning DJ from Lagos with 10 years of experience.", "image": "https://cdn.example.com/artists/dj-afrobeat.jpg", "performanceDay": 2, "performanceTime": "22:00", "order": 2 } ] } Request Body Parameters : Parameter Type Required Description Validation lineup array Yes Full lineup list Empty array is valid lineup[].entryType string Yes Entry source Enum: PLATFORM_USER , CUSTOM lineup[].userId UUID Conditional Platform user ID Required when entryType=PLATFORM_USER ; must exist in the system lineup[].name string Conditional Display name Required when entryType=CUSTOM ; auto-set from user profile when PLATFORM_USER lineup[].role string No Lineup role Enum: HEADLINER , PERFORMER , SPEAKER , DJ , HOST , PANELIST , MODERATOR , GUEST lineup[].title string No Professional title — lineup[].bio string No Biography text Auto-set from user profile when PLATFORM_USER lineup[].image string No Headshot URL Auto-set from user profile when PLATFORM_USER lineup[].performanceDay integer No Day number of performance — lineup[].performanceTime string No Performance start time ( HH:mm ) — lineup[].order integer No Display order — Auto-enrichment : When entryType=PLATFORM_USER , the system automatically fetches and populates name , bio , and image from the user's profile. These are also refreshed on every GET of the event. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Lineup updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.lineup will contain enriched entries. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found, or a userId in lineup does not exist 12. Update Draft — Agenda Purpose : Replaces the full event agenda organized by day and session. Supports both platform users and custom entries as session presenters. Sending an empty array clears the agenda. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/agenda Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "agenda": [ { "dayNumber": 1, "date": "2025-07-18", "sessions": [ { "startTime": "18:00", "endTime": "19:00", "title": "Welcome & Opening Ceremony", "type": "CEREMONY", "location": "Main Stage", "presenterType": "PLATFORM_USER", "presenterId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" }, { "startTime": "20:00", "endTime": "22:00", "title": "Headliner Performance", "type": "PERFORMANCE", "presenterType": "CUSTOM", "presenterName": "The Sauti Sol Band", "presenterTitle": "Headline Act", "presenterBio": "East Africa's most celebrated group." } ] } ] } Request Body Parameters : Parameter Type Required Description Validation agenda array Yes Full agenda list (by day) Empty array is valid agenda[].dayNumber integer Yes Day number (1-based) — agenda[].date string No Date string ( YYYY-MM-DD ) — agenda[].sessions array Yes Sessions for this day — sessions[].startTime string Yes Session start ( HH:mm ) — sessions[].endTime string Yes Session end ( HH:mm ) — sessions[].title string Yes Session name — sessions[].description string No Session description — sessions[].type string No Session type Enum: GENERAL , PERFORMANCE , CEREMONY , PANEL , WORKSHOP , NETWORKING , MEAL , BREAK sessions[].location string No Sub-location within venue — sessions[].presenterType string No Presenter source Enum: PLATFORM_USER , CUSTOM sessions[].presenterId UUID Conditional Platform user ID Required when presenterType=PLATFORM_USER sessions[].presenterName string Conditional Presenter name Required when presenterType=CUSTOM ; auto-set when PLATFORM_USER sessions[].presenterTitle string No Professional title Auto-enriched for platform users sessions[].presenterBio string No Biography Auto-enriched for platform users sessions[].presenterImage string No Image URL Auto-enriched for platform users Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Agenda updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.agenda will contain enriched sessions. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found, or a presenterId does not exist 13. Attach Product to Event 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 Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID productId UUID Yes The product to attach Must exist and have status ACTIVE Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Product attached", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.linkedProducts will include the newly attached product. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event or product not found 400 Product is not active, already attached, or event is not in DRAFT/PUBLISHED status 14. Remove Product from Event Purpose : Detaches a previously linked product from the event. Allowed on both DRAFT and PUBLISHED events. Endpoint : DELETE /api/v1/e-events/draft/{eventId}/products/{productId} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID productId UUID Yes The product to remove Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Product removed", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or product not currently attached 15. Attach Shop to Event 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 Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID shopId UUID Yes The shop to attach Must exist and have status ACTIVE Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Shop attached", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.linkedShops will include the newly attached shop. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event or shop not found 400 Shop is not active, already attached, or event is not in DRAFT/PUBLISHED status 16. Remove Shop from Event Purpose : Detaches a previously linked shop from the event. Allowed on both DRAFT and PUBLISHED events. Endpoint : DELETE /api/v1/e-events/draft/{eventId}/shops/{shopId} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID shopId UUID Yes The shop to remove Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Shop removed", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Same as Endpoint 14 (401, 403, 404). 17. Publish Event Purpose : Transitions a draft event to PUBLISHED status. This is the final step of the creation workflow. The system validates all required stages are complete, performs a duplicate event check against existing published events, and generates an RSA key pair used for secure ticket QR code signing. Endpoint : PATCH /api/v1/e-events/{eventId}/publish Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 ✓ REGISTRATION_SETUPS stage completed ✓ At least one active ticket exists for the event ✓ registrationOpensAt is before registrationClosesAt ✓ registrationClosesAt is not after event end ✓ 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 date/registration validation fails 500 RSA key generation failed 18. Unpublish Event Purpose : Reverts a PUBLISHED event back to DRAFT status. Only allowed if zero tickets have been sold across all ticket types. If tickets have been sold, the organizer must cancel the event instead. Endpoint : PATCH /api/v1/e-events/{eventId}/unpublish Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to unpublish Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event unpublished successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.status will be "DRAFT" . Error Response Sample (tickets sold): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Cannot unpublish: tickets have already been sold. Please cancel the event instead.", "action_time": "2025-02-17T10:30:45", "data": "Cannot unpublish: tickets have already been sold. Please cancel the event instead." } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not currently PUBLISHED, or tickets have been sold 19. Cancel Event Purpose : Cancels an event. Can be triggered from any non-terminal status ( DRAFT , PUBLISHED , HAPPENING ). This action is irreversible. If the event was published and tickets were sold, a bulk refund process is triggered. Endpoint : PATCH /api/v1/e-events/{eventId}/cancel Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to cancel Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event cancelled successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.status will be "CANCELLED" . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is already CANCELLED or COMPLETED 20. Update Published Event — Basic Info Purpose : Updates the description and/or media of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly. Endpoint : PATCH /api/v1/e-events/{eventId}/published/basic-info Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request JSON Sample : { "description": "Updated: The biggest jazz event in East Africa. Now featuring 35 artists across 3 stages.", "media": { "banner": "https://cdn.example.com/banners/jazz-2025-v3.jpg", "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v3.jpg", "gallery": ["https://cdn.example.com/gallery/img1.jpg", "https://cdn.example.com/gallery/img2.jpg"] } } Request Body Parameters : Parameter Type Required Description Validation description string No Updated event description Min: 15, Max: 5000 characters media object No Updated media URLs See media fields in Endpoint 1 media.banner string No Banner image URL Max: 500 characters media.thumbnail string No Thumbnail image URL Max: 500 characters media.gallery array No Gallery image URLs — Blocked fields : title , categoryId , eventFormat , and eventVisibility cannot be changed on a published event. Providing them will have no effect. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Published event updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not in PUBLISHED status 422 Validation failure on provided fields 21. Update Published Event — Registration Window Purpose : Extends the registration close date for a published event. The new registrationClosesAt must be later than the current value — shortening the registration window is not allowed. Endpoint : PATCH /api/v1/e-events/{eventId}/published/registration Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request JSON Sample : { "registrationClosesAt": "2025-07-20T23:59:00+03:00" } Request Body Parameters : Parameter Type Required Description Validation registrationClosesAt ZonedDateTime Yes New registration close date Must be after the current registrationClosesAt ; must not be after event end Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Registration window extended successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not PUBLISHED 422 New close date is not after current close date, or is after event end 22. Update Published Event — Highlights Purpose : Replaces the full list of highlights for a published event. Behavior is identical to the draft equivalent — sending an empty array clears all highlights. Endpoint : PATCH /api/v1/e-events/{eventId}/published/highlights Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 9 — Update Draft Highlights . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Highlights updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not PUBLISHED 23. Update Published Event — FAQs Purpose : Replaces the full list of FAQs for a published event. Sending an empty array clears all FAQs. Endpoint : PATCH /api/v1/e-events/{eventId}/published/faqs Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 10 — Update Draft FAQs . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "FAQs updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Same as Endpoint 22 (401, 403, 404, 400). 24. Update Published Event — Lineup Purpose : Replaces the full event lineup on a published event. Supports the same platform user enrichment as the draft equivalent. Endpoint : PATCH /api/v1/e-events/{eventId}/published/lineup Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 11 — Update Draft Lineup . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Lineup updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or a userId in lineup does not exist 400 Event is not PUBLISHED 25. Update Published Event — Agenda Purpose : Replaces the full event agenda on a published event. Supports the same platform user enrichment as the draft equivalent. Endpoint : PATCH /api/v1/e-events/{eventId}/published/agenda Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 12 — Update Draft Agenda . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Agenda updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or a presenterId does not exist 400 Event is not PUBLISHED 26. Reveal Location (TBA → Actual) Purpose : Reveals the actual location for an event that was originally published with eventFormat=TBA . The new format cannot be TBA . Required fields depend on the new format chosen. Endpoint : PATCH /api/v1/e-events/{eventId}/published/reveal-location Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 27. Get Event by ID Purpose : Retrieves full event details. Published events are publicly accessible without authentication. Draft events can only be viewed by their organizer. Endpoint : GET /api/v1/e-events/{eventId} Access Level : 🌐 Public (for PUBLISHED events) | 🔒 Protected (for DRAFT events — organizer only) Authentication : Bearer Token (required only for DRAFT access) Request Headers : Header Type Required Description Authorization string Conditional Required when accessing a DRAFT event Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to retrieve Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 403 Attempting to access a DRAFT event without being its organizer 404 Event not found or is soft-deleted 28. Get My Events Purpose : Returns a paginated list of all events (any status) created by the authenticated organizer. Endpoint : GET /api/v1/e-events/my-events Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 12, "totalPages": 2 } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Pagination follows Standard Paginated Response Wrapper . Possible Error Responses : 401 — see Shared Error: 401 . 29. Get My Events by Status Purpose : Returns a paginated list of the authenticated organizer's events filtered by a specific status. Endpoint : GET /api/v1/e-events/my-events/status/{status} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation status string Yes Event status filter Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 5, "totalPages": 1 } } Success Response Fields : Same as Endpoint 28. Possible Error Responses : Status Scenario 401 No or expired token 400 Invalid status enum value 30. Get Events Feed Purpose : Returns a paginated list of all published events for the public discovery feed. Results are ordered by creation date descending. This endpoint will eventually incorporate a recommendation algorithm. Endpoint : GET /api/v1/e-events/events-feed Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events feed retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects — PUBLISHED only ]" ], "totalElements": 87, "totalPages": 9 } } Success Response Fields : data.content[] contains EventSummaryResponse objects (PUBLISHED status only). Possible Error Responses : None expected (no authentication required). 31. Search Events Purpose : Full-text search across published event titles. Results are ordered by start date ascending. Endpoint : GET /api/v1/e-events/search Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default query string Yes Search keyword(s) Non-empty string — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Search results retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 4, "totalPages": 1 } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Possible Error Responses : None expected for normal queries (empty results return an empty content array, not a 404). 32. Filter Events by Date Range Purpose : Returns published events whose schedule overlaps with the provided date range. An event is included if it starts before endDate AND ends after startDate (overlap logic, not exact range match). Endpoint : GET /api/v1/e-events/filter/date Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default startDate ZonedDateTime Yes Range start ISO 8601 with offset (e.g., 2025-07-01T00:00:00+03:00 ) — endDate ZonedDateTime Yes Range end ISO 8601 with offset; must be after startDate — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Filtered events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 7, "totalPages": 1 } } Possible Error Responses : Status Scenario 400 startDate is after endDate 33. Search and Filter Events (Combined) Purpose : Combines keyword search with optional date range filtering in a single call. All parameters are optional — calling with no parameters is equivalent to getting the full published feed. Endpoint : GET /api/v1/e-events/filter Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default query string No Title keyword search — — startDate ZonedDateTime No Date range start ISO 8601 with offset — endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Possible Error Responses : Status Scenario 400 startDate is after endDate (when both are provided) 34. Search My Events (Organizer) Purpose : Allows an organizer to search and filter within their own events across all statuses. Supports keyword, status filter, and date range simultaneously. Endpoint : GET /api/v1/e-events/my-events/search Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default query string No Keyword search on title — — status string No Filter by status Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED — startDate ZonedDateTime No Date range start ISO 8601 with offset — endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Possible Error Responses : Status Scenario 401 No or expired token 400 startDate is after endDate Quick Reference — Endpoint Summary # Method Path Auth Description 1 POST /e-events/drafts 🔒 Create event draft 2 GET /e-events/drafts 🔒 List my drafts (paginated) 3 GET /e-events/drafts/{draftId} 🔒 Get draft by ID 4 DELETE /e-events/drafts/{draftId} 🔒 Discard a draft 5 PATCH /e-events/drafts/{draftId}/basic-info 🔒 Update basic info 6 PATCH /e-events/drafts/{draftId}/schedule 🔒 Update schedule / days 7 PATCH /e-events/drafts/{draftId}/location 🔒 Update venue / virtual details 8 PATCH /e-events/drafts/{draftId}/registration 🔒 Update registration window 9 PATCH /e-events/drafts/{draftId}/highlights 🔒 Update highlights 10 PATCH /e-events/drafts/{draftId}/faqs 🔒 Update FAQs 11 PATCH /e-events/drafts/{draftId}/lineup 🔒 Update lineup 12 PATCH /e-events/drafts/{draftId}/agenda 🔒 Update agenda 13 POST /e-events/draft/{eventId}/products/{productId} 🔒 Attach product (DRAFT or PUBLISHED) 14 DELETE /e-events/draft/{eventId}/products/{productId} 🔒 Remove product (DRAFT or PUBLISHED) 15 POST /e-events/draft/{eventId}/shops/{shopId} 🔒 Attach shop (DRAFT or PUBLISHED) 16 DELETE /e-events/draft/{eventId}/shops/{shopId} 🔒 Remove shop (DRAFT or PUBLISHED) 17 PATCH /e-events/{eventId}/publish 🔒 Publish event 18 PATCH /e-events/{eventId}/unpublish 🔒 Unpublish event (0 sales only) 19 PATCH /e-events/{eventId}/cancel 🔒 Cancel event (terminal) 20 PATCH /e-events/{eventId}/published/basic-info 🔒 Update description/media on published event 21 PATCH /e-events/{eventId}/published/registration 🔒 Extend registration close date 22 PATCH /e-events/{eventId}/published/highlights 🔒 Update highlights on published event 23 PATCH /e-events/{eventId}/published/faqs 🔒 Update FAQs on published event 24 PATCH /e-events/{eventId}/published/lineup 🔒 Update lineup on published event 25 PATCH /e-events/{eventId}/published/agenda 🔒 Update agenda on published event 26 PATCH /e-events/{eventId}/published/reveal-location 🔒 Reveal TBA location 27 GET /e-events/{eventId} 🌐/🔒 Get event by ID 28 GET /e-events/my-events 🔒 List my events 29 GET /e-events/my-events/status/{status} 🔒 My events by status 30 GET /e-events/events-feed 🌐 Public events feed 31 GET /e-events/search 🌐 Search events by keyword 32 GET /e-events/filter/date 🌐 Filter by date range 33 GET /e-events/filter 🌐 Combined search + filter 34 GET /e-events/my-events/search 🔒 Search within my events Note on path inconsistency : Endpoints 5–12 use the path prefix /drafts/{id}/... (plural), while endpoints 6, 7, 13–16 use /draft/{id}/... (singular). This reflects the actual controller routing in the current codebase — use the exact paths shown in the table above. Data Format Standards Concern Standard Timestamps ISO 8601 with timezone offset: 2025-07-18T18:00:00+03:00 Dates YYYY-MM-DD format: 2025-07-18 Times HH:mm:ss (24-hour): 18:00:00 IDs UUID v4: 3fa85f64-5717-4562-b3fc-2c963f66afa6 Pagination 1-based page query parameter, Spring Page wrapper in response Prices BigDecimal with 2 decimal places; 0.00 for free Enums Uppercase strings as defined (e.g., IN_PERSON , PUBLISHED ) Ticket Management API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-02-20 Version : v1.2 Base URL : https://your-api-domain.com/api/v1/e-events/tickets Short Description : The Ticket Management API allows event organizers to define and manage ticket types for their events on NextGate. Organizers can create multiple ticket tiers (e.g. VIP, Early Bird, General Admission), control pricing, capacity, sales periods, visibility, and sales channels. Tickets are scoped to a specific event and follow the event's lifecycle from DRAFT through to PUBLISHED. Hints : 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 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 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 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 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 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 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 Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation ticketId UUID Yes The published ticket to update Must be a valid UUID Request Body Parameters : Parameter Type Required Description Validation visibility string No Updated visibility setting Enum: VISIBLE , HIDDEN , HIDDEN_WHEN_NOT_ON_SALE , CUSTOM_SCHEDULE visibilityStartDate datetime No Start of custom visibility window Required if visibility is CUSTOM_SCHEDULE . ISO 8601 ZonedDateTime visibilityEndDate datetime No End of custom visibility window Required if visibility is CUSTOM_SCHEDULE . Must be after visibilityStartDate . ISO 8601 ZonedDateTime status string No Updated ticket status Enum: ACTIVE , INACTIVE , CLOSED . Cannot set SOLD_OUT or DELETED inclusiveItems array of strings No Full replacement list of perks Max: 50 items. Each: max 200 characters, cannot be blank This endpoint is specifically for PUBLISHED events. For DRAFT events, use the full PUT /{ticketId} endpoint instead. Request JSON Sample : { "visibility": "CUSTOM_SCHEDULE", "visibilityStartDate": "2026-03-01T00:00:00+03:00", "visibilityEndDate": "2026-04-17T23:59:00+03:00", "inclusiveItems": [ "Backstage access", "Complimentary gift bag", "Priority seating", "Artist meet & greet", "Exclusive after-party entry" ] } Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Ticket updated successfully", "action_time": "2026-02-20T10:00:00", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventId": "f1e2d3c4-b5a6-7890-fedc-ba9876543210", "name": "VIP Pass", "description": "Full weekend access with backstage entry and a complimentary gift bag.", "price": 150.00, "ticketPricingType": "PAID", "salesChannel": "EVERYWHERE", "totalTickets": 200, "ticketsSold": 87, "ticketsRemaining": 113, "ticketsAvailable": 113, "isSoldOut": false, "salesStartDateTime": "2026-03-18T05:00:00Z", "salesEndDateTime": "2026-04-17T20:59:00Z", "isOnSale": true, "minQuantityPerOrder": 1, "maxQuantityPerOrder": 4, "maxQuantityPerUser": 4, "visibility": "CUSTOM_SCHEDULE", "visibilityStartDate": "2026-03-01T00:00:00+03:00", "visibilityEndDate": "2026-04-17T23:59:00+03:00", "isCurrentlyVisible": false, "attendanceMode": "IN_PERSON", "inclusiveItems": [ "Backstage access", "Complimentary gift bag", "Priority seating", "Artist meet & greet", "Exclusive after-party entry" ], "status": "ACTIVE", "createdAt": "2025-02-18T10:30:45+03:00", "updatedAt": "2026-02-20T10:00:00+03:00", "createdBy": "john_organizer", "updatedBy": "john_organizer" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Ticket not found 400 Event is not in PUBLISHED status — use the draft update endpoint instead 400 Ticket is deleted 422 Invalid visibility schedule dates, attempted to set SOLD_OUT or DELETED , or CUSTOM_SCHEDULE missing required date fields Event Not Published (400): { "success": false, "httpStatus": "BAD_REQUEST", "message": "This endpoint is only for published events. Use the draft ticket update endpoint instead.", "action_time": "2026-02-20T10:00:00", "data": "This endpoint is only for published events. Use the draft ticket update endpoint instead." } Quick Reference Endpoint Summary # Method Path Description Auth Event Status 1 POST /{eventId} Create a ticket type 🔒 Organizer DRAFT or PUBLISHED 2 PUT /{ticketId} Full update of ticket details 🔒 Organizer DRAFT only 3 GET /{eventId} Get all tickets for an event 🌐 Public Any 4 GET /{eventId}/{ticketId} Get a single ticket by ID 🌐 Public Any 5 PATCH /{eventId}/{ticketId}/capacity Update ticket capacity 🔒 Organizer DRAFT or PUBLISHED 6 PATCH /{eventId}/{ticketId}/status Update ticket status 🔒 Organizer DRAFT or PUBLISHED 7 DELETE /{eventId}/{ticketId} Delete a ticket 🔒 Organizer DRAFT or PUBLISHED (0 sold) 8 PATCH /{ticketId}/sales-window Update sales window dates 🔒 Organizer PUBLISHED 9 PATCH /{ticketId}/published Limited update (visibility, status, perks) 🔒 Organizer PUBLISHED only Common HTTP Status Codes Code Meaning 200 OK Successful GET, PATCH, PUT, or DELETE 201 Created Successful POST (ticket created) 400 Bad Request Business rule violated (wrong status, already sold, duplicate name) 401 Unauthorized Missing or invalid token 403 Forbidden Authenticated but not the event organizer 404 Not Found Event or ticket does not exist 422 Unprocessable Entity Validation errors on request fields Business Rule Cheat Sheet Rule Detail Create ticket Allowed on DRAFT and PUBLISHED events Full update ( PUT ) DRAFT events only Published update ( PATCH /published ) PUBLISHED events only. Visibility, status, and inclusiveItems only Sales window update PUBLISHED events. Validates against registration window Capacity update DRAFT and PUBLISHED events Status update DRAFT and PUBLISHED events FREE ticket Price must be exactly 0.00 PAID ticket Price must be greater than 0.00 DONATION ticket ONLINE_ONLY channel. Max 1 per order and per user. No fixed price — buyer sets amount at checkout. price is null in response Sales window Must fall within the event's registration window Sales period gap Minimum 30 minutes between salesStartDateTime and salesEndDateTime HYBRID event Must have ≥ 1 IN_PERSON ticket and ≥ 1 ONLINE ticket to publish Delete Only allowed if ticketsSold = 0 SOLD_OUT System-managed. Cannot be set manually. Auto-cleared when capacity is increased CLOSED Permanent. Cannot be reversed Event Checkout & Payment API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2025-02-19 Version : v1.0 Base URL : https://api.nextgate.co.tz/api/v1 Short Description : The NextGate Checkout API manages the complete ticket purchasing lifecycle on the NextGate event platform. It supports two distinct checkout flows: online checkout for registered attendees and at-door ticket sales for event organizers and scanner devices. This API should be used by frontend clients, mobile applications, and authorized scanner hardware integrations. Hints : 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 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" } 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 | [ Auto-processed immediately ] | | v | [ GET /checkout/{sessionId} ] | [ — Review session details ] | | | v | [ POST /checkout/{sessionId}/payment ] | [ — Deduct from wallet, create escrow ] | | `----------->-----------' | v [ Booking order created asynchronously ] [ QR codes generated & sent to attendees ] | v [ Session status = COMPLETED ] At any point before payment: [ POST /checkout/{sessionId}/cancel ] [ — Releases ticket hold ] Flow B — At-Door Sale via Scanner Device [ Customer arrives at event venue ] | v [ Scanner device sends sale request ] [ POST /checkout/sell-at-door-ticket/scanner ] | ............|............ . . v v [ Validate scanner ID ] [ Validate device fingerprint ] [ & permissions ] [ & SELL_TICKETS permission ] . . `..........v............' | v [ Validate ticket type belongs to event ] [ Check sales channel = AT_DOOR or BOTH ] | v [ Cash payment processed (no wallet deduction) ] [ Booking order created immediately ] | v [ QR codes returned in response ] [ immediateCheckIn = true → ticket marked as checked-in ] Flow C — At-Door Sale via Organizer [ Organizer is authenticated & accesses event ] | v [ POST /checkout/{eventId}/organizer ] [ — Organizer sells ticket at their counter ] | v [ System verifies organizer owns the event ] | v [ Validate ticket type & attendee count ] | v [ Cash payment recorded (NEUTRAL transaction) ] [ Booking order created ] | v [ QR codes returned in response ] [ immediateCheckIn flag respected ] Endpoints 1. Create Checkout Session Purpose : Creates a new online checkout session for a registered attendee purchasing event tickets, holding the requested quantity and initializing the payment intent. Endpoint : POST /api/v1/e-events/checkout Access Level : 🔒 Protected (Requires valid Bearer token — authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Content-Type string Yes Must be application/json Request JSON Sample : { "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "ticketsForMe": 2, "donationAmount": null, "otherAttendees": [ { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+255712345678", "quantity": 1 } ], "sendTicketsToAttendees": true, "paymentMethodId": null } Request Body Parameters : Parameter Type Required Description Validation eventId UUID Yes The ID of the event being booked Must be a valid published event that has not yet started ticketTypeId UUID Yes The ID of the ticket type being purchased Must belong to the specified event and be active and on sale ticketsForMe integer Yes Number of tickets for the buyer themselves. Use 0 if the buyer is not attending Min: 0 donationAmount decimal No Donation amount in TZS. Only applicable for DONATION type tickets Only used when ticket pricing type is DONATION otherAttendees array No List of other attendees to purchase tickets for Each attendee must have valid name, email, and Tanzanian phone number otherAttendees[].name string Yes (if array provided) Full name of the attendee Min: 2, Max: 100 characters otherAttendees[].email string Yes (if array provided) Email address of the attendee Valid email format; no duplicate emails in the array otherAttendees[].phone string Yes (if array provided) Phone number of the attendee Must match Tanzania format: +255[67]XXXXXXXX otherAttendees[].quantity integer Yes (if array provided) Number of tickets for this attendee Min: 1 sendTicketsToAttendees boolean No If true , QR tickets are sent to each attendee's email. If false , all QR codes are sent to the buyer only Default: true paymentMethodId UUID No ID of a saved payment method. Defaults to wallet if not provided Optional Business Rules : 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, insufficient wallet balance, AT_DOOR_ONLY ticket purchased online, DONATION rules violated, questionnaire not submitted 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Event or ticket type not found 422 UNPROCESSABLE_ENTITY Validation failed on request fields (missing eventId, invalid email format, invalid phone format, etc.) 500 INTERNAL_SERVER_ERROR Unexpected server error 2. Get Checkout Session Purpose : Retrieves the current state of an existing checkout session belonging to the authenticated user. Endpoint : GET /api/v1/e-events/checkout/{sessionId} Access Level : 🔒 Protected (Authenticated attendee — only the session owner can retrieve it) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to retrieve Must be a valid UUID belonging to the authenticated user Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Checkout session retrieved successfully", "action_time": "2025-09-23T10:35:00", "data": { "sessionId": "a1b2c3d4-0000-0000-0000-000000000001", "status": "PENDING_PAYMENT", "customerId": "f1e2d3c4-0000-0000-0000-000000000010", "customerUserName": "john_doe", "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "eventTitle": "Kilimanjaro Jazz Night 2025", "ticketDetails": { "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "ticketTypeName": "VIP", "unitPrice": 50000.00, "ticketsForBuyer": 2, "otherAttendees": [ { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+255712345678", "quantity": 1 } ], "sendTicketsToAttendees": true, "totalQuantity": 3, "subtotal": 150000.00 }, "pricing": { "subtotal": 150000.00, "total": 150000.00 }, "paymentIntent": { "provider": "WALLET", "clientSecret": null, "paymentMethods": ["WALLET"], "status": "PENDING" }, "paymentAttempts": [], "ticketsHeld": true, "ticketHoldExpiresAt": "2025-09-23T10:45:45", "expiresAt": "2025-09-23T10:45:45", "createdAt": "2025-09-23T10:30:45", "updatedAt": "2025-09-23T10:30:45", "completedAt": null, "createdBookingOrderId": null, "isExpired": false, "canRetryPayment": false } } Success Response Fields : Field Description sessionId Unique session identifier status Current session status paymentAttempts List of all payment attempts made on this session, including failures paymentAttempts[].attemptNumber Sequential attempt number (1-indexed) paymentAttempts[].paymentMethod Payment method used for this attempt paymentAttempts[].status Result of the attempt: SUCCESS or FAILED paymentAttempts[].errorMessage Failure reason if the attempt failed paymentAttempts[].attemptedAt Timestamp of the attempt paymentAttempts[].transactionId External or internal transaction reference All other fields Same as Create Checkout Session response Possible Errors : HTTP Status Scenario 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Session not found or does not belong to the authenticated user 500 INTERNAL_SERVER_ERROR Unexpected server error 3. Process Payment Purpose : Initiates wallet payment for a pending checkout session, creating an escrow account and triggering asynchronous booking order creation upon success. Endpoint : POST /api/v1/e-events/checkout/{sessionId}/payment Access Level : 🔒 Protected (Session owner only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to pay for Must be a valid UUID; session must be in PENDING_PAYMENT status and not expired Business Rules : 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, session is expired, or attempting payment on a FREE ticket 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Session not found or does not belong to the authenticated user 500 INTERNAL_SERVER_ERROR Payment processing error, insufficient balance at time of processing 4. Cancel Checkout Session Purpose : Cancels an active checkout session and releases any held tickets back to available inventory. Endpoint : POST /api/v1/e-events/checkout/{sessionId}/cancel Access Level : 🔒 Protected (Session owner only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to cancel Must be a valid UUID belonging to the authenticated user Business Rules : 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 ] [ Wallet balance validated upfront ] [ Ticket hold applied ] | v [ Session status = PENDING_PAYMENT ] [ paymentIntent.provider = WALLET ] | v [ POST /{sessionId}/payment called by client ] | v [ Wallet deducted via double-entry ledger ] [ Escrow account created (ESC-YYYY-NNNNNN)] [ platformFee = 5% of total ] [ sellerAmount = total - platformFee ] | v [ PaymentCompletedEvent published (escrow != null) ] | v [ Booking order created asynchronously ] [ Ticket serials assigned ] [ QR codes generated ] | v [ Session status = COMPLETED ] [ Escrow status = HELD ] [ (Released to organizer on event completion) ] Key rules : Wallet balance is checked twice : once during session creation (upfront validation) and once at actual payment time — a window exists between the two checks where balance could change 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 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, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Booking Orders API provides comprehensive booking management functionality for confirmed event ticket purchases in the Nexgate platform. This API enables users to view their booking details including JWT-signed QR codes for secure event entry, track multi-day event check-ins, retrieve complete booking history, and access event snapshots at time of booking. The system supports both single-day and multi-day events with per-day check-in tracking, automated ticket series generation, and comprehensive booking references. Hints : Auto-Creation : Bookings created automatically after successful checkout payment JWT Security : QR codes are RSA-signed JWTs containing ticket and event data Multi-Day Support : Full support for multi-day events with per-day check-in tracking Ticket Series : Auto-generated unique series (e.g., "VIP-0001", "GENER-0042") Event Snapshots : Event details captured at booking time (immutable) Booking Reference : Short readable codes (e.g., "EVT-2025-A3F4") Check-In Tracking : Complete history with location, staff, and day information Access Control : Customers see own bookings, organizers see event bookings, admins see all Notification Integration : Auto-emails tickets to buyer and optionally to attendees Virtual Events : Includes virtual meeting links for online/hybrid events Attendee Management : Track tickets for buyer and other attendees separately Standard Response Format All API responses follow a consistent structure using our Globe Response Builder pattern: Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2025-12-11T10:30:45", "data": { // Actual response data goes here } } Error Response Structure { "success": false, "httpStatus": "BAD_REQUEST", "message": "Error description", "action_time": "2025-12-11T10:30:45", "data": "Error description" } HTTP Method Badge Standards GET - GET - Green (Read operations) BookingOrderResponse Structure This is the comprehensive response structure returned by booking endpoints: { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "event": { "eventId": "770e8400-e29b-41d4-a716-446655440002", "title": "East African Tech Summit 2025", "startDateTime": "2025-12-15T09:00:00", "endDateTime": "2025-12-17T18:00:00", "timezone": "Africa/Nairobi", "location": "KICC Nairobi, Harambee Avenue, Nairobi", "format": "HYBRID", "virtualDetails": { "platform": "ZOOM", "meetingUrl": "https://zoom.us/j/123456789", "meetingId": "123 456 789", "passcode": "summit2025", "additionalInstructions": "Join 5 minutes early for networking" } }, "organizer": { "name": "TechEvents Kenya", "email": "organizer@techevents.ke", "phone": "+254712345678" }, "customer": { "customerId": "660e8400-e29b-41d4-a716-446655440001", "name": "johndoe", "email": "john@example.com" }, "tickets": [ { "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440010", "ticketTypeName": "VIP Pass", "ticketSeries": "VIP-0001", "price": 150.00, "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "attendanceMode": "IN_PERSON", "attendee": { "name": "John Doe", "email": "john@example.com", "phone": "+255712345678" }, "buyer": { "name": "John Doe", "email": "john@example.com" }, "checkIns": [ { "checkInTime": "2025-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "dayName": "Day 1 - Opening Day", "scannerId": "SCANNER-001", "checkInMethod": "QR_SCAN" }, { "checkInTime": "2025-12-16T08:45:00+03:00", "checkInLocation": "VIP Entrance", "checkedInBy": "Scanner Operator 2", "dayName": "Day 2 - Conference Day", "scannerId": "SCANNER-003", "checkInMethod": "QR_SCAN" } ], "hasBeenCheckedIn": true, "lastCheckedInAt": "2025-12-16T08:45:00+03:00", "lastCheckedInBy": "Scanner Operator 2", "lastCheckInLocation": "VIP Entrance", "lastCheckInDayName": "Day 2 - Conference Day", "status": "USED", "validFrom": "2025-12-15T09:00:00+03:00", "validUntil": "2025-12-17T18:00:00+03:00" }, { "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440011", "ticketTypeName": "General Admission", "ticketSeries": "GENER-0042", "price": 50.00, "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "attendanceMode": "IN_PERSON", "attendee": { "name": "Jane Smith", "email": "jane@example.com", "phone": "+255723456789" }, "buyer": { "name": "John Doe", "email": "john@example.com" }, "checkIns": [], "hasBeenCheckedIn": false, "lastCheckedInAt": null, "lastCheckedInBy": null, "lastCheckInLocation": null, "lastCheckInDayName": null, "status": "ACTIVE", "validFrom": "2025-12-15T09:00:00+03:00", "validUntil": "2025-12-17T18:00:00+03:00" } ], "totalTickets": 2, "checkedInTicketsCount": 1, "subtotal": 200.00, "total": 200.00, "bookedAt": "2025-12-11T10:30:45", "cancelledAt": null } Response Field Descriptions Root Level Fields Field Type Description bookingId string (UUID) Unique booking identifier bookingReference string Short readable code (e.g., "EVT-A3F4B21C") status enum Booking status: CONFIRMED, CANCELLED event object Event snapshot at time of booking organizer object Organizer details snapshot customer object Customer (buyer) information tickets array List of all booked tickets with check-in history totalTickets integer Total number of tickets in booking checkedInTicketsCount integer Count of tickets with at least one check-in subtotal decimal Subtotal amount total decimal Total amount paid bookedAt string (LocalDateTime) When booking was created cancelledAt string (LocalDateTime) When booking was cancelled (null if active) Event Snapshot Object Field Type Description eventId string (UUID) Event identifier title string Event title at time of booking startDateTime string (LocalDateTime) Event start date/time endDateTime string (LocalDateTime) Event end date/time timezone string IANA timezone (e.g., "Africa/Nairobi") location string Full venue location (name + address) format string Event format: IN_PERSON, ONLINE, HYBRID virtualDetails object Virtual meeting details (for ONLINE/HYBRID) Virtual Details Object Field Type Description platform string Platform: ZOOM, GOOGLE_MEET, MS_TEAMS, CUSTOM meetingUrl string Full meeting URL meetingId string Meeting ID (optional) passcode string Meeting passcode (optional) additionalInstructions string Extra instructions (optional) Organizer Snapshot Object Field Type Description name string Organizer name at time of booking email string Organizer email phone string Organizer phone Customer Info Object Field Type Description customerId string (UUID) Customer account ID name string Customer username email string Customer email Booked Ticket Response Object Field Type Description ticketInstanceId string (UUID) Unique ticket instance ID ticketTypeName string Ticket type name (e.g., "VIP Pass") ticketSeries string Auto-generated series (e.g., "VIP-0001") price decimal Ticket price qrCode string JWT-signed QR code (very long string) attendanceMode string IN_PERSON or ONLINE (for hybrid events) attendee object Attendee information buyer object Buyer information (who paid) checkIns array Complete check-in history (all days) hasBeenCheckedIn boolean True if checked in at least once lastCheckedInAt string (ZonedDateTime) Most recent check-in time lastCheckedInBy string Who performed last check-in lastCheckInLocation string Location of last check-in lastCheckInDayName string Day name of last check-in status enum ACTIVE, USED, CANCELLED validFrom string (ZonedDateTime) Ticket valid from validUntil string (ZonedDateTime) Ticket valid until Attendee/Buyer Info Object Field Type Description name string Person's name email string Person's email phone string Person's phone (attendee only) Check-In Record Object Field Type Description checkInTime string (ZonedDateTime) When check-in occurred checkInLocation string Where (e.g., "Main Gate", "VIP Entrance") checkedInBy string Staff/scanner operator name dayName string Event day (e.g., "Day 1", "Day 2 - Saturday") scannerId string Scanner device ID checkInMethod string Method: QR_SCAN (default), MANUAL, NFC BookingOrderSummaryResponse Structure Lightweight response for listing bookings: { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "eventTitle": "East African Tech Summit 2025", "eventStartDateTime": "2025-12-15T09:00:00", "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi", "totalTickets": 2, "checkedInTickets": 1, "total": 200.00, "bookedAt": "2025-12-11T10:30:45" } Endpoints 1. Get Booking by ID Purpose : Retrieve complete booking details including all tickets and check-in history Endpoint : GET {base_url}/e-events/booking-orders/{bookingId} Access Level : 🔒 Protected (Booking Owner, Event Organizer, or Admin) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Path Parameters : Parameter Type Required Description bookingId string (UUID) Yes Booking order ID Success Response : Returns complete BookingOrderResponse structure Success Response Message : "Booking retrieved successfully" HTTP Status Code : 200 OK Access Rules : Customers : Can view their own bookings Organizers : Can view bookings for their events Admins (SUPER_ADMIN, STAFF_ADMIN): Can view any booking Behavior : Returns complete booking details with all tickets Includes event snapshot (as it was at booking time) Shows full check-in history for multi-day events Includes JWT-signed QR codes for all tickets Virtual details included for ONLINE/HYBRID events Use Cases : Customer viewing booking confirmation Customer retrieving QR codes for event entry Organizer checking booking details Admin reviewing booking for support Standard Error Types : 401 UNAUTHORIZED : Authentication issues 403 FORBIDDEN : User doesn't have permission to view this booking 404 NOT_FOUND : Booking not found Error Response Examples : Booking Not Found (404): { "success": false, "httpStatus": "NOT_FOUND", "message": "Booking not found: 550e8400-e29b-41d4-a716-446655440000", "action_time": "2025-12-11T10:30:45", "data": "Booking not found: 550e8400-e29b-41d4-a716-446655440000" } Access Denied (403): { "success": false, "httpStatus": "FORBIDDEN", "message": "You don't have permission to view this booking", "action_time": "2025-12-11T10:30:45", "data": "You don't have permission to view this booking" } 2. Get My Bookings Purpose : Retrieve all bookings for the authenticated user Endpoint : GET {base_url}/e-events/booking-orders/my-bookings Access Level : 🔒 Protected (Authenticated Users) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Success Response : Returns array of BookingOrderSummaryResponse Success Response Message : "Bookings retrieved successfully" Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Bookings retrieved successfully", "action_time": "2025-12-11T10:30:45", "data": [ { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "eventTitle": "East African Tech Summit 2025", "eventStartDateTime": "2025-12-15T09:00:00", "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi", "totalTickets": 2, "checkedInTickets": 1, "total": 200.00, "bookedAt": "2025-12-11T10:30:45" }, { "bookingId": "550e8400-e29b-41d4-a716-446655440001", "bookingReference": "EVT-B5D2E12F", "status": "CONFIRMED", "eventTitle": "Dar es Salaam Food Festival", "eventStartDateTime": "2025-12-20T11:00:00", "eventLocation": "Mlimani City, Sam Nujoma Road, Dar es Salaam", "totalTickets": 4, "checkedInTickets": 0, "total": 150.00, "bookedAt": "2025-12-10T14:20:30" } ] } HTTP Status Code : 200 OK Sorting : Bookings sorted by bookedAt descending (newest first) Behavior : Returns lightweight summary for all user's bookings Sorted newest first Includes check-in progress (checkedInTickets / totalTickets) Shows upcoming and past events Use Cases : User viewing booking history Dashboard showing all purchases Finding booking for upcoming event Checking past event attendance Standard Error Types : 401 UNAUTHORIZED : Authentication issues 404 NOT_FOUND : User not found/authenticated 3. Download Ticket PDF Purpose : Download the ticket PDF for a specific ticket belonging to the authenticated user Endpoint : GET {base_url}/e-events/booking-orders/tickets/{ticketId}/pdf Access Level : 🔒 Protected (Authenticated Users) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Path Parameters : Parameter Type Required Description ticketId UUID Yes The unique ID of the ticket to download Query Parameters : Parameter Type Required Default Description mode string No download Controls how the PDF is served. download forces a file save dialog. inline opens the PDF directly in the browser. Mode Values : Value Content-Disposition Behaviour download attachment; filename="ticket-{series}.pdf" Browser prompts user to save the file inline inline; filename="ticket-{series}.pdf" PDF opens directly in the browser tab Success Response : Returns a binary PDF file Success Response Headers : Header Value Content-Type application/pdf Content-Disposition attachment; filename="ticket-{series}.pdf" (or inline depending on mode) HTTP Status Code : 200 OK Behavior : Verifies the ticket belongs to the authenticated user before generating the PDF Generates the ticket PDF on-the-fly using the event's SVG template The PDF contains event details, attendee name, seat/series, QR code for check-in, and ticket number QR code encodes a signed JWT token used by gate staff to verify the ticket at entry File name in the Content-Disposition header uses the ticket's series (e.g. ticket-VIP-0033.pdf ) Use Cases : User downloading their ticket after booking Re-downloading a lost or deleted ticket Saving ticket to device for offline access at the event Standard Error Types : Code Type Description 401 UNAUTHORIZED Missing or invalid Bearer token 403 FORBIDDEN Ticket does not belong to the authenticated user 404 NOT_FOUND Ticket with the given ID not found 500 INTERNAL_SERVER_ERROR PDF generation failed Booking Status Lifecycle Status Descriptions Status Description Can Cancel? Tickets Valid? CONFIRMED Booking active and tickets valid Future (not implemented) Yes CANCELLED Booking cancelled (placeholder) N/A No Current Implementation : All bookings created as CONFIRMED Cancellation not yet implemented (status exists for future) Tickets remain ACTIVE after booking until checked in Ticket Instance Status Status Lifecycle Status Description Can Check In? ACTIVE Ticket ready for use Yes USED Ticket checked in Yes (for multi-day) CANCELLED Ticket cancelled No Multi-Day Events : Ticket status remains ACTIVE even after first check-in Can check in multiple times (once per day) Status changes to USED after all expected check-ins Each check-in tracked separately in checkIns array Ticket Series Generation How Ticket Series Work Format : {TICKET_CODE}-{COUNTER} Examples : VIP Pass → VIP-0001 , VIP-0002 , VIP-0003 General Admission → GENER-0001 , GENER-0002 Early Bird → EARLY-0001 , EARLY-0002 Ticket Code Extraction : Take first 5 characters of ticket type name Remove spaces Convert to uppercase Fallback to "TICK" if name empty Counter System : Separate counter per ticket type (stored in DB) Pessimistic locking prevents duplicates Counter never resets (unique forever) Increments with each ticket created Example Flow : Ticket Type: "VIP Pass" Counter: 0 First Booking (3 tickets): - VIP-0001 (counter now 1) - VIP-0002 (counter now 2) - VIP-0003 (counter now 3) Second Booking (2 tickets): - VIP-0004 (counter now 4) - VIP-0005 (counter now 5) Why Series Numbers? Easy reference for staff Quick ticket identification Helps track capacity sold Professional appearance on PDF tickets JWT-Signed QR Codes Security Architecture What is the QR Code? Not a random string Full JWT (JSON Web Token) Signed with event's RSA private key Contains complete ticket and event data Cannot be forged or tampered with JWT Structure : Header.Payload.Signature eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9. eyJ0aWNrZXRJbnN0YW5jZUlkIjoiODgw... [RSA signature] JWT Payload Contains : ticketInstanceId (UUID) ticketTypeId (UUID) ticketTypeName (string) ticketSeries (string) eventId (UUID) eventName (string) eventStartDateTime (timestamp) attendeeName (string) attendeeEmail (string) attendeePhone (string) attendanceMode (IN_PERSON/ONLINE) bookingReference (string) eventSchedules (array of days for multi-day events) validFrom (timestamp) validUntil (timestamp) Multi-Day Event Schedules : "eventSchedules": [ { "dayName": "Day 1 - Opening Day", "startDateTime": "2025-12-15T09:00:00+03:00", "endDateTime": "2025-12-15T18:00:00+03:00", "description": "Conference Opening & Keynotes" }, { "dayName": "Day 2 - Conference Day", "startDateTime": "2025-12-16T09:00:00+03:00", "endDateTime": "2025-12-16T18:00:00+03:00", "description": "Technical Sessions & Workshops" }, { "dayName": "Day 3 - Closing Day", "startDateTime": "2025-12-17T09:00:00+03:00", "endDateTime": "2025-12-17T18:00:00+03:00", "description": "Finals & Networking" } ] RSA Key Pair : Generated when event is published (2048-bit) Private key: Stored securely in database (for signing) Public key: Available to scanners (for verification) Cannot derive private key from public key Verification Process (Scanner App): Scanner reads QR code Extracts JWT token Fetches event's public key Verifies JWT signature Checks expiry and validity Checks if already checked in for this day Records check-in if valid Why JWT over Simple Codes? Cannot be forged : Requires private key Contains all data : No database lookup needed Offline capable : Scanner can verify without internet Tamper-proof : Any modification breaks signature Time-limited : validFrom/validUntil built in Day tracking : Multi-day schedules embedded Multi-Day Event Check-Ins How Multi-Day Check-Ins Work Single-Day Event : One check-in expected After check-in, status changes to USED Simple entry tracking Multi-Day Event (e.g., 3-day festival): Multiple check-ins allowed (one per day) Each day tracked separately Ticket remains ACTIVE after first day Full history stored in checkIns array Check-In Record Fields : checkInTime : When check-in happened checkInLocation : Where (e.g., "Main Gate", "VIP Entrance") checkedInBy : Staff/scanner operator name dayName : Which day (must match eventSchedules) scannerId : Scanner device ID checkInMethod : QR_SCAN, MANUAL, NFC (default: QR_SCAN) Example: 3-Day Festival Day 1 (Friday): { "checkInTime": "2025-12-15T18:30:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Staff Member 1", "dayName": "Day 1 - Friday Night", "scannerId": "SCANNER-001", "checkInMethod": "QR_SCAN" } Day 2 (Saturday): { "checkInTime": "2025-12-16T14:15:00+03:00", "checkInLocation": "VIP Entrance", "checkedInBy": "Staff Member 2", "dayName": "Day 2 - Saturday", "scannerId": "SCANNER-003", "checkInMethod": "QR_SCAN" } Day 3 (Sunday): { "checkInTime": "2025-12-17T12:00:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Staff Member 1", "dayName": "Day 3 - Sunday", "scannerId": "SCANNER-001", "checkInMethod": "QR_SCAN" } Validation Rules : dayName must match one from JWT's eventSchedules Cannot check in twice on same day Can check in on different days Check-in time must be within day's start/end Benefits : Accurate attendance tracking per day Prevents duplicate same-day entries Allows organizers to see daily attendance Supports flexible entry (don't need all days) Event Snapshots Why Snapshots? Problem : Event details can change after booking Event renamed Time changed Venue moved Organizer updated contact Solution : Capture event state at booking time Snapshot Fields (Immutable after booking): eventTitle eventStartDateTime eventEndDateTime eventTimezone eventLocation (full venue details) eventFormat virtualDetails (for online/hybrid) organizerName organizerEmail organizerPhone Benefits : PDF Tickets : Generate correct ticket with original details Legal Record : What customer actually purchased Email Confirmations : Show accurate booking details Refund Logic : Reference original event details Audit Trail : Track what changed vs. what was booked Example Scenario : User books ticket for "Tech Summit" at KICC Snapshot created: "Tech Summit", "KICC Nairobi" Organizer renames event to "African Tech Summit" Organizer moves venue to "Safari Park Hotel" User's booking still shows original: "Tech Summit at KICC" This is what they paid for (legal protection) Booking Creation Flow Automatic Creation After Payment Trigger : EventPaymentCompletedListener fires after successful payment Steps : Fetch Entities : Get EventCheckoutSession Get Event details Get TicketType details Generate Booking Reference : Format: EVT-{8-char-UUID} Example: EVT-A3F4B21C Unique and readable Create Ticket Instances : For buyer's tickets (ticketsForMe count) For each other attendee (their quantity) Each ticket gets unique series number Each ticket assigned to correct attendee Generate JWT QR Codes : Create JWT payload with ticket data Include event schedules for multi-day Sign with event's RSA private key Set as ticket's qrCode field Snapshot Event Details : Capture current event title, times, location Capture organizer details Store in booking (immutable) Save Booking : Create EventBookingOrderEntity Save to database with all tickets Link to checkout session Update Checkout Session : Set createdBookingOrderId Mark as COMPLETED Publish Event : Fire BookingCreatedEvent Triggers notifications Send Notifications : Email buyer confirmation with tickets Optionally email other attendees their tickets Notify event organizer of new booking Transaction Safety : Entire flow in single transaction If any step fails, everything rolls back No partial bookings created Timing : Happens immediately after payment success Usually completes in < 2 seconds User gets instant confirmation Notification System Integration Who Gets Notified? 1. Buyer (ALWAYS) : Receives booking confirmation Gets ALL tickets (if sendTicketsToAttendees = false) Gets only THEIR tickets (if sendTicketsToAttendees = true) Channels: EMAIL, SMS, PUSH, IN_APP Priority: HIGH 2. Other Attendees (CONDITIONAL) : Only if sendTicketsToAttendees = true Each attendee gets ONLY their tickets Channels: EMAIL, SMS Priority: HIGH Grouped by email (all tickets for same person) 3. Event Organizer (ALWAYS) : Notified of new booking Gets booking summary (not tickets) Channels: EMAIL, IN_APP Priority: NORMAL Notification Content Buyer Confirmation : Booking reference Event title and date Number of tickets QR codes attached Event location/virtual link Instructions for entry Attendee Ticket Email : "You've received tickets from [Buyer Name]" Event details Their specific QR codes Entry instructions Organizer Alert : New booking notification Buyer name and email Number of tickets sold Total amount Booking reference for lookup QR Code Delivery sendTicketsToAttendees = false : Buyer receives: - Ticket 1 QR (for self) - Ticket 2 QR (for self) - Ticket 3 QR (for Jane) - Ticket 4 QR (for Jane) - Ticket 5 QR (for Bob) Buyer must forward to Jane and Bob manually sendTicketsToAttendees = true : Buyer receives: - Ticket 1 QR (for self) - Ticket 2 QR (for self) Jane receives: - Ticket 3 QR (for Jane) - Ticket 4 QR (for Jane) Bob receives: - Ticket 5 QR (for Bob) Everyone gets their own tickets directly Access Control Rules Who Can View Bookings? 1. Booking Owner (Customer) : Can view all their own bookings GET /booking-orders/my-bookings (all) GET /booking-orders/{bookingId} (specific) 2. Event Organizer : Can view bookings for their events Useful for checking attendance Verifying purchases Customer support 3. Platform Admins : Roles: SUPER_ADMIN, STAFF_ADMIN Can view any booking For support and dispute resolution 4. Others : Cannot view bookings they don't own Cannot view other users' bookings 403 Forbidden error Virtual Event Support Virtual Details Structure Included for : ONLINE events (only virtual) HYBRID events (both physical + virtual) Fields : platform : ZOOM, GOOGLE_MEET, MS_TEAMS, CUSTOM meetingUrl : Full meeting URL (required) meetingId : Meeting ID if applicable passcode : Meeting passcode if required additionalInstructions : Extra guidance Example (Zoom) : { "platform": "ZOOM", "meetingUrl": "https://zoom.us/j/123456789?pwd=abc123", "meetingId": "123 456 789", "passcode": "summit2025", "additionalInstructions": "Please join 5 minutes early for a smooth start" } Example (Custom) : { "platform": "CUSTOM", "meetingUrl": "https://customplatform.com/event/12345", "meetingId": null, "passcode": null, "additionalInstructions": "Use your booking email to log in" } Delivery : Included in booking response Sent in confirmation email Sent in ticket emails Available before event starts Hybrid Events : Virtual details + physical venue Attendees choose format via ticket type IN_PERSON tickets: Physical entry ONLINE tickets: Virtual access Both in same booking possible Date/Time Formats DateTime Standards LocalDateTime (No timezone): Format: YYYY-MM-DDTHH:mm:ss Example: 2025-12-15T09:00:00 Used for: bookedAt, cancelledAt, event snapshots ZonedDateTime (With timezone): Format: YYYY-MM-DDTHH:mm:ss±HH:mm Example: 2025-12-15T09:00:00+03:00 Used for: validFrom, validUntil, checkInTime Why Different Formats? LocalDateTime : Server-local time Audit timestamps No conversion needed Simple comparison ZonedDateTime : User-aware time Ticket validity Check-in times Handles timezone correctly Example : Event in Nairobi (UTC+3): startDateTime: 2025-12-15T09:00:00+03:00 Ticket validFrom: 2025-12-15T09:00:00+03:00 User in London sees: 2025-12-15T06:00:00+00:00 ✓ User in New York sees: 2025-12-15T01:00:00-05:00 ✓ Validation Rules Summary Booking Retrieval Validation Access Validation : ✅ User must be authenticated ✅ Booking must exist ✅ User must be: owner OR organizer OR admin ❌ Other users cannot access Data Validation : ✅ bookingId must be valid UUID ✅ Booking must not be deleted (soft delete support) Quick Reference Guide Common HTTP Status Codes 200 OK : Successful request 401 Unauthorized : Authentication required/failed 403 Forbidden : User doesn't have permission 404 Not Found : Booking not found Booking Reference Format Pattern: EVT-{8-char-UUID} Examples: EVT-A3F4B21C , EVT-F2D5E891 Unique and readable Easy for customer service Ticket Series Format Pattern: {CODE}-{COUNTER} Examples: VIP-0001 , GENER-0042 , EARLY-0123 Code: First 5 chars of ticket name Counter: Unique incrementing number QR Code Format Type: RSA-signed JWT Length: ~1000-2000 characters Contains: Complete ticket + event data Cannot be: Forged, tampered, reused (tracked) Data Format Standards Dates : LocalDateTime for timestamps, ZonedDateTime for validity IDs : UUID format Money : Decimal with 2 decimals (150.00) Currency : TZS (Tanzanian Shilling) Timezone : IANA format (Africa/Nairobi) Booking Flow Checklist ✅ User completes checkout payment ✅ Payment listener triggers ✅ System generates booking reference ✅ System creates ticket instances ✅ System generates JWT QR codes ✅ System snapshots event details ✅ System saves booking order ✅ System publishes booking event ✅ System sends email notifications ✅ User receives confirmation + QR codes Best Practices For Users : Save QR codes to phone immediately Screenshot confirmation email Arrive early for check-in One QR per person at gate For Developers : Always show booking reference prominently Display QR codes large enough to scan Cache booking details locally Handle offline QR display Show check-in history clearly Support PDF ticket generation For Organizers : Provide clear entry instructions Test scanner apps before event Have backup manual verification Track attendance by day Monitor check-in progress live Error Handling Tips 403 Forbidden : Show "This booking belongs to another user" 404 Not Found : Suggest "Check your email for correct reference" Network Error : Show cached booking if available QR Load Failure : Provide booking reference as backup Common Mistakes to Avoid ❌ Showing other users' bookings ❌ Not displaying virtual details for ONLINE events ❌ Forgetting to show multi-day check-in history ❌ Not caching QR codes for offline use ❌ Displaying mutable event data instead of snapshot ❌ Not grouping attendees' tickets properly ❌ Ignoring timezone in validity checks Additional Notes Future Enhancements (Not Yet Implemented) Booking Cancellation : Status: CANCELLED exists but not functional Future: Refund logic + ticket release Future: Cancellation deadline rules Future: Partial cancellations Ticket Transfers : Currently: Tickets assigned at booking Future: Transfer ticket to another person Future: Resale marketplace Future: Name change requests Check-In API : Currently: Manual check-ins not via API Future: Scanner app endpoints Future: QR verification endpoint Future: Manual check-in endpoint Booking Modifications : Currently: Cannot modify after creation Future: Add more tickets Future: Change attendee names Future: Upgrade ticket types Integration Points Payment System : Booking created after payment success Linked via checkoutSessionId Payment status determines creation Notification System : BookingCreatedEvent triggers emails Async processing via listeners Multiple notification channels Escrow System : Payments held until event completion Booking tracks payment via checkout session Organizer paid after event Scanner Apps : Will consume booking data Verify JWT QR codes Record check-ins Update ticket status Conclusion The Event Booking Orders API provides comprehensive booking management with: ✅ Security : JWT-signed QR codes prevent fraud ✅ Flexibility : Multi-day events with per-day tracking ✅ Reliability : Event snapshots preserve booking details ✅ Scalability : Unique ticket series with DB counters ✅ User Experience : Clear references and notifications ✅ Multi-Attendee : Support for group bookings ✅ Virtual Events : Full online/hybrid event support ✅ Access Control : Proper permissions for all parties Event Check-In System API Author : Josh, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Check-In System API provides secure ticket validation and scanner management for event entry. This API enables organizers to generate registration tokens (like WhatsApp's "Link Device"), scanners to register for events, and perform real-time ticket validation with JWT signature verification. The system supports multi-day events with per-day check-in tracking, device fingerprinting for security, automatic scanner revocation, and comprehensive duplicate detection. Hints : 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 // 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 : 2025-02-17 Version : v1.0 Base URL : https://your-api-domain.com/api/v1/e-events/applicant-form Short Description : The Applicant Form API allows event organizers to attach an optional custom multi-page form to their event. When enabled, attendees are prompted to fill in the form as part of the registration flow. The form is fully scoped to the event — all endpoints use eventId as the primary key, so the organizer never needs to track a separate formId . The entire feature is optional; if never enabled, the event registration proceeds with no form step. Hints : 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 automatically creates the underlying form and a default first page — you do not create them separately. startSubmission is idempotent — if the attendee already has a draft response it returns the existing one instead of creating a new one. savePage saves answers for a page without advancing the page index — it is a save-in-place operation. Call submit when all pages are done. 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 │ │ - Form auto-created internally │ │ - Default page auto-created │ │ - Config saved (displayTime etc.) │ └──────────────┬──────────────────────┘ │ │ Build the form structure: │ │ POST /events/{eventId}/pages │ POST /events/{eventId}/pages/{pageId}/fields │ POST /events/{eventId}/fields/{fieldId}/options │ (DROPDOWN / RADIO / CHECKBOX only) │ │ (Optional bulk / clone shortcuts) │ POST /events/{eventId}/pages/bulk │ POST /events/{eventId}/pages/{pageId}/fields/bulk │ POST /events/{eventId}/pages/{pageId}/clone │ POST /events/{eventId}/fields/{fieldId}/clone │ │ Preview before publishing: │ GET /events/{eventId}/preview/metadata │ GET /events/{eventId}/preview/pages/{pageNumber} │ POST /events/{eventId}/preview/pages/{pageNumber}/validate ▼ ┌─────────────────────────────────────┐ │ Form ready — event can publish │ └─────────────────────────────────────┘ Attendee Submission Journey [Attendee — event must be PUBLISHED and within registration window] │ │ POST /events/{eventId}/start ▼ ┌──────────────────────────────────┐ │ Response created (DRAFT) │ Returns existing draft if one │ or existing draft returned │ already exists (idempotent) └──────────────┬───────────────────┘ │ │ For each page: │ PUT /events/{eventId}/pages/{pageId}/save │ Body: { fieldId: { value, fileUrl, ... }, ... } ▼ ┌──────────────────────────────────┐ │ Page answers saved │ Save is in-place, does NOT │ (repeat per page) │ advance page index automatically └──────────────┬───────────────────┘ │ │ POST /events/{eventId}/submit ▼ ┌──────────────────────────────────┐ │ Response SUBMITTED │ completionTimeSeconds recorded └──────────────────────────────────┘ Response Status Flow DRAFT ──────────────────────────────► SUBMITTED │ │ │ (withdraw — not in this API) │ (organizer action — outside scope) ▼ ▼ WITHDRAWN UNDER_REVIEW → APPROVED / REJECTED Standard Response Format All API responses follow a consistent structure using the Globe Response Builder pattern. Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2025-02-17T10:30:45", "data": { } } Error Response Structure { "success": false, "httpStatus": "BAD_REQUEST", "message": "Error description", "action_time": "2025-02-17T10:30:45", "data": "Error description" } Standard Response Fields Field Type Description success boolean true for successful operations, false for errors httpStatus string HTTP status name (OK, CREATED, BAD_REQUEST, etc.) message string Human-readable description of the result action_time string ISO 8601 timestamp of when the response was generated data object / string Response payload on success; error detail on failure HTTP Method Badge Standards 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. 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, required field missing on save 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}" and an initial default page titled "Attendee Information" . Only the event organizer can call this. Returns the full form structure alongside the config. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/enable Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to enable the form for Must be a valid UUID Request JSON Sample : { "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false } Request Body Parameters : Parameter Type Required Description Validation displayTime string No When attendees see the form in the UI flow Enum: BEFORE_CHECKOUT , AFTER_CHECKOUT . Defaults to BEFORE_CHECKOUT isRequiredOnline boolean No Whether online ticket buyers must complete the form before proceeding Defaults to false applyToAtDoor boolean No Whether at-door / walk-in attendees also see the form Defaults to false Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Applicant form enabled with default page", "action_time": "2025-02-17T10:30:45", "data": { "config": { "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false, "createdAt": "2025-02-17T10:30:45+03:00", "updatedAt": null }, "form": { "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025", "description": null, "settings": null, "coverPage": null, "createdBy": "amina.hassan", "createdAt": "2025-02-17T10:30:45", "pages": [ { "pageId": "1a2b3c4d-...", "title": "Attendee Information", "description": "Please provide your details", "displayOrder": 1, "actionButtonText": "Next", "fields": [] } ] } } } Success Response Fields : data is an ApplicantFormSetupResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer, or form is already enabled for this event 404 Event not found 2. Update Form Settings Purpose : Updates the form configuration for the event — displayTime , isRequiredOnline , and applyToAtDoor . Does not affect the form structure (pages/fields). All fields are optional. Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/settings Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Applicant form disabled", "action_time": "2025-02-17T10:30:45", "data": null } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled Page Management 4. Add Page Purpose : Appends a new blank page to the form. displayOrder is auto-assigned as the next position. Fields are added to the page separately. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Request JSON Sample : { "options": [ { "optionId": "optionId-1", "label": "Train" }, { "optionId": "optionId-2", "label": "Bus" } ] } Request Body Parameters : Parameter Type Required Description Validation options array Yes List of option updates Min 1 item options[].optionId UUID Yes The option to update Must be a valid UUID options[].label string No New label Max: 255 characters Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "2 options updated, 0 failed", "action_time": "2025-02-17T10:30:45", "data": { "successCount": 2, "failureCount": 0, "errors": [] } } Success Response Fields : data is a BulkOperationResult . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 An option ID not found — reported in data.errors[] Preview 22. Get Preview Metadata Purpose : Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used to render a progress indicator before the attendee starts filling. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/metadata Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Metadata retrieved", "data": { "formId": "uuid", "formTitle": "Attendee Questions - Tech Summit 2025", "totalPages": 3, "pageNumbers": [ { "pageNumber": 1, "pageTitle": "Personal Info" }, { "pageNumber": 2, "pageTitle": "Experience" }, { "pageNumber": 3, "pageTitle": "Preferences" } ], "responseId": "uuid", "responseStatus": "DRAFT", "currentPageIndex": 1, "completedPageIds": ["page-uuid-1"] } } Success Response Fields : Field Description formId Internal form ID formTitle Form title totalPages Total active pages pageNumbers[].pageNumber 1-based page number pageNumbers[].pageTitle Page heading Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled 23. Preview Page by Number Purpose : Returns a specific page by its 1-based position number, including all its active fields and options. Allows the organizer to preview what attendees will see before publishing. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber} Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageNumber integer Yes 1-based page position Must be ≥ 1 and ≤ total active pages Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Preview page retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "PageResponse object" } } Success Response Fields : data is a PageResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, form not enabled, or pageNumber out of range 24. Validate Preview Page Answers Purpose : Runs validation on a set of answers for a specific page number without creating or saving any data. Lets the organizer test field validation rules before the form goes live. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}/validate Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageNumber integer Yes 1-based page number to validate against Must be ≥ 1 and ≤ total active pages Request JSON Sample : { "fieldId-fullname": { "value": "" }, "fieldId-email": { "value": "not-an-email" } } Request Body : A flat map of fieldId (UUID) → AnswerValue . AnswerValue has value (any), plus optional fileUrl , fileName , fileSize , fileType for FILE fields. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Validation complete", "action_time": "2025-02-17T10:30:45", "data": { "valid": false, "errors": [ { "pageId": "1a2b3c4d-...", "pageTitle": "Attendee Information", "fieldId": "fieldId-fullname", "fieldLabel": "Full Name", "errorMessage": "Full Name is required", "errorType": "REQUIRED" }, { "pageId": "1a2b3c4d-...", "pageTitle": "Attendee Information", "fieldId": "fieldId-email", "fieldLabel": "Email", "errorMessage": "Email must be valid email", "errorType": "INVALID_FORMAT" } ] } } Success Response Fields : Field Description data.valid true if all fields passed validation data.errors[].pageId Page UUID data.errors[].pageTitle Page title data.errors[].fieldId Field UUID data.errors[].fieldLabel Field label data.errors[].errorMessage Human-readable error data.errors[].errorType REQUIRED , INVALID_FORMAT , INVALID_TYPE , VALIDATION_FAILED Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, form not enabled, or pageNumber out of range Attendee Submission 25. Start Submission Purpose : Initiates the attendee's form response session. Creates a new DRAFT response tied to this event's form. If the attendee already has a draft or submitted response for this event's form, the existing response is returned — this endpoint is idempotent. Requires the event to be PUBLISHED and within its registration window. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/start Access Level : 🔒 Protected (Any authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Form submission started", "action_time": "2025-02-17T10:30:45", "data": { "responseId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b", "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "submittedBy": "john.doe", "status": "DRAFT", "completedPageIds": [], "currentPageIndex": 0, "startedAt": "2025-02-17T10:30:45", "submittedAt": null, "completionTimeSeconds": null, "answers": [] } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 403 Event not published, registration not yet open, or registration already closed 404 Event not found or form not enabled 26. Save Page Answers Purpose : Saves the attendee's answers for a specific page. Can be called multiple times — each call replaces the previous answers for that page. This is a save-in-place operation; it does not advance the page index. Call submit (Endpoint 28) when all pages are complete. Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/save Access Level : 🔒 Protected (Attendee — response owner) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageId UUID Yes The page being answered Must be a valid UUID belonging to this event's form Request JSON Sample : { "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f": { "value": "Amina Hassan" }, "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a": { "value": "amina@example.com" }, "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b": { "value": "optionId-bycar" }, "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c": { "value": null, "fileUrl": "https://cdn.example.com/uploads/id.pdf", "fileName": "national-id.pdf", "fileSize": 204800, "fileType": "application/pdf" } } Request Body : A flat Map where each key is a fieldId . Key Type Description {fieldId} UUID (map key) The field being answered .value any Answer value. See answer value types below .fileUrl string FILE fields — uploaded file URL .fileName string FILE fields — original file name .fileSize long FILE fields — file size in bytes .fileType string FILE fields — MIME type Answer Value Types Field Type Expected Value TEXT , TEXTAREA , EMAIL , PHONE , URL String NUMBER Number (integer or decimal) DATE String — YYYY-MM-DD TIME String — HH:mm DATETIME String — ISO 8601 datetime DROPDOWN , RADIO String — UUID of selected option CHECKBOX Array of strings — UUIDs of selected options RATING Integer — 1 to 5 FILE null — metadata goes in fileUrl , fileName , etc. HEADER Omit entirely — no answer needed Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Page saved", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject with updated answers" } } Success Response Fields : data is a FormResponseObject reflecting the updated answer state. Possible Error Responses : Status Scenario 401 No or expired token 403 Registration window closed or event not published 404 Event not found, form not enabled, or no active draft found — call /start first 422 A required field has no value 27. Get My Response Purpose : Returns the authenticated attendee's current response for the event's form — whether still in draft or already submitted. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/my-response Access Level : 🔒 Protected (Any authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Response retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject" } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 404 No response found for this attendee on this event's form 28. Submit Purpose : Finalises and submits the attendee's response. The system validates all pages before accepting. Sets status to SUBMITTED and records completionTimeSeconds . Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/submit Access Level : 🔒 Protected (Attendee — response owner) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Form submitted successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject with status SUBMITTED" } } Success Response Fields : data is a FormResponseObject . data.status will be "SUBMITTED" . Possible Error Responses : Status Scenario 401 No or expired token 403 Already submitted, or registration window closed 404 Event not found, form not enabled, or no draft response found 422 Required fields on one or more pages are unanswered Organizer Response Management 29. Get Response by ID Purpose : Retrieves a specific attendee response by ID. Available to the event organizer only. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses/{responseId} Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID responseId UUID Yes The response to retrieve Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Response retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject" } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or response not found 30. Get All Responses Purpose : Returns a paginated list of all attendee responses for the event's form. For organizer review and export. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (0-based) Min: 0 0 size integer No Items per page Min: 1 20 Note : This endpoint uses 0-based pagination ( page=0 is the first page), matching the Spring Pageable default passed from the controller. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Responses retrieved", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ FormResponseObject entries ]" ], "totalElements": 48, "totalPages": 3, "first": true, "last": false, "empty": false } } Success Response Fields : data.content[] contains FormResponseObject entries. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled 31. Get Analytics Purpose : Returns full submission analytics for the event's form — stats overview, per-field distributions, and daily submission trends. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/analytics Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Analytics retrieved", "action_time": "2025-02-17T10:30:45", "data": { "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "formTitle": "Attendee Questions - Dar es Salaam Jazz Festival 2025", "stats": { "totalStarted": 120, "totalDrafts": 18, "totalSubmitted": 95, "totalWithdrawn": 7, "completionRate": 79.2, "dropOffRate": 20.8, "avgCompletionTimeSeconds": 312.0, "fastestTimeSeconds": 45, "slowestTimeSeconds": 1890 }, "fieldAnalytics": [ { "fieldId": "c3d4e5f6-...", "fieldLabel": "Full Name", "fieldType": "TEXT", "fieldDeleted": false, "totalResponses": 95, "uniqueResponses": 95, "textResponses": ["Amina Hassan", "John Doe", "..."] }, { "fieldId": "e5f6a7b8-...", "fieldLabel": "Arrival method", "fieldType": "DROPDOWN", "fieldDeleted": false, "totalResponses": 95, "choiceDistribution": [ { "option": "By Car", "count": 42, "percentage": 44.2 }, { "option": "Public Transport", "count": 35, "percentage": 36.8 }, { "option": "On Foot", "count": 18, "percentage": 18.9 } ] } ], "dailySubmissions": [ { "date": "2025-07-15", "count": 12 }, { "date": "2025-07-16", "count": 28 } ] } } Success Response Fields : data is a FormAnalytics . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled Quick Reference — Endpoint Summary # Method Path Auth Who Description Form Setup 1 POST /events/{eventId}/enable 🔒 Organizer Enable form + create default page 2 PUT /events/{eventId}/settings 🔒 Organizer Update form display settings 3 DELETE /events/{eventId}/disable 🔒 Organizer Disable and remove form Page Management 4 POST /events/{eventId}/pages 🔒 Organizer Add a page 5 PUT /events/{eventId}/pages/{pageId} 🔒 Organizer Update a page 6 DELETE /events/{eventId}/pages/{pageId}?hard=false 🔒 Organizer Delete a page (soft or hard) 7 POST /events/{eventId}/pages/bulk 🔒 Organizer Bulk add pages with fields 8 DELETE /events/{eventId}/pages/bulk?hard=false 🔒 Organizer Bulk delete pages 9 POST /events/{eventId}/pages/{pageId}/clone 🔒 Organizer Clone a page Field Management 10 POST /events/{eventId}/pages/{pageId}/fields 🔒 Organizer Add a field to a page 11 PUT /events/{eventId}/fields/{fieldId} 🔒 Organizer Update a field 12 DELETE /events/{eventId}/fields/{fieldId}?hard=false 🔒 Organizer Delete a field (soft or hard) 13 POST /events/{eventId}/pages/{pageId}/fields/bulk 🔒 Organizer Bulk add fields 14 DELETE /events/{eventId}/pages/{pageId}/fields/bulk?hard=false 🔒 Organizer Bulk delete fields 15 PATCH /events/{eventId}/fields/bulk 🔒 Organizer Bulk update fields 16 POST /events/{eventId}/fields/{fieldId}/clone 🔒 Organizer Clone a field Option Management 17 POST /events/{eventId}/fields/{fieldId}/options 🔒 Organizer Add option to a field 18 PUT /events/{eventId}/options/{optionId} 🔒 Organizer Update an option 19 DELETE /events/{eventId}/options/{optionId}?hard=false 🔒 Organizer Delete an option (soft or hard) 20 DELETE /events/{eventId}/fields/{fieldId}/options/bulk?hard=false 🔒 Organizer Bulk delete options 21 PATCH /events/{eventId}/options/bulk 🔒 Organizer Bulk update option labels Preview 22 GET /events/{eventId}/preview/metadata 🔒 Organizer Get form page metadata 23 GET /events/{eventId}/preview/pages/{pageNumber} 🔒 Organizer Preview a page by number 24 POST /events/{eventId}/preview/pages/{pageNumber}/validate 🔒 Organizer Validate answers without saving Attendee Submission 25 POST /events/{eventId}/start 🔒 Attendee Start form / get existing draft 26 PUT /events/{eventId}/pages/{pageId}/save 🔒 Attendee Save page answers 27 GET /events/{eventId}/my-response 🔒 Attendee Get my response 28 POST /events/{eventId}/submit 🔒 Attendee Submit form Organizer — Responses & Analytics 29 GET /events/{eventId}/responses/{responseId} 🔒 Organizer Get response by ID 30 GET /events/{eventId}/responses 🔒 Organizer Get all responses (paginated) 31 GET /events/{eventId}/analytics 🔒 Organizer Get form analytics All paths are prefixed with /api/v1/e-events/applicant-form . Data Format Standards Concern Standard Timestamps ZonedDateTime — ISO 8601 with offset: 2025-02-17T10:30:45+03:00 Local timestamps LocalDateTime — ISO 8601 no offset: 2025-02-17T10:30:45 Dates YYYY-MM-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, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Attendance Analytics API provides comprehensive attendee tracking and analytics for event organizers. This API enables organizers to view attendance statistics with per-day breakdowns, list all checked-in attendees with filters, track absentees by category (full no-shows vs specific-day absences), and view detailed check-in history for individual attendees. The system supports multi-day events with separate tracking per day and provides real-time attendance metrics. Hints : Organizer Only : All endpoints restricted to event organizers Multi-Day Support : Per-day statistics and filtering Real-Time : Updates instantly after check-ins Absentee Categories : FULL_NO_SHOW, SPECIFIC_DAY_ONLY, ALL Ticket Type Filter : Filter by specific ticket types Search : Search attendees by name/email Pagination : Lists paginated (default 50 per page) Check-In History : Complete per-day check-in records Attendance Patterns : Tracks which days attended/absent Response Structures AttendanceStatsResponse { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "totalDays": 3, "eventSchedule": [ {"dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2025-12-15"}, {"dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2025-12-16"}, {"dayNumber": 3, "dayName": "Day 3 - Closing", "date": "2025-12-17"} ], "overallStats": { "totalTickets": 500, "totalCheckedIn": 462, "totalAbsent": 38, "attendanceRate": 92.4, "byDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2025-12-15", "totalTickets": 500, "checkedIn": 475, "absent": 25, "attendanceRate": 95.0, "status": "COMPLETED" } ] }, "byTicketType": [ { "ticketTypeId": "uuid", "ticketTypeName": "VIP Pass", "totalSold": 100, "totalCheckedIn": 98, "totalAbsent": 2, "attendanceRate": 98.0, "byDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "checkedIn": 99, "absent": 1, "attendanceRate": 99.0 } ] } ] } AttendeeListResponse { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "dayNumber": 1, "dayName": "Day 1 - Opening", "dayDate": "2025-12-15", "ticketTypeId": "uuid", "ticketTypeName": "VIP Pass", "summary": { "totalTicketsForType": 100, "checkedInThisDay": 99 }, "attendees": [ { "ticketInstanceId": "uuid", "attendeeName": "John Doe", "attendeeEmail": "john@example.com", "attendeePhone": "+255712345678", "ticketType": "VIP Pass", "ticketSeries": "VIP-0001", "bookingReference": "EVT-A3F4B21C", "pricePaid": 150.00, "checkInTime": "2025-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "scannerId": "uuid-string" } ], "pagination": { "currentPage": 0, "pageSize": 50, "totalPages": 2, "totalElements": 99, "hasNext": true, "hasPrevious": false } } AbsenteeListResponse { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "dayNumber": 1, "dayName": "Day 1 - Opening", "dayDate": "2025-12-15", "ticketTypeId": null, "ticketTypeName": null, "summary": { "totalTicketsForType": 500, "absentThisDay": 25, "absenteeRate": 5.0, "breakdown": { "fullNoShow": 10, "specificDayOnly": 15 } }, "absentees": [ { "ticketInstanceId": "uuid", "attendeeName": "Jane Smith", "attendeeEmail": "jane@example.com", "attendeePhone": "+255723456789", "ticketType": "General Admission", "ticketSeries": "GENER-0042", "bookingReference": "EVT-B5D2E12F", "pricePaid": 50.00, "statusForThisDay": "NOT_CHECKED_IN", "attendancePattern": { "totalEventDays": 3, "daysAttended": 2, "daysAbsent": 1, "attendedDayNumbers": [2, 3], "absentDayNumbers": [1], "category": "SPECIFIC_DAY_ONLY" } } ], "pagination": { "currentPage": 0, "pageSize": 50, "totalPages": 1, "totalElements": 25, "hasNext": false, "hasPrevious": false } } AttendeeDetailResponse { "ticketInstanceId": "uuid", "attendeeName": "John Doe", "attendeeEmail": "john@example.com", "attendeePhone": "+255712345678", "ticketType": "VIP Pass", "ticketSeries": "VIP-0001", "bookingReference": "EVT-A3F4B21C", "pricePaid": 150.00, "overallStatus": "FULLY_ATTENDED", "daysAttended": 3, "daysTotal": 3, "checkInsByDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "dayDate": "2025-12-15", "status": "CHECKED_IN", "checkInTime": "2025-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "scannerId": "uuid-string" }, { "dayNumber": 2, "dayName": "Day 2 - Conference", "dayDate": "2025-12-16", "status": "CHECKED_IN", "checkInTime": "2025-12-16T08:45:00+03:00", "checkInLocation": "VIP Entrance", "checkedInBy": "Scanner Operator 2", "scannerId": "uuid-string" }, { "dayNumber": 3, "dayName": "Day 3 - Closing", "dayDate": "2025-12-17", "status": "CHECKED_IN", "checkInTime": "2025-12-17T09:00:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "scannerId": "uuid-string" } ] } Endpoints 1. Get Attendance Stats Endpoint : GET /attendance/{eventId}/stats Access : 🔒 Event Organizer Only Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Success Response : Returns AttendanceStatsResponse Success Response Message : "Attendance stats retrieved" Behavior : Validates organizer owns event Calculates overall attendance metrics Breaks down by day (multi-day events) Breaks down by ticket type Real-time from check-in data Errors : 403 FORBIDDEN : Not event organizer 404 NOT_FOUND : Event not found 2. Get Attendees Endpoint : GET /attendance/{eventId}/attendees?dayNumber=1&ticketTypeId=uuid&search=john&page=0&size=50 Access : 🔒 Event Organizer Only Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Query Parameters : Parameter Type Required Description dayNumber integer Multi-day: Yes Day number (1, 2, 3...) ticketTypeId string (UUID) No Filter by ticket type search string No Search by name/email page integer No Page number (0-indexed, default: 0) size integer No Items per page (default: 50) Success Response : Returns AttendeeListResponse with pagination Success Response Message : "Attendees retrieved" Behavior : Lists checked-in attendees for specified day Filters by ticket type if provided Searches by name/email if provided Paginated results Shows check-in details Validation : Multi-day events require dayNumber Ticket type must belong to event Day number must be valid (1 to totalDays) 3. Get Absentees Endpoint : GET /attendance/{eventId}/absentees?dayNumber=1&ticketTypeId=uuid&category=FULL_NO_SHOW&search=jane&page=0&size=50 Access : 🔒 Event Organizer Only Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Query Parameters : Parameter Type Required Description dayNumber integer Multi-day: Yes Day number (1, 2, 3...) ticketTypeId string (UUID) No Filter by ticket type category enum No ALL, FULL_NO_SHOW, SPECIFIC_DAY_ONLY (default: ALL) search string No Search by name/email page integer No Page number (0-indexed, default: 0) size integer No Items per page (default: 50) Success Response : Returns AbsenteeListResponse with pagination Success Response Message : "Absentees retrieved" Behavior : Lists NOT checked-in attendees for specified day Categories: ALL : Everyone not checked-in for this day FULL_NO_SHOW : Never checked-in any day SPECIFIC_DAY_ONLY : Attended other days but not this one Shows attendance patterns Paginated results Absentee Breakdown : Full no-shows: Tickets never used Specific day only: Partial attendance 4. Get Attendee Detail Endpoint : GET /attendance/{eventId}/attendees/{ticketInstanceId} Access : 🔒 Event Organizer Only Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier ticketInstanceId string (UUID) Yes Ticket instance identifier Success Response : Returns AttendeeDetailResponse Success Response Message : "Attendee detail retrieved" Behavior : Shows complete check-in history Per-day status (CHECKED_IN, NOT_CHECKED_IN, UPCOMING) Overall attendance status Check-in details (time, location, scanner) Overall Status : FULLY_ATTENDED : Checked-in all days PARTIALLY_ATTENDED : Checked-in some days NOT_ATTENDED : Never checked-in Absentee Categories Explained ALL (Default) Who : Everyone who didn't check in for the specified day Includes : Full no-shows (never attended any day) Specific-day absentees (attended other days) Use Case : Complete list of who's missing today FULL_NO_SHOW Who : Tickets never checked-in for ANY event day Characteristics : Purchased ticket but never attended 0% attendance rate Wasted tickets Use Case : Identify completely unused tickets for follow-up SPECIFIC_DAY_ONLY Who : Attended some days but NOT this specific day Characteristics : Checked-in on other event days Partial attendance Shows attendance pattern Use Case : Identify who skipped specific days (e.g., missed keynote) Example : 3-Day Festival: - Ticket A: Attended Day 1, 2, 3 → FULLY_ATTENDED - Ticket B: Attended Day 1, 3 (missed Day 2) → SPECIFIC_DAY_ONLY for Day 2 - Ticket C: Never attended → FULL_NO_SHOW for all days Multi-Day Event Logic Day Number Requirements Single-Day Events : dayNumber optional (defaults to 1) Only one day to track Multi-Day Events : dayNumber REQUIRED Must specify which day (1, 2, 3...) System returns error if missing Validation : Event has 3 days → dayNumber must be 1, 2, or 3 Request dayNumber=5 → Error: "Invalid day 5. Valid: 1-3" Per-Day Tracking Statistics : Total tickets (same for all days) Checked-in (varies per day) Absent (varies per day) Attendance rate (varies per day) Day Status : COMPLETED : Day has passed ONGOING : Day is today UPCOMING : Day hasn't started Example : 3-Day Event (Dec 15-17), Today: Dec 16 Day 1: status=COMPLETED, checkedIn=475 Day 2: status=ONGOING, checkedIn=450 Day 3: status=UPCOMING, checkedIn=0 Attendance Rate Calculations Overall Attendance Rate Rate = (Unique Tickets Checked-In / Total Tickets) × 100 Example : Total tickets: 500 Tickets with at least one check-in: 462 Rate: 92.4% Per-Day Attendance Rate Rate = (Checked-In This Day / Total Tickets) × 100 Example : Total tickets: 500 Checked-in Day 1: 475 Rate: 95.0% Per-Ticket-Type Rate Rate = (Checked-In for Type / Total Sold for Type) × 100 Example : VIP tickets sold: 100 VIP checked-in: 98 Rate: 98.0% Search Functionality Searches : Attendee name (case-insensitive) Attendee email (case-insensitive) Match : Partial match (contains) Examples : Search "john" → Matches "John Doe", "Johnny Smith", "john@example.com" Search "@gmail" → Matches all Gmail addresses Search "255712" → Matches phone numbers containing this Use Cases Dashboard Overview GET /attendance/{eventId}/stats Shows: - Overall attendance rate - Per-day breakdown - Per-ticket-type breakdown - Real-time metrics Check Who Attended Today GET /attendance/{eventId}/attendees?dayNumber=1 Shows: - All checked-in attendees for Day 1 - Check-in times and locations - Searchable and filterable Find No-Shows GET /attendance/{eventId}/absentees?dayNumber=1&category=FULL_NO_SHOW Shows: - Tickets that were never used - For follow-up or refund processing Track Partial Attendance GET /attendance/{eventId}/absentees?dayNumber=2&category=SPECIFIC_DAY_ONLY Shows: - Who attended Day 1 but missed Day 2 - Useful for understanding engagement patterns Individual Ticket History GET /attendance/{eventId}/attendees/{ticketInstanceId} Shows: - Complete check-in history - Which days attended/missed - Check-in details per day Best Practices For Organizers ✅ Monitor attendance in real-time during event ✅ Follow up with full no-shows post-event ✅ Track partial attendance patterns ✅ Export attendee lists for post-event communications ✅ Use absentee data to improve future events For Developers ✅ Require dayNumber for multi-day events ✅ Show attendance rate with 1 decimal (92.4%) ✅ Implement export to CSV functionality ✅ Cache stats (refresh every 5 minutes) ✅ Display check-in times in event timezone Quick Reference HTTP Status Codes 200 OK : Successful request 401 UNAUTHORIZED : Authentication required 403 FORBIDDEN : Not event organizer 404 NOT_FOUND : Event/ticket not found Attendance Status FULLY_ATTENDED : All days checked-in PARTIALLY_ATTENDED : Some days checked-in NOT_ATTENDED : Never checked-in Day Status COMPLETED : Day has passed ONGOING : Day is today UPCOMING : Day hasn't started Absentee Categories ALL : All absentees for this day FULL_NO_SHOW : Never attended any day SPECIFIC_DAY_ONLY : Missed this day only Conclusion The Attendance Analytics API provides comprehensive tracking with: ✅ Real-Time Stats : Instant attendance metrics ✅ Multi-Day Support : Per-day breakdowns ✅ Absentee Tracking : Full no-shows vs partial attendance ✅ Search & Filter : By ticket type, day, name/email ✅ Individual History : Complete check-in records ✅ Attendance Patterns : Understand engagement Event Feedback API Author : Josh, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Feedback API enables attendees to rate and review events after attendance. This API provides a simple 5-star rating system with optional comments, prevents duplicate feedback, ensures only attendees (non-organizers) can submit reviews, and allows anyone to view event feedback with pagination. The system supports escrow release decisions based on feedback quality and helps organizers improve future events. Hints : 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 (
{stars.map(star => ( onChange(star)} > ⭐ ))}
); }; 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 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