Events API Events Management API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-07-09 Version : v1.3 Base URL : https://your-api-domain.com/api/v1/e-events Short Description : The Event Core API provides full lifecycle management for events on the NextGate platform — from creating drafts and configuring schedules, to publishing, managing live events, and discovery. It is used by event organizers to build, publish, and manage events step-by-step, and by all authenticated users to browse, search, and filter published events. Hints : All write operations (create, update, publish) require a valid Bearer token. Read operations on published events are public. Dates and times must always use ISO 8601 format with timezone offset (e.g., 2025-06-15T09:00:00+03:00 ). The API stores and returns ZonedDateTime . Event creation follows a staged workflow : BASIC_INFO → SCHEDULE → LOCATION_DETAILS → TICKETS . All required stages must be completed before publishing. Slugs are auto-generated from the event title with a UUID suffix to guarantee uniqueness — do not pass a slug manually. Pagination uses 1-based page numbers (page=1 is the first page). Category seeding and management live in the Event Categories API (base: /api/v1/e-events/categories ), documented separately. Banner media is upload-then-reference, not a URL string. Upload the banner image through the FileThunder upload endpoint first to obtain a fileId , then pass that fileId as bannerFileId when creating/updating the event. The API resolves it into a full media object ( bannerMedia ) in responses — do not construct or pass CDN URLs directly. On a PUBLISHED event, the banner can only be changed via the dedicated PATCH /{eventId}/published/banner endpoint (query param, not body) — it is no longer part of the "Update Published Basic Info" request. Event Creation User Journey [Organizer] │ │ POST /draft ▼ ┌─────────────┐ │ BASIC INFO │ title, category, format, description, bannerFileId (optional) └──────┬──────┘ │ PATCH /drafts/{id}/basic-info (optional update) │ │ PATCH /draft/{id}/schedule ▼ ┌──────────────┐ │ SCHEDULE │ days[ date, startTime, endTime ], timezone └──────┬───────┘ │ │ PATCH /draft/{id}/location ▼ ┌──────────────────┐ │ LOCATION DETAILS │ venue (IN_PERSON/HYBRID) or virtualDetails (ONLINE/HYBRID) └──────┬───────────┘ or skip entirely (TBA format) │ │ (Ticket creation via Tickets API — required before publish) ▼ ┌──────────────┐ │ TICKETS │ at least one active ticket required └──────┬───────┘ │ │ (Optional enrichment) │ PATCH /drafts/{id}/highlights │ PATCH /drafts/{id}/faqs │ PATCH /drafts/{id}/lineup │ PATCH /drafts/{id}/agenda │ POST /draft/{id}/products/{productId} │ POST /draft/{id}/shops/{shopId} │ │ PATCH /{eventId}/publish ▼ ┌───────────────┐ │ PUBLISHED │ RSA keys generated, ctaLabel auto-derived if not set, │ │ category count incremented, event visible in public feed └───────────────┘ Event Status Flow DRAFT ◄──────────────────────► PUBLISHED │ (unpublish │ │ if 0 tickets sold) │ (unpublish — only if no tickets sold) │ │ │ (discardDraft) │ ▼ ▼ [deleted] CANCELLED ◄─── (cancel from any non-terminal status) │ [terminal state] PUBLISHED ──► (system / scheduled job) ──► HAPPENING ──► COMPLETED Status Rules : 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). Stripped to null on the public GET /{eventId} endpoint — only returned to the organizer via draft/my-events endpoints virtualDetails.meetingId string Platform meeting ID (same stripping rule as above) virtualDetails.passcode string Meeting passcode (same stripping rule as above) bannerMedia object | null Resolved banner media object. See EventBannerMedia definition below. null if no banner was ever set highlights[] array See HighlightEntry definition below faqs[] array See FaqEntry definition below lineup[] array See LineupEntry definition below agenda[] array See AgendaDay definition below linkedProducts[] array { productId, productName, productSlug } linkedShops[] array { shopId, shopName, shopSlug } tickets[] array See TicketSummaryInfo definition below organizer object See OrganizerInfo definition below ctaLabel string CTA button label (e.g., "Get Tickets", "Register for Free"). Auto-derived from ticket pricing at publish if not explicitly set hasApplicantForm boolean Whether an applicant form is configured for this event applicantForm.displayTime string When the form is shown: BEFORE_CHECKOUT or AFTER_CHECKOUT applicantForm.isRequired boolean Whether form submission is required for online attendees applicantForm.applyToAtDoor boolean Whether the form also applies to at-door check-in currentStage string Current event creation stage completedStages[] array List of completed stage names completionPercentage integer 0–100 completion percentage canPublish boolean Whether all required stages are completed createdAt ZonedDateTime Creation timestamp updatedAt ZonedDateTime Last update timestamp createdBy string Username of creator updatedBy string Username of last editor EventDayInfo Field Type Description id UUID Day entity ID date string Date in YYYY-MM-DD format startTime string Start time in HH:mm:ss endTime string End time in HH:mm:ss description string Optional day description dayOrder integer Display order (1-based) HighlightEntry Field Type Description type string AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM title string Display title for this highlight value string Short value (e.g., "18+") description string Longer explanation FaqEntry Field Type Description question string The FAQ question answer string The answer order integer Display order LineupEntry Field Type Description entryType string PLATFORM_USER or CUSTOM userId UUID Platform user ID (only when entryType=PLATFORM_USER ) name string Display name (auto-enriched from user profile when PLATFORM_USER ) role string HEADLINER , PERFORMER , SPEAKER , DJ , HOST , PANELIST , MODERATOR , GUEST title string Professional title (e.g., "Lead Vocalist") bio string Short biography profileMedia object | null Resolved profile picture — only populated for PLATFORM_USER entries , auto-refreshed from the linked account on every read. Same lean shape as EventBannerPrimaryMedia ( fileId , status , thumbUrl , mimeType ). CUSTOM entries have no image/media at all — there is no way to attach a photo to a custom lineup entry performanceDay integer Which day number of the event they perform performanceTime string Time of performance order integer Display order AgendaDay Field Type Description dayNumber integer Day number (1-based) date string Date in YYYY-MM-DD format sessions[] array See AgendaSession definition below AgendaSession Field Type Description startTime string Session start time ( HH:mm ) endTime string Session end time ( HH:mm ) title string Session title description string Session description type string GENERAL , PERFORMANCE , CEREMONY , PANEL , WORKSHOP , NETWORKING , MEAL , BREAK location string Sub-location within the event venue presenterType string PLATFORM_USER or CUSTOM presenterId UUID Platform user ID (when presenterType=PLATFORM_USER ) presenterName string Presenter name (auto-enriched from user profile when PLATFORM_USER ) presenterTitle string Professional title presenterBio string Short biography presenterMedia object | null Resolved profile picture — only populated for PLATFORM_USER presenters , auto-refreshed from the linked account on every read. Same lean shape as EventBannerPrimaryMedia ( fileId , status , thumbUrl , mimeType ). CUSTOM presenters have no image/media at all — there is no way to attach a photo to a custom presenter TicketSummaryInfo Field Type Description id UUID Ticket type ID name string Ticket type name (e.g., "VIP", "General Admission") price BigDecimal Ticket price (0.00 for free) salesChannel string Ticket sales channel enum value ticketPricingType string FREE , PAID , or DONATION totalTickets integer Total slots allocated ticketsSold integer Number sold ticketsAvailable integer Remaining slots ticketsReserved integer Slots currently held in an active (unpaid) checkout isSoldOut boolean True when available = 0 attendanceMode string Ticket attendance mode enum value status string Ticket status enum value isOnSale boolean Whether the ticket is currently within its sales window saleStatusMessage string Human-readable explanation of the current sale status EventBannerMedia (full media object — on EventResponse) Resolved banner media, returned on the full event detail response. Built from the bannerFileId supplied at creation/update. Field Type Description fileId UUID FileThunder file identifier status string File processing status enum (e.g., READY , PROCESSING , FAILED ) variants map All resolved size/format variants, keyed by variant name (e.g., "thumb" , "medium" , "original" ) → signed/public URL mimeType string MIME type of the uploaded file EventBannerPrimaryMedia (lean media object — on EventSummaryResponse) Lightweight single-URL counterpart used in list/search results — only the "thumb" variant is resolved (falls back to the first available variant if no thumb exists). Field Type Description fileId UUID FileThunder file identifier status string File processing status enum thumbUrl string Single resolved thumbnail URL mimeType string MIME type of the uploaded file OrganizerInfo (on EventResponse only) Field Type Description id UUID Organizer account ID name string Organizer full name ( firstName + " " + lastName ) username string Organizer system username profileMedia object | null Organizer's profile picture, lean shape — same fields as EventBannerPrimaryMedia ( fileId , status , thumbUrl , mimeType ) Note : EventSummaryResponse (list/search results) does not use this nested object — it still exposes flat organizerId / organizerName / organizerUsername fields with no profile picture at all. This is a known inconsistency versus the full EventResponse shape (which was updated to include profileMedia ); the summary shape has not been migrated yet. B. EventSummaryResponse (Lightweight List Object) Returned by all paginated list and search endpoints. Field Type Description id UUID Event ID title string Event title slug string URL slug shortDescription string First 150 characters of description categoryId UUID Category ID categoryName string Category display name eventFormat string IN_PERSON , ONLINE , HYBRID , TBA eventVisibility string PUBLIC , PRIVATE , UNLISTED status string Event status startDateTime ZonedDateTime Start date/time with offset endDateTime ZonedDateTime End date/time with offset timezone string IANA timezone locationSummary string Human-readable location (e.g., "Dar es Salaam, TZ", "Online Event", "Location To Be Announced") bannerMedia object | null Lean resolved banner media — see EventBannerPrimaryMedia above. null if no banner was ever set hasApplicantForm boolean Whether an applicant form is configured ctaLabel string CTA button label pricing.pricingType string FREE , PAID , DONATION , or MIXED — frontend uses this directly for the display label pricing.minPrice BigDecimal | null Lowest available ticket price (PAID/FREE tickets only, excludes DONATION). null if pricing is FREE-only or DONATION-only pricing.maxPrice BigDecimal | null Highest available ticket price (same exclusion rule as minPrice ) pricing.hasFreeTickets boolean True when at least one active FREE ticket type exists pricing.hasPaidTickets boolean True when at least one active PAID ticket type exists pricing.hasDonationTickets boolean True when at least one active DONATION ticket type exists organizerId UUID Organizer ID (flat field — no nested media/profile picture on this lightweight object, see note above) organizerName string Organizer full name organizerUsername string Organizer username stats.totalTickets integer Sum of all ticket slots stats.ticketsSold integer Total tickets sold stats.ticketsAvailable integer Remaining tickets stats.isSoldOut boolean True when no tickets remain stats.attendeeCount integer Same as ticketsSold createdAt ZonedDateTime Creation timestamp C. Standard Paginated Response Wrapper All list endpoints return data inside a Spring Page wrapper. { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ ], "pageable": { "pageNumber": 0, "pageSize": 10 }, "totalElements": 42, "totalPages": 5, "last": false, "first": true, "empty": false } } Note : All list endpoints accept page (1-based, default 1 ) and size (default 10 ) as query parameters. Spring internally converts to 0-based before querying. D. EventStatusResponse (Lightweight Status Object) Returned by the publish/unpublish/cancel transition endpoints. These no longer return the full EventResponse — only the minimal fields needed to confirm the transition. Field Type Description id UUID Event ID slug string Event URL slug status string New status after the transition ( PUBLISHED , DRAFT , or CANCELLED ) If you need the full updated event (media, tickets, organizer, etc.) after a publish/unpublish/cancel call, follow up with GET /{eventId} ( Endpoint 25 ) or GET /drafts/{draftId} ( Endpoint 3 ). HTTP Method Badge Standards 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.", "bannerFileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" } Request Body Parameters : Parameter Type Required Description Validation title string Yes Event title Min: 3, Max: 200 characters categoryId UUID Yes ID of an active event category Must exist and be active eventFormat string Yes Event format Enum: IN_PERSON , ONLINE , HYBRID , TBA eventVisibility string No Who can see the event Enum: PUBLIC , PRIVATE , UNLISTED . Defaults to PUBLIC description string No Full event description Max: 5000 characters bannerFileId UUID No ID of a file already uploaded via FileThunder to use as the banner Must reference an existing uploaded file Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Event draft created", "action_time": "2025-02-17T10:30:45", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Dar es Salaam Jazz Festival 2025", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "description": "An annual celebration of jazz...", "category": { "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "categoryName": "Music & Concerts", "categorySlug": "music-concerts" }, "eventFormat": "IN_PERSON", "eventVisibility": "PUBLIC", "status": "DRAFT", "currentStage": "BASIC_INFO", "completedStages": ["BASIC_INFO"], "completionPercentage": 25, "canPublish": false, "schedule": null, "venue": null, "virtualDetails": null, "bannerMedia": { "fileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", "status": "READY", "variants": { "thumb": "https://cdn.example.com/ft/6b1a2c3d/thumb.jpg", "medium": "https://cdn.example.com/ft/6b1a2c3d/medium.jpg", "original": "https://cdn.example.com/ft/6b1a2c3d/original.jpg" }, "mimeType": "image/jpeg" }, "highlights": null, "faqs": null, "lineup": null, "agenda": null, "linkedProducts": [], "linkedShops": [], "tickets": [], "organizer": { "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "name": "Amina Hassan", "username": "amina.hassan", "profileMedia": { "fileId": "9f8e7d6c-5b4a-3c2d-1e0f-a1b2c3d4e5f6", "status": "READY", "thumbUrl": "https://cdn.example.com/ft/9f8e7d6c/thumb.jpg", "mimeType": "image/jpeg" } }, "ctaLabel": null, "hasApplicantForm": false, "applicantForm": null, "createdAt": "2025-02-17T10:30:45+03:00", "updatedAt": null, "createdBy": "amina.hassan", "updatedBy": null } } Success Response Fields : See Shared Response Object A — EventResponse . Possible Error Responses : Status Scenario 401 No or expired token — see Shared Error: 401 404 Category ID not found 422 Missing required fields (title, categoryId, eventFormat) — see Shared Error: 422 2. Get My Drafts Purpose : Returns a paginated list of all DRAFT events owned by the authenticated organizer. Endpoint : GET /api/v1/e-events/drafts Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Drafts retrieved", "action_time": "2025-02-17T10:30:45", "data": { "content": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Dar es Salaam Jazz Festival 2025", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "shortDescription": "An annual celebration of jazz and live music...", "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "categoryName": "Music & Concerts", "eventFormat": "IN_PERSON", "eventVisibility": "PUBLIC", "status": "DRAFT", "startDateTime": null, "endDateTime": null, "timezone": null, "locationSummary": null, "bannerMedia": { "fileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", "status": "READY", "thumbUrl": "https://cdn.example.com/ft/6b1a2c3d/thumb.jpg", "mimeType": "image/jpeg" }, "hasApplicantForm": false, "ctaLabel": null, "pricing": { "pricingType": "FREE", "minPrice": null, "maxPrice": null, "hasFreeTickets": false, "hasPaidTickets": false, "hasDonationTickets": false }, "organizerId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "organizerName": "Amina Hassan", "organizerUsername": "amina.hassan", "stats": { "totalTickets": 0, "ticketsSold": 0, "ticketsAvailable": 0, "isSoldOut": false }, "createdAt": "2025-02-17T10:30:45+03:00" } ], "totalElements": 3, "totalPages": 1, "first": true, "last": true, "empty": false } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Pagination fields follow Standard Paginated Response Wrapper . Possible Error Responses : Status Scenario 401 No or expired token — see Shared Error: 401 3. Get Draft by ID Purpose : Retrieves the full detail of a specific draft. Only the draft's organizer can access it. Endpoint : GET /api/v1/e-events/drafts/{draftId} Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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", "ctaLabel": "Get Tickets", "bannerFileId": "6b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" } Request Body Parameters : Parameter Type Required Description Validation title string No Updated event title Min: 3, Max: 200 characters description string No Updated description Min: 15, Max: 5000 characters categoryId UUID No New category Must exist and be active eventVisibility string No Visibility change Enum: PUBLIC , PRIVATE , UNLISTED eventFormat string No Format change Enum: IN_PERSON , ONLINE , HYBRID , TBA ctaLabel string No CTA button label override Max: 50 characters. If omitted, existing value is kept; auto-derived at publish if never set bannerFileId UUID No ID of a file already uploaded via FileThunder to replace the banner Must reference an existing uploaded file Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Basic info updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Note that completedStages will now include "BASIC_INFO" and currentStage will be "SCHEDULE" . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft or category not found 422 Validation failure on provided fields 6. Update Draft — Schedule Purpose : Sets the event's day-by-day schedule. Supports multi-day events. Each day must have a unique date in chronological order. Advances currentStage to LOCATION_DETAILS . The overall startDateTime and endDateTime on the event are derived automatically from the first and last day. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/schedule Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "timezone": "Africa/Dar_es_Salaam", "days": [ { "date": "2025-07-18", "startTime": "18:00:00", "endTime": "23:00:00", "description": "Opening Night", "dayOrder": 1 }, { "date": "2025-07-19", "startTime": "16:00:00", "endTime": "23:59:00", "description": "Main Concert Day", "dayOrder": 2 } ] } Request Body Parameters : Parameter Type Required Description Validation timezone string No IANA timezone identifier Must be a valid IANA zone ID (e.g., Africa/Dar_es_Salaam ). Defaults to UTC days array Yes List of event days Min 1 day required days[].date string Yes Date of the day YYYY-MM-DD format; must not be in the past days[].startTime string Yes Day start time HH:mm:ss format days[].endTime string Yes Day end time HH:mm:ss ; must be after startTime days[].description string No Optional day description — days[].dayOrder integer No Display order Defaults to position in array if omitted Notes : All existing days are replaced on each call. To update the schedule, resend the full days array. Dates must be unique — duplicate dates in the same request are rejected with 422 . Days must be provided in chronological order (sorted ascending by date). Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Schedule updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . schedule.days will be populated, and schedule.startDateTime / schedule.endDateTime will be set from the first and last day. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 422 Days out of order, duplicate dates, date in the past, missing time fields 7. Update Draft — Location Purpose : Sets the physical venue and/or virtual meeting details for the event. Required fields depend on eventFormat . For TBA format, this endpoint can be called but no fields are required — the stage is automatically marked complete. Advances currentStage to TICKETS . Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/location Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample (IN_PERSON): { "venue": { "name": "Mlimani City Arena", "address": "Sam Nujoma Road, Dar es Salaam", "coordinates": { "latitude": -6.7724, "longitude": 39.2083 } } } Request JSON Sample (ONLINE): { "virtualDetails": { "meetingLink": "https://zoom.us/j/123456789", "meetingId": "123 456 789", "passcode": "jazz2025" } } Request Body Parameters : Parameter Type Required Description Validation venue object Conditional Required when format is IN_PERSON or HYBRID — venue.name string Yes (if venue) Venue name Max: 200 characters venue.address string No Full address Max: 500 characters venue.coordinates.latitude BigDecimal No GPS latitude — venue.coordinates.longitude BigDecimal No GPS longitude — virtualDetails object Conditional Required when format is ONLINE or HYBRID — virtualDetails.meetingLink string Yes (if virtualDetails) Meeting URL Max: 500 characters virtualDetails.meetingId string No Platform meeting ID Max: 100 characters virtualDetails.passcode string No Meeting passcode Max: 100 characters Format-based rules : IN_PERSON → venue.name is required; virtualDetails is ignored ONLINE → virtualDetails.meetingLink is required; venue is ignored HYBRID → both venue.name and virtualDetails.meetingLink are required TBA → no fields required; stage is immediately marked complete Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Location updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . completedStages will include "LOCATION_DETAILS" when requirements are met and currentStage will advance to "TICKETS" . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 422 Missing required venue/virtual fields for the event format 8. Update Draft — Highlights Purpose : Replaces the full list of event highlights (key attendee-facing info such as age restriction, dress code, parking, etc.). Sending an empty array clears all highlights. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/highlights Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "highlights": [ { "type": "AGE_RESTRICTION", "title": "Age Limit", "value": "18+", "description": "This event is strictly for adults aged 18 and above." }, { "type": "DRESS_CODE", "title": "Dress Code", "value": "Smart Casual", "description": "No sportswear or flip flops allowed." } ] } Request Body Parameters : Parameter Type Required Description Validation highlights array Yes Full list of highlights Empty array is valid (clears existing) highlights[].type string Yes Highlight category Enum: AGE_RESTRICTION , CHECK_IN_TIME , PARKING , DRESS_CODE , FOOD_DRINKS , ACCESSIBILITY , REFUND_POLICY , WHAT_TO_BRING , PROHIBITED_ITEMS , WEATHER_INFO , CUSTOM highlights[].title string Yes Display title — highlights[].value string No Short value summary — highlights[].description string No Longer explanation — Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Highlights updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.highlights will reflect the new list. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found 9. Update Draft — FAQs Purpose : Replaces the full list of frequently asked questions for the event. Sending an empty array clears all FAQs. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/faqs Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "faqs": [ { "question": "Is this event suitable for children?", "answer": "No. This event is strictly 18+ only.", "order": 1 }, { "question": "Is parking available?", "answer": "Yes, free parking is available on site.", "order": 2 } ] } Request Body Parameters : Parameter Type Required Description Validation faqs array Yes Full list of FAQs Empty array is valid (clears existing) faqs[].question string Yes FAQ question text — faqs[].answer string Yes FAQ answer text — faqs[].order integer No Display order — Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "FAQs updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.faqs will reflect the new list. Possible Error Responses : Same as Endpoint 8 (401, 403, 404). 10. Update Draft — Lineup Purpose : Replaces the full event lineup. Supports both platform users (whose profile data is auto-enriched) and custom entries for external performers or speakers. Sending an empty array clears the lineup. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/lineup Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "lineup": [ { "entryType": "PLATFORM_USER", "userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "role": "HEADLINER", "performanceDay": 1, "performanceTime": "21:00", "order": 1 }, { "entryType": "CUSTOM", "name": "DJ Afrobeat", "role": "DJ", "title": "International Guest DJ", "bio": "Award-winning DJ from Lagos with 10 years of experience.", "performanceDay": 2, "performanceTime": "22:00", "order": 2 } ] } Request Body Parameters : Parameter Type Required Description Validation lineup array Yes Full lineup list Empty array is valid lineup[].entryType string Yes Entry source Enum: PLATFORM_USER , CUSTOM lineup[].userId UUID Conditional Platform user ID Required when entryType=PLATFORM_USER ; must exist in the system lineup[].name string Conditional Display name Required when entryType=CUSTOM ; auto-set from user profile when PLATFORM_USER lineup[].role string No Lineup role Enum: HEADLINER , PERFORMER , SPEAKER , DJ , HOST , PANELIST , MODERATOR , GUEST lineup[].title string No Professional title — lineup[].bio string No Biography text Auto-set from user profile when PLATFORM_USER lineup[].performanceDay integer No Day number of performance — lineup[].performanceTime string No Performance start time ( HH:mm ) — lineup[].order integer No Display order — Auto-enrichment : When entryType=PLATFORM_USER , the system automatically fetches and populates name , bio , and profileMedia (resolved profile picture object) from the user's profile. These are also refreshed on every GET of the event. There is no image field on this request — CUSTOM entries cannot have a photo at all; only PLATFORM_USER entries get one, sourced from the linked account. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Lineup updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.lineup will contain enriched entries. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found, or a userId in lineup does not exist 11. Update Draft — Agenda Purpose : Replaces the full event agenda organized by day and session. Supports both platform users and custom entries as session presenters. Sending an empty array clears the agenda. Endpoint : PATCH /api/v1/e-events/drafts/{draftId}/agenda Access Level : 🔒 Protected (Organizer — own drafts only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation draftId UUID Yes The draft to update Must be a valid UUID Request JSON Sample : { "agenda": [ { "dayNumber": 1, "date": "2025-07-18", "sessions": [ { "startTime": "18:00", "endTime": "19:00", "title": "Welcome & Opening Ceremony", "type": "CEREMONY", "location": "Main Stage", "presenterType": "PLATFORM_USER", "presenterId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" }, { "startTime": "20:00", "endTime": "22:00", "title": "Headliner Performance", "type": "PERFORMANCE", "presenterType": "CUSTOM", "presenterName": "The Sauti Sol Band", "presenterTitle": "Headline Act", "presenterBio": "East Africa's most celebrated group." } ] } ] } Request Body Parameters : Parameter Type Required Description Validation agenda array Yes Full agenda list (by day) Empty array is valid agenda[].dayNumber integer Yes Day number (1-based) — agenda[].date string No Date string ( YYYY-MM-DD ) — agenda[].sessions array Yes Sessions for this day — sessions[].startTime string Yes Session start ( HH:mm ) — sessions[].endTime string Yes Session end ( HH:mm ) — sessions[].title string Yes Session name — sessions[].description string No Session description — sessions[].type string No Session type Enum: GENERAL , PERFORMANCE , CEREMONY , PANEL , WORKSHOP , NETWORKING , MEAL , BREAK sessions[].location string No Sub-location within venue — sessions[].presenterType string No Presenter source Enum: PLATFORM_USER , CUSTOM sessions[].presenterId UUID Conditional Platform user ID Required when presenterType=PLATFORM_USER sessions[].presenterName string Conditional Presenter name Required when presenterType=CUSTOM ; auto-set when PLATFORM_USER sessions[].presenterTitle string No Professional title Auto-enriched for platform users sessions[].presenterBio string No Biography Auto-enriched for platform users There is no presenterImage field on this request — CUSTOM presenters cannot have a photo at all; only PLATFORM_USER presenters get one ( presenterMedia , resolved profile picture object), sourced from the linked account. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Agenda updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.agenda will contain enriched sessions. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the draft owner 404 Draft not found, or a presenterId does not exist 12. Attach Product to Event 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/{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 13. Remove Product from Event Purpose : Detaches a previously linked product from the event. Allowed on both DRAFT and PUBLISHED events. Endpoint : DELETE /api/v1/e-events/{eventId}/products/{productId} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID productId UUID Yes The product to remove Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Product removed", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or product not currently attached 14. Attach Shop to Event Purpose : Links an existing active shop from the PRODUCT domain to an event. Allowed on both DRAFT and PUBLISHED events. Marks the LINKS stage as completed. Endpoint : POST /api/v1/e-events/{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 15. Remove Shop from Event Purpose : Detaches a previously linked shop from the event. Allowed on both DRAFT and PUBLISHED events. Endpoint : DELETE /api/v1/e-events/{eventId}/shops/{shopId} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event (DRAFT or PUBLISHED) Must be a valid UUID shopId UUID Yes The shop to remove Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Shop removed", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Same as Endpoint 13 (401, 403, 404). 16. Publish Event Purpose : Transitions a draft event to PUBLISHED status. This is the final step of the creation workflow. The system validates all required stages are complete, performs a duplicate event check against existing published events, generates an RSA key pair used for secure ticket QR code signing, and auto-derives the ctaLabel from ticket pricing if the organizer has not set one. Endpoint : PATCH /api/v1/e-events/{eventId}/publish Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to publish Must be a valid UUID Pre-publish checklist (all must pass or the request is rejected with 422 ): BASIC_INFO stage completed ✓ SCHEDULE stage completed (at least one day, not in the past) ✓ LOCATION_DETAILS stage completed ✓ At least one active ticket exists for the event ✓ Event start date is not in the past ✓ No duplicate event detected with ≥85% similarity score to another organizer's public event ✓ Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event published successfully", "action_time": "2025-02-17T10:30:45", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "status": "PUBLISHED" } } Success Response Fields : data is an EventStatusResponse — not the full EventResponse . Error Response Sample (duplicate detected): { "success": false, "httpStatus": "BAD_REQUEST", "message": "This event appears to be a duplicate of 'Dar es Salaam Jazz Night' by user123. Please make the title, date, or location more distinct.", "action_time": "2025-02-17T10:30:45", "data": "This event appears to be a duplicate..." } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is already published, or duplicate event detected 422 One or more required stages are incomplete, or no active tickets exist 500 RSA key generation failed 17. Unpublish Event Purpose : Reverts a PUBLISHED event back to DRAFT status. Only allowed if zero tickets have been sold across all ticket types. If tickets have been sold, the organizer must cancel the event instead. Endpoint : PATCH /api/v1/e-events/{eventId}/unpublish Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to unpublish Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event unpublished successfully", "action_time": "2025-02-17T10:30:45", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "status": "DRAFT" } } Success Response Fields : data is an EventStatusResponse — not the full EventResponse . Error Response Sample (tickets sold): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Cannot unpublish: tickets have already been sold. Please cancel the event instead.", "action_time": "2025-02-17T10:30:45", "data": "Cannot unpublish: tickets have already been sold. Please cancel the event instead." } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not currently PUBLISHED, or tickets have been sold 18. Cancel Event Purpose : Cancels an event. Can be triggered from any non-terminal status ( DRAFT , PUBLISHED , HAPPENING ). This action is irreversible. If the event was published and tickets were sold, a bulk refund process is triggered. Endpoint : PATCH /api/v1/e-events/{eventId}/cancel Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to cancel Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event cancelled successfully", "action_time": "2025-02-17T10:30:45", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4", "status": "CANCELLED" } } Success Response Fields : data is an EventStatusResponse — not the full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is already CANCELLED or COMPLETED 19. Update Published Event — Basic Info Purpose : Updates the description and/or CTA label of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly. Banner updates are no longer part of this endpoint — use Endpoint 19a — Update Published Event — Banner instead. Endpoint : PATCH /api/v1/e-events/{eventId}/published/basic-info Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request JSON Sample : { "description": "Updated: The biggest jazz event in East Africa. Now featuring 35 artists across 3 stages.", "ctaLabel": "Get Tickets Now" } Request Body Parameters : Parameter Type Required Description Validation description string No Updated event description Min: 15, Max: 5000 characters ctaLabel string No CTA button label override Max: 50 characters. If provided and non-blank → saved as-is. If omitted or null → re-derived from current ticket pricing Excluded fields : title , categoryId , and eventFormat are not present on this request at all (too breaking for a published event whose slug is already shared). eventVisibility and banner media are also not accepted here — banner updates now go through Endpoint 19a as a separate call. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event info updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not in PUBLISHED status 422 Validation failure on provided fields 19a. Update Published Event — Banner Purpose : Replaces the banner image on a published event. Split out from the basic-info endpoint since the banner is a FileThunder-managed upload, not a plain string field. Endpoint : PATCH /api/v1/e-events/{eventId}/published/banner Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Query Parameters : Parameter Type Required Description Validation bannerFileId UUID Yes ID of a file already uploaded via FileThunder to use as the new banner Must reference an existing uploaded file Note : Unlike every other update endpoint in this API, this one takes its input as a query parameter, not a JSON request body — there is no request body for this call. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event banner updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.bannerMedia reflects the newly resolved banner. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or bannerFileId does not reference an existing file 400 Event is not in PUBLISHED status 20. Update Published Event — Highlights Purpose : Replaces the full list of highlights for a published event. Behavior is identical to the draft equivalent — sending an empty array clears all highlights. Endpoint : PATCH /api/v1/e-events/{eventId}/published/highlights Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 8 — Update Draft Highlights . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Highlights updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not PUBLISHED 21. Update Published Event — FAQs Purpose : Replaces the full list of FAQs for a published event. Sending an empty array clears all FAQs. Endpoint : PATCH /api/v1/e-events/{eventId}/published/faqs Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 9 — Update Draft FAQs . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "FAQs updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Same as Endpoint 20 (401, 403, 404, 400). 22. Update Published Event — Lineup Purpose : Replaces the full event lineup on a published event. Supports the same platform user enrichment as the draft equivalent. Endpoint : PATCH /api/v1/e-events/{eventId}/published/lineup Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 10 — Update Draft Lineup . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Lineup updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or a userId in lineup does not exist 400 Event is not PUBLISHED 22a. Update Check-in Settings Purpose : Configures the check-in window for attendee entry — how early before start, and how late after start, attendees are allowed to check in. Endpoint : PATCH /api/v1/e-events/{eventId}/checkin-settings Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to update Must be a valid UUID Request JSON Sample : { "checkInBeforeValue": 2, "checkInBeforeUnit": "HOURS", "checkInAfterValue": 30, "checkInAfterUnit": "MINUTES" } Request Body Parameters : Parameter Type Required Description Validation checkInBeforeValue integer Yes How long before start check-in opens Min: 1 checkInBeforeUnit string Yes Unit for checkInBeforeValue Enum: MINUTES , HOURS , DAYS checkInAfterValue integer Yes How long after start check-in remains open Min: 0 checkInAfterUnit string Yes Unit for checkInAfterValue Enum: MINUTES , HOURS , DAYS Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Check-in settings updated", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 422 Missing/invalid check-in value or unit 23. Update Published Event — Agenda Purpose : Replaces the full event agenda on a published event. Supports the same platform user enrichment as the draft equivalent. Endpoint : PATCH /api/v1/e-events/{eventId}/published/agenda Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event to update Must be a valid UUID Request Body : Same format as Endpoint 11 — Update Draft Agenda . Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Agenda updated successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, or a presenterId does not exist 400 Event is not PUBLISHED 24. Reveal Location (TBA → Actual) Purpose : Reveals the actual location for an event that was originally published with eventFormat=TBA . The new format cannot be TBA . Required fields depend on the new format chosen. Endpoint : PATCH /api/v1/e-events/{eventId}/published/reveal-location Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published TBA event Must be a valid UUID Request JSON Sample (revealing as IN_PERSON): { "eventFormat": "IN_PERSON", "venue": { "name": "Mlimani City Arena", "address": "Sam Nujoma Road, Dar es Salaam", "coordinates": { "latitude": -6.7724, "longitude": 39.2083 } } } Request JSON Sample (revealing as ONLINE): { "eventFormat": "ONLINE", "virtualDetails": { "meetingLink": "https://zoom.us/j/123456789", "meetingId": "123 456 789", "passcode": "jazz2025" } } Request Body Parameters : Parameter Type Required Description Validation eventFormat string Yes The actual event format Enum: IN_PERSON , ONLINE , HYBRID — cannot be TBA venue object Conditional Physical venue details Required when new format is IN_PERSON or HYBRID venue.name string Yes (if venue) Venue name Max: 200 characters venue.address string No Full address Max: 500 characters venue.coordinates.latitude BigDecimal No GPS latitude — venue.coordinates.longitude BigDecimal No GPS longitude — virtualDetails object Conditional Virtual meeting details Required when new format is ONLINE or HYBRID virtualDetails.meetingLink string Yes (if virtualDetails) Meeting URL Max: 500 characters virtualDetails.meetingId string No Platform meeting ID Max: 100 characters virtualDetails.passcode string No Meeting passcode Max: 100 characters Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Location revealed successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found 400 Event is not PUBLISHED, current format is not TBA, or new format is TBA 422 Missing required venue/virtual fields for the chosen format 25. Get Event by ID Purpose : Retrieves full event details. Published events are publicly accessible without authentication. Draft events can only be viewed by their organizer. Endpoint : GET /api/v1/e-events/{eventId} Access Level : 🌐 Public (for PUBLISHED events) | 🔒 Protected (for DRAFT events — organizer only) Authentication : Bearer Token (required only for DRAFT access) Request Headers : Header Type Required Description Authorization string Conditional Required when accessing a DRAFT event Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to retrieve Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full EventResponse object" } } Success Response Fields : data is a full EventResponse . data.virtualDetails is always null on this endpoint — it uses the public-response mapping, which strips meeting link/ID/passcode even when the requester is the event's own organizer. While the event is still DRAFT , virtual meeting details can be read via GET /drafts/{draftId} ( Endpoint 3 ); there is currently no endpoint that returns virtualDetails for an already- PUBLISHED event, even to its organizer. Possible Error Responses : Status Scenario 403 Attempting to access a DRAFT event without being its organizer 404 Event not found or is soft-deleted 26. Get My Events Purpose : Returns a paginated list of all events (any status) created by the authenticated organizer. Endpoint : GET /api/v1/e-events/my-events Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 12, "totalPages": 2 } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Pagination follows Standard Paginated Response Wrapper . Possible Error Responses : 401 — see Shared Error: 401 . 27. Get My Events by Status Purpose : Returns a paginated list of the authenticated organizer's events filtered by a specific status. Endpoint : GET /api/v1/e-events/my-events/status/{status} Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation status string Yes Event status filter Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 5, "totalPages": 1 } } Success Response Fields : Same as Endpoint 26. Possible Error Responses : Status Scenario 401 No or expired token 400 Invalid status enum value 28. Get Events Feed Purpose : Returns a paginated list of all published events for the public discovery feed. Results are ordered by creation date descending. This endpoint will eventually incorporate a recommendation algorithm. Endpoint : GET /api/v1/e-events/events-feed Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Events feed retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects — PUBLISHED only ]" ], "totalElements": 87, "totalPages": 9 } } Success Response Fields : data.content[] contains EventSummaryResponse objects (PUBLISHED status only). Possible Error Responses : None expected (no authentication required). 28a. Get Past Events Purpose : Returns a paginated list of PUBLISHED events whose end date/time has already passed (e.g., for an "Events you missed" / past-events archive view). Note that this is distinct from COMPLETED status — an event moves to COMPLETED via a separate scheduled job, so there can be a lag where a past-dated event still shows here as PUBLISHED . Endpoint : GET /api/v1/e-events/events-feed/past Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Past events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects — PUBLISHED, end date in the past ]" ], "totalElements": 23, "totalPages": 3 } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Possible Error Responses : None expected (no authentication required). 29. Search Events Purpose : Full-text search across published event titles. Results are ordered by start date ascending. Endpoint : GET /api/v1/e-events/search Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default query string Yes Search keyword(s) Non-empty string — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Search results retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 4, "totalPages": 1 } } Success Response Fields : data.content[] contains EventSummaryResponse objects. Possible Error Responses : None expected for normal queries (empty results return an empty content array, not a 404). 30. Filter Events by Date Range Purpose : Returns published events whose schedule overlaps with the provided date range. An event is included if it starts before endDate AND ends after startDate (overlap logic, not exact range match). Endpoint : GET /api/v1/e-events/filter/date Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default startDate ZonedDateTime Yes Range start ISO 8601 with offset (e.g., 2025-07-01T00:00:00+03:00 ) — endDate ZonedDateTime Yes Range end ISO 8601 with offset; must be after startDate — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Filtered events retrieved successfully", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ EventSummaryResponse objects ]" ], "totalElements": 7, "totalPages": 1 } } Possible Error Responses : Status Scenario 400 startDate is after endDate 31. Search and Filter Events (Combined) Purpose : Combines keyword search with optional date range filtering in a single call. All parameters are optional — calling with no parameters is equivalent to getting the full published feed. Endpoint : GET /api/v1/e-events/filter Access Level : 🌐 Public Authentication : None required Query Parameters : Parameter Type Required Description Validation Default query string No Title keyword search — — startDate ZonedDateTime No Date range start ISO 8601 with offset — endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Possible Error Responses : Status Scenario 400 startDate is after endDate (when both are provided) 32. Search My Events (Organizer) Purpose : Allows an organizer to search and filter within their own events across all statuses. Supports keyword, status filter, and date range simultaneously. Endpoint : GET /api/v1/e-events/my-events/search Access Level : 🔒 Protected (Organizer — own events only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Query Parameters : Parameter Type Required Description Validation Default query string No Keyword search on title — — status string No Filter by status Enum: DRAFT , PUBLISHED , HAPPENING , CANCELLED , COMPLETED — startDate ZonedDateTime No Date range start ISO 8601 with offset — endDate ZonedDateTime No Date range end ISO 8601 with offset; must be after startDate if both provided — page integer No Page number (1-based) Min: 1 1 size integer No Items per page Min: 1 10 Possible Error Responses : Status Scenario 401 No or expired token 400 startDate is after endDate 33. Get Event OG Meta by Slug Purpose : Returns lightweight Open Graph metadata for a published event, for use by link-preview crawlers (social media unfurling, messaging apps) when someone shares an event URL. Endpoint : GET /api/v1/e-events/slug/{slug}/og-meta Access Level : 🌐 Public Authentication : None required Path Parameters : Parameter Type Required Description Validation slug string Yes The event's URL slug Must match an existing event ⚠️ Non-standard response shape : Unlike every other endpoint in this API, this one returns the EventOGMetaResponse object directly as the HTTP body — it is not wrapped in the standard { success, httpStatus, message, action_time, data } envelope described at the top of this document. Build OG-meta consumers accordingly. Success Response JSON Sample : { "title": "Dar es Salaam Jazz Festival 2025", "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "An annual celebration of jazz and live music at the heart of the city.", "banner": "https://cdn.example.com/ft/6b1a2c3d/medium.jpg", "startDateTime": "2025-07-18T18:00:00+03:00", "locationSummary": "Dar es Salaam, TZ" } Success Response Fields : Field Type Description title string Event title eventId UUID Event ID description string Full event description banner string | null Resolved banner image URL (single string, not the full bannerMedia object) startDateTime ZonedDateTime Event start date/time with offset locationSummary string Human-readable location summary Possible Error Responses : Status Scenario 404 No event found with the given slug Data Format Standards Concern Standard Timestamps ISO 8601 with timezone offset: 2025-07-18T18:00:00+03:00 Dates YYYY-MM-DD format: 2025-07-18 Times HH:mm:ss (24-hour): 18:00:00 IDs UUID v4: 3fa85f64-5717-4562-b3fc-2c963f66afa6 Pagination 1-based page query parameter, Spring Page wrapper in response Prices BigDecimal with 2 decimal places; 0.00 for free Enums Uppercase strings as defined (e.g., IN_PERSON , PUBLISHED ) Ticket Management API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-02-20 Version : v1.2 Base URL : https://your-api-domain.com/api/v1/e-events/tickets Short Description : The Ticket Management API allows event organizers to define and manage ticket types for their events on NextGate. Organizers can create multiple ticket tiers (e.g. VIP, Early Bird, General Admission), control pricing, capacity, sales periods, visibility, and sales channels. Tickets are scoped to a specific event and follow the event's lifecycle from DRAFT through to PUBLISHED. Hints : 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 : 2026-05-23 Version : v2.0 Base URL : https://api.nextgate.co.tz/api/v1 Short Description : The NextGate Checkout API manages the complete ticket purchasing lifecycle on the NextGate event platform. It supports two distinct checkout flows: online checkout for registered attendees and at-door ticket sales for event organizers and scanner devices. This API should be used by frontend clients, mobile applications, and authorized scanner hardware integrations. Hints : All monetary values are in TZS (Tanzanian Shilling) unless otherwise stated Checkout sessions expire after 15 minutes for online checkout and 1 hour for at-door sales — always check expiresAt before processing payment Bearer token authentication is required for all endpoints Wallet balance is validated at session creation time for PAID tickets — if insufficient, the session is never created and a structured 422 response is returned with shortfall and recommendedTopUp so the frontend can direct the user to top up immediately For FREE tickets, payment is auto-processed immediately upon session creation — no separate payment step is needed For DONATION tickets, maximum 1 ticket per order and cannot be purchased for other attendees Ticket holds are applied immediately on session creation; cancelling the session releases the hold Scanner devices must have the SELL_TICKETS permission assigned and a valid deviceFingerprint registered before calling the scanner sale endpoint Standard Response Format All API responses follow a consistent structure using our Globe Response Builder pattern: Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2025-09-23T10:30:45", "data": {} } Error Response Structure { "success": false, "httpStatus": "BAD_REQUEST", "message": "Error description", "action_time": "2025-09-23T10:30:45", "data": "Error description" } Insufficient Balance Error Structure (422) Returned when wallet balance is insufficient at session creation time for a PAID ticket. No session is created. The data field carries rich balance details so the frontend can guide the user directly to top up. { "success": false, "httpStatus": "UNPROCESSABLE_ENTITY", "message": "Insufficient wallet balance to complete checkout", "action_time": "2025-09-23T10:30:45", "data": { "walletBalance": 50000.00, "sessionTotal": 150000.00, "shortfall": 100000.00, "hasSufficientBalance": false, "recommendedTopUp": 100000.00, "pspMinimum": 500.00, "currency": "TZS" } } Field Description walletBalance Current wallet balance of the user in TZS sessionTotal Total amount required for this checkout shortfall Amount missing ( sessionTotal - walletBalance ) hasSufficientBalance Always false when this error is returned recommendedTopUp Suggested top-up amount — at minimum equals shortfall , rounded up to pspMinimum if needed pspMinimum Minimum top-up amount accepted by the payment provider currency Always TZS Standard Response Fields Field Type Description success boolean Always true for successful operations, false for errors httpStatus string HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) message string Human-readable message describing the operation result action_time string ISO 8601 timestamp of when the response was generated data object/string Response payload for success, error details for failures HTTP Method Badge Standards GET — Green: Safe, read-only operations POST — Blue: Create new resources PUT — Yellow: Update/replace entire resource PATCH — Orange: Partial updates DELETE — Red: Remove resources User Journey Flows Flow A — Online Checkout (Registered Attendee) [ Attendee browses event & selects ticket ] | v [ POST /checkout — Create checkout session ] | ............|............ . . v v [ FREE / DONATION ticket ] [ PAID ticket ] | | | v | [ Wallet balance check ] | .................... | . INSUFFICIENT . | .................... | | | [ 422 response — no session created ] | [ data: { shortfall, recommendedTopUp, ... } ] | | .................... | . SUFFICIENT . | .................... | | v v [ Auto-processed immediately ] [ Ticket hold applied ] | [ Session = PENDING_PAYMENT ] | | | v | [ POST /checkout/{sessionId}/payment ] | [ — Deduct from wallet, create escrow ] | | `----------->-----------' | v [ Booking order created asynchronously ] [ QR codes generated & sent to attendees ] | v [ Session status = COMPLETED ] At any point before payment: [ POST /checkout/{sessionId}/cancel ] [ — Releases ticket hold ] Flow B — At-Door Sale via Scanner Device [ Customer arrives at event venue ] | v [ Scanner device sends sale request ] [ POST /checkout/sell-at-door-ticket/scanner ] | ............|............ . . v v [ Validate scanner ID ] [ Validate device fingerprint ] [ & permissions ] [ & SELL_TICKETS permission ] . . `..........v............' | v [ Validate ticket type belongs to event ] [ Check sales channel = AT_DOOR or BOTH ] | v [ Cash payment processed (no wallet deduction) ] [ Booking order created immediately ] | v [ QR codes returned in response ] [ immediateCheckIn = true → ticket marked as checked-in ] Flow C — At-Door Sale via Organizer [ Organizer is authenticated & accesses event ] | v [ POST /checkout/{eventId}/organizer ] [ — Organizer sells ticket at their counter ] | v [ System verifies organizer owns the event ] | v [ Validate ticket type & attendee count ] | v [ Cash payment recorded (NEUTRAL transaction) ] [ Booking order created ] | v [ QR codes returned in response ] [ immediateCheckIn flag respected ] Endpoints 1. Create Checkout Session Purpose : Creates a new online checkout session for a registered attendee purchasing event tickets, holding the requested quantity and initializing the payment intent. Endpoint : POST /api/v1/e-events/checkout Access Level : 🔒 Protected (Requires valid Bearer token — authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Content-Type string Yes Must be application/json Request JSON Sample : { "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "ticketsForMe": 2, "donationAmount": null, "otherAttendees": [ { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+255712345678", "quantity": 1 } ], "sendTicketsToAttendees": true, "paymentMethodId": null } Request Body Parameters : Parameter Type Required Description Validation eventId UUID Yes The ID of the event being booked Must be a valid published event that has not yet started ticketTypeId UUID Yes The ID of the ticket type being purchased Must belong to the specified event and be active and on sale ticketsForMe integer Yes Number of tickets for the buyer themselves. Use 0 if the buyer is not attending Min: 0 donationAmount decimal No Donation amount in TZS. Only applicable for DONATION type tickets Only used when ticket pricing type is DONATION otherAttendees array No List of other attendees to purchase tickets for Each attendee must have valid name, email, and Tanzanian phone number otherAttendees[].name string Yes (if array provided) Full name of the attendee Min: 2, Max: 100 characters otherAttendees[].email string Yes (if array provided) Email address of the attendee Valid email format; no duplicate emails in the array otherAttendees[].phone string Yes (if array provided) Phone number of the attendee Must match Tanzania format: +255[67]XXXXXXXX otherAttendees[].quantity integer Yes (if array provided) Number of tickets for this attendee Min: 1 sendTicketsToAttendees boolean No If true , QR tickets are sent to each attendee's email. If false , all QR codes are sent to the buyer only Default: true paymentMethodId UUID No ID of a saved payment method. Defaults to wallet if not provided Optional Business Rules : Total quantity = ticketsForMe + sum of all otherAttendees[].quantity — must be at least 1 DONATION tickets: maximum 1 per order, cannot be bought for other attendees, online only AT_DOOR_ONLY tickets cannot be purchased through this endpoint Wallet balance is validated upfront for PAID tickets If the event has a required questionnaire set to BEFORE_CHECKOUT , it must be submitted before calling this endpoint FREE tickets are auto-processed immediately — the response will already show PAYMENT_COMPLETED Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Checkout session created successfully", "action_time": "2025-09-23T10:30:45", "data": { "sessionId": "a1b2c3d4-0000-0000-0000-000000000001", "status": "PENDING_PAYMENT", "customerId": "f1e2d3c4-0000-0000-0000-000000000010", "customerUserName": "john_doe", "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "eventTitle": "Kilimanjaro Jazz Night 2025", "ticketDetails": { "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "ticketTypeName": "VIP", "unitPrice": 50000.00, "ticketsForBuyer": 2, "otherAttendees": [ { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+255712345678", "quantity": 1 } ], "sendTicketsToAttendees": true, "totalQuantity": 3, "subtotal": 150000.00 }, "pricing": { "subtotal": 150000.00, "total": 150000.00 }, "paymentIntent": { "provider": "WALLET", "clientSecret": null, "paymentMethods": ["WALLET"], "status": "PENDING" }, "ticketsHeld": true, "ticketHoldExpiresAt": "2025-09-23T10:45:45", "expiresAt": "2025-09-23T10:45:45", "createdAt": "2025-09-23T10:30:45", "updatedAt": "2025-09-23T10:30:45", "completedAt": null, "createdBookingOrderId": null, "isExpired": false, "canRetryPayment": false } } Success Response Fields : Field Description sessionId Unique identifier for this checkout session. Use it for all subsequent actions status Current session status. Values: PENDING_PAYMENT , PAYMENT_COMPLETED , COMPLETED , PAYMENT_FAILED , CANCELLED , EXPIRED customerId Account ID of the buyer customerUserName Username of the buyer eventId ID of the event being booked eventTitle Human-readable event title ticketDetails.ticketTypeId ID of the chosen ticket type ticketDetails.ticketTypeName Name of the ticket type (e.g., VIP, Regular) ticketDetails.unitPrice Price per single ticket in TZS ticketDetails.ticketsForBuyer Number of tickets allocated to the buyer ticketDetails.otherAttendees List of other attendees and their ticket quantities ticketDetails.sendTicketsToAttendees Whether QR codes will be emailed to each attendee ticketDetails.totalQuantity Total tickets across buyer and all attendees ticketDetails.subtotal Total price before any discounts (TZS) pricing.subtotal Subtotal amount in TZS pricing.total Final payable amount in TZS paymentIntent.provider Payment provider (e.g., WALLET ) paymentIntent.paymentMethods Available payment methods for this session paymentIntent.status Payment intent status ( PENDING , COMPLETED ) ticketsHeld Whether the tickets are currently being held in reserve ticketHoldExpiresAt Timestamp when the ticket hold expires expiresAt Timestamp when the entire session expires createdBookingOrderId Populated after payment is completed — the resulting booking order ID isExpired Computed flag indicating whether the session has passed its expiry time canRetryPayment Whether the session allows another payment attempt (true if status is PAYMENT_FAILED and attempts < 5) Error Response JSON Sample : { "success": false, "httpStatus": "BAD_REQUEST", "message": "Please complete the event questionnaire before purchasing tickets", "action_time": "2025-09-23T10:30:45", "data": "Please complete the event questionnaire before purchasing tickets" } Possible Errors : HTTP Status Scenario 400 BAD_REQUEST Event is not published, event has already started, ticket not on sale, AT_DOOR_ONLY ticket purchased online, DONATION rules violated, questionnaire not submitted 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Event or ticket type not found 422 UNPROCESSABLE_ENTITY Insufficient wallet balance (returns rich data with shortfall and recommendedTopUp ); or validation failed on request fields 500 INTERNAL_SERVER_ERROR Unexpected server error 2. Get Checkout Session Purpose : Retrieves the current state of an existing checkout session belonging to the authenticated user. Endpoint : GET /api/v1/e-events/checkout/{sessionId} Access Level : 🔒 Protected (Authenticated attendee — only the session owner can retrieve it) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to retrieve Must be a valid UUID belonging to the authenticated user Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Checkout session retrieved successfully", "action_time": "2025-09-23T10:35:00", "data": { "sessionId": "a1b2c3d4-0000-0000-0000-000000000001", "status": "PENDING_PAYMENT", "customerId": "f1e2d3c4-0000-0000-0000-000000000010", "customerUserName": "john_doe", "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "eventTitle": "Kilimanjaro Jazz Night 2025", "ticketDetails": { "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "ticketTypeName": "VIP", "unitPrice": 50000.00, "ticketsForBuyer": 2, "otherAttendees": [ { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+255712345678", "quantity": 1 } ], "sendTicketsToAttendees": true, "totalQuantity": 3, "subtotal": 150000.00 }, "pricing": { "subtotal": 150000.00, "total": 150000.00 }, "paymentIntent": { "provider": "WALLET", "clientSecret": null, "paymentMethods": ["WALLET"], "status": "PENDING" }, "paymentAttempts": [], "ticketsHeld": true, "ticketHoldExpiresAt": "2025-09-23T10:45:45", "expiresAt": "2025-09-23T10:45:45", "createdAt": "2025-09-23T10:30:45", "updatedAt": "2025-09-23T10:30:45", "completedAt": null, "createdBookingOrderId": null, "isExpired": false, "canRetryPayment": false } } Success Response Fields : Field Description sessionId Unique session identifier status Current session status paymentAttempts List of all payment attempts made on this session, including failures paymentAttempts[].attemptNumber Sequential attempt number (1-indexed) paymentAttempts[].paymentMethod Payment method used for this attempt paymentAttempts[].status Result of the attempt: SUCCESS or FAILED paymentAttempts[].errorMessage Failure reason if the attempt failed paymentAttempts[].attemptedAt Timestamp of the attempt paymentAttempts[].transactionId External or internal transaction reference All other fields Same as Create Checkout Session response Possible Errors : HTTP Status Scenario 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Session not found or does not belong to the authenticated user 500 INTERNAL_SERVER_ERROR Unexpected server error 3. Process Payment Purpose : Initiates wallet payment for a pending checkout session, creating an escrow account and triggering asynchronous booking order creation upon success. Endpoint : POST /api/v1/e-events/checkout/{sessionId}/payment Access Level : 🔒 Protected (Session owner only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to pay for Must be a valid UUID; session must be in PENDING_PAYMENT status and not expired Business Rules : Session must be in PENDING_PAYMENT status Session must not be expired Cannot call this on FREE tickets (auto-processed on creation) Wallet must have sufficient balance at time of payment call Maximum 5 payment attempts per session; after that canRetryPayment becomes false On success, escrow is created, session moves to PAYMENT_COMPLETED , and a booking order is created asynchronously Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Payment completed successfully. Your booking is being processed.", "action_time": "2025-09-23T10:38:00", "data": { "success": true, "status": "SUCCESS", "message": "Payment completed successfully. Your booking is being processed.", "checkoutSessionId": "a1b2c3d4-0000-0000-0000-000000000001", "escrowId": "e9f8a7b6-0000-0000-0000-000000000020", "escrowNumber": "ESC-2025-000001", "orderId": null, "orderNumber": null, "paymentMethod": "WALLET", "amountPaid": 150000.00, "platformFee": 7500.00, "sellerAmount": 142500.00, "currency": "TZS" } } Success Response Fields : Field Description status Payment result status: SUCCESS , FAILED , or PENDING checkoutSessionId The checkout session this payment belongs to escrowId ID of the escrow account holding the funds escrowNumber Human-readable escrow reference number (format: ESC-YYYY-NNNNNN) orderId Booking order ID — may be null immediately after payment as booking is created asynchronously orderNumber Human-readable order reference — null until order is created paymentMethod Payment method used: WALLET amountPaid Total amount deducted from buyer's wallet in TZS platformFee Platform fee amount (5% of total) in TZS sellerAmount Amount that will be released to the event organizer in TZS currency Currency code, always TZS Possible Errors : HTTP Status Scenario 400 BAD_REQUEST Session is not in PENDING_PAYMENT status, or session is expired 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Session not found or does not belong to the authenticated user 500 INTERNAL_SERVER_ERROR Unexpected payment processing or booking creation error 4. Cancel Checkout Session Purpose : Cancels an active checkout session and releases any held tickets back to available inventory. Endpoint : POST /api/v1/e-events/checkout/{sessionId}/cancel Access Level : 🔒 Protected (Session owner only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated attendee Path Parameters : Parameter Type Required Description Validation sessionId UUID Yes The ID of the checkout session to cancel Must be a valid UUID belonging to the authenticated user Business Rules : Cannot cancel a session that is in COMPLETED status Cannot cancel a session that is in PAYMENT_COMPLETED status (payment already processed) Cancelling releases the ticket hold immediately Cancellation does not trigger a refund — refunds are handled separately through the escrow system Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Checkout session cancelled successfully", "action_time": "2025-09-23T10:40:00", "data": null } Possible Errors : HTTP Status Scenario 400 BAD_REQUEST Session is already completed or payment has been completed 401 UNAUTHORIZED Missing, expired, or invalid Bearer token 404 NOT_FOUND Session not found or does not belong to the authenticated user 500 INTERNAL_SERVER_ERROR Unexpected server error 5. Scanner — Sell Ticket at Door Purpose : Allows an authorized scanner device to sell tickets at the venue entrance, processing a cash payment and optionally checking in the attendee immediately. Endpoint : POST /api/v1/e-events/checkout/sell-at-door-ticket/scanner Access Level : 🔒 Protected (Scanner device authentication via scannerId + deviceFingerprint ) Authentication : Bearer Token (of scanner's linked account) + Scanner credentials in body Request Headers : Header Type Required Description Authorization string Yes Bearer token linked to the scanner's registered account Content-Type string Yes Must be application/json Request JSON Sample : { "scannerId": "SCN-2025-001", "deviceFingerprint": "a3f1b2c4d5e6f7890abc1234def56789", "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "quantity": 2, "attendees": [ { "fullName": "John Mbeki", "email": "john.mbeki@example.com", "phoneNumber": "+255789123456" }, { "fullName": "Amina Hassan", "email": "amina.hassan@example.com", "phoneNumber": "+255754321987" } ], "immediateCheckIn": true } Request Body Parameters : Parameter Type Required Description Validation scannerId string Yes Unique identifier of the registered scanner device Must match a registered, active scanner with SELL_TICKETS permission deviceFingerprint string Yes Hardware fingerprint of the scanner device Must match the fingerprint registered for this scanner ticketTypeId UUID Yes ID of the ticket type to sell Must belong to the event this scanner is assigned to; must not be ONLINE_ONLY ; must be on sale quantity integer Yes Total number of tickets to sell Min: 1; must equal the number of attendees in the attendees array attendees array Yes List of attendee details, one entry per ticket Min: 1 entry; count must match quantity attendees[].fullName string No Full name of the attendee Optional — if blank, a generated name like ATTENDEE-XXXX is assigned attendees[].email string No Email address of the attendee Valid email format if provided attendees[].phoneNumber string No Phone number of the attendee Optional immediateCheckIn boolean Yes If true , the ticket is marked as checked-in immediately upon sale Required Business Rules : The scanner must be active, not expired, and have the SELL_TICKETS permission The deviceFingerprint must exactly match the registered fingerprint for this scanner The number of attendees must equal quantity — a 1-to-1 mapping is enforced Payment method is always CASH — no wallet deduction occurs The ticket type must not be ONLINE_ONLY immediateCheckIn = true automatically marks each generated ticket as checked-in Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Tickets sold successfully at door", "action_time": "2025-09-23T18:00:00", "data": { "bookingId": "c9d8e7f6-0000-0000-0000-000000000030", "bookingReference": "BK-2025-000042", "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "eventName": "Kilimanjaro Jazz Night 2025", "tickets": [ { "ticketInstanceId": "aa11bb22-0000-0000-0000-000000000050", "ticketSeries": "VIP-0042-A", "ticketTypeName": "VIP", "attendeeName": "John Mbeki", "attendeeEmail": "john.mbeki@example.com", "checkedIn": true, "checkInTime": "2025-09-23T18:00:05Z", "qrCode": "eyJhbGciOiJIUzI1NiJ9..." }, { "ticketInstanceId": "cc33dd44-0000-0000-0000-000000000051", "ticketSeries": "VIP-0042-B", "ticketTypeName": "VIP", "attendeeName": "Amina Hassan", "attendeeEmail": "amina.hassan@example.com", "checkedIn": true, "checkInTime": "2025-09-23T18:00:05Z", "qrCode": "eyJhbGciOiJIUzI1NiJ9..." } ], "totalAmount": 100000.00, "currency": "TZS", "paymentMethod": "CASH", "soldBy": "Gate-A Scanner", "soldAt": "Main Entrance", "saleTime": "2025-09-23T18:00:05Z" } } Success Response Fields : Field Description bookingId UUID of the created booking order bookingReference Human-readable booking reference number eventId ID of the event eventName Name of the event tickets Array of issued ticket instances — one per attendee tickets[].ticketInstanceId Unique ID of this specific ticket instance tickets[].ticketSeries Ticket serial number (e.g., VIP-0042-A) tickets[].ticketTypeName The type of the sold ticket tickets[].attendeeName Name of the attendee this ticket is assigned to tickets[].attendeeEmail Email of the attendee tickets[].checkedIn Whether the attendee has been checked in tickets[].checkInTime Timestamp of check-in if immediateCheckIn was true tickets[].qrCode JWT-encoded QR code string for this ticket totalAmount Total cash amount collected in TZS currency Always TZS paymentMethod Always CASH for at-door sales soldBy Name of the scanner that processed the sale soldAt Location label of the scanner (e.g., "Main Entrance") saleTime ISO 8601 timestamp of when the sale occurred Possible Errors : HTTP Status Scenario 400 BAD_REQUEST Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale 401 UNAUTHORIZED Missing or invalid Bearer token 403 FORBIDDEN Scanner does not have SELL_TICKETS permission, device fingerprint mismatch, scanner is inactive or expired 404 NOT_FOUND Scanner ID not found, ticket type not found 422 UNPROCESSABLE_ENTITY Validation errors on request fields 500 INTERNAL_SERVER_ERROR Payment processing or booking creation failure 6. Organizer — Sell Ticket at Door Purpose : Allows the authenticated event organizer to sell tickets directly at their event counter, processing a cash payment and optionally checking in the attendee immediately. Endpoint : POST /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer Access Level : 🔒 Protected (Must be the organizer of the specified event) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token of the authenticated event organizer Content-Type string Yes Must be application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The ID of the event to sell tickets for Must be an existing, non-deleted event; authenticated user must be the organizer Request JSON Sample : { "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002", "quantity": 2, "attendees": [ { "fullName": "Peter Salim", "email": "peter.salim@example.com", "phoneNumber": "+255711223344" }, { "fullName": "Grace Mwangi", "email": "grace.mwangi@example.com", "phoneNumber": null } ], "immediateCheckIn": false, "location": "VIP Gate" } Request Body Parameters : Parameter Type Required Description Validation ticketTypeId UUID Yes ID of the ticket type to sell Must belong to the event in the path; must not be ONLINE_ONLY ; must be on sale quantity integer Yes Total number of tickets to sell Min: 1; must equal the number of attendees in the attendees array attendees array Yes List of attendee details — one entry per ticket Min: 1 entry; count must match quantity attendees[].fullName string No Full name of the attendee Optional — auto-generated if blank attendees[].email string No Email of the attendee Valid email format if provided attendees[].phoneNumber string No Phone number of the attendee Optional immediateCheckIn boolean Yes Whether to mark attendees as checked-in immediately Required location string No Description of the sale point, e.g., "VIP Gate", "Main Counter" Max: 200 characters; defaults to "Organizer Counter" if not provided Business Rules : Only the event organizer (the user who created the event) can call this endpoint Number of entries in attendees must equal quantity Payment is always CASH — no wallet or ledger deduction Ticket type must not be ONLINE_ONLY immediateCheckIn = true marks each ticket as checked-in at time of sale Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Tickets sold successfully at door", "action_time": "2025-09-23T17:30:00", "data": { "bookingId": "d7e6f5a4-0000-0000-0000-000000000035", "bookingReference": "BK-2025-000043", "eventId": "b3f1a2c4-1234-5678-abcd-000000000001", "eventName": "Kilimanjaro Jazz Night 2025", "tickets": [ { "ticketInstanceId": "ee55ff66-0000-0000-0000-000000000060", "ticketSeries": "VIP-0043-A", "ticketTypeName": "VIP", "attendeeName": "Peter Salim", "attendeeEmail": "peter.salim@example.com", "checkedIn": false, "checkInTime": null, "qrCode": "eyJhbGciOiJIUzI1NiJ9..." }, { "ticketInstanceId": "gg77hh88-0000-0000-0000-000000000061", "ticketSeries": "VIP-0043-B", "ticketTypeName": "VIP", "attendeeName": "Grace Mwangi", "attendeeEmail": "grace.mwangi@example.com", "checkedIn": false, "checkInTime": null, "qrCode": "eyJhbGciOiJIUzI1NiJ9..." } ], "totalAmount": 100000.00, "currency": "TZS", "paymentMethod": "CASH", "soldBy": "organizer_username", "soldAt": "VIP Gate", "saleTime": "2025-09-23T17:30:05Z" } } Success Response Fields : Field Description bookingId UUID of the created booking order bookingReference Human-readable booking reference number eventId ID of the event eventName Name of the event tickets Array of issued ticket instances — one per attendee tickets[].ticketInstanceId Unique ID of this specific ticket instance tickets[].ticketSeries Ticket serial number tickets[].ticketTypeName The type of ticket sold tickets[].attendeeName Assigned attendee name tickets[].attendeeEmail Attendee email tickets[].checkedIn Whether immediately checked in tickets[].checkInTime Check-in timestamp, null if not checked in tickets[].qrCode JWT-encoded QR code string for this ticket totalAmount Total cash amount in TZS currency Always TZS paymentMethod Always CASH soldBy Username of the organizer who made the sale soldAt Location label provided in the request saleTime ISO 8601 timestamp of the sale Possible Errors : HTTP Status Scenario 400 BAD_REQUEST Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale 401 UNAUTHORIZED Missing or invalid Bearer token 403 FORBIDDEN Authenticated user is not the organizer of the specified event 404 NOT_FOUND Event not found, ticket type not found 422 UNPROCESSABLE_ENTITY Validation errors on request fields 500 INTERNAL_SERVER_ERROR Payment processing or booking creation failure Standard Error Response Examples Bad Request — General (400): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Ticket is not currently on sale", "action_time": "2025-09-23T10:30:45", "data": "Ticket is not currently on sale" } Unauthorized — Token Issues (401): { "success": false, "httpStatus": "UNAUTHORIZED", "message": "Token has expired", "action_time": "2025-09-23T10:30:45", "data": "Token has expired" } Forbidden — Access Denied (403): { "success": false, "httpStatus": "FORBIDDEN", "message": "Only the event organizer can sell tickets at door", "action_time": "2025-09-23T10:30:45", "data": "Only the event organizer can sell tickets at door" } Not Found (404): { "success": false, "httpStatus": "NOT_FOUND", "message": "Event not found", "action_time": "2025-09-23T10:30:45", "data": "Event not found" } Validation Error (422): { "success": false, "httpStatus": "UNPROCESSABLE_ENTITY", "message": "Validation failed", "action_time": "2025-09-23T10:30:45", "data": { "ticketTypeId": "must not be null", "quantity": "must be greater than or equal to 1", "immediateCheckIn": "must not be null" } } Standard Error Types Reference Application-Level Exceptions (400–499) 400 BAD_REQUEST : General invalid request, business rule violations, or item already exists 401 UNAUTHORIZED : Authentication issues (missing, invalid, expired, or malformed token) 403 FORBIDDEN : Access denied, scanner permission issues, organizer mismatch 404 NOT_FOUND : Event, ticket type, session, or scanner not found 422 UNPROCESSABLE_ENTITY : Bean validation errors with per-field details 429 TOO_MANY_REQUESTS : Rate limit exceeded Server-Level Exceptions (500+) 500 INTERNAL_SERVER_ERROR : Unexpected server errors, payment orchestration failures Ticket Pricing Types — Detailed Behaviour This section explains exactly how the system handles each pricing type end-to-end: from checkout creation through payment, booking order creation, and ticket serial assignment. Understanding this is critical for integrating correctly with the checkout API. FREE Tickets What they are : Tickets with a price of 0 TZS . No money changes hands. Checkout flow : [ POST /checkout — session created ] | v [ System detects price = 0 TZS ] | v [ Payment auto-processed immediately ] [ No wallet deduction ] [ No escrow created ] | v [ PaymentCompletedEvent published (escrow = null) ] | v [ Booking order created asynchronously ] [ Ticket serials assigned ] | v [ Session status = COMPLETED ] [ Response returned to caller ] Key rules : The caller does not need to call POST /{sessionId}/payment — this is skipped entirely The session is returned from the create endpoint already in PAYMENT_COMPLETED status (may shift to COMPLETED once the booking is written) No escrow record exists for this transaction; escrowId will be null in all responses A NEUTRAL transaction history entry is recorded for audit purposes Ticket holds are still applied on creation and released naturally upon booking completion FREE tickets can be ONLINE_ONLY or BOTH depending on the sales channel configuration — AT_DOOR_ONLY FREE tickets go through the scanner/organizer at-door flows instead PAID Tickets What they are : Tickets with a price greater than 0 TZS . Wallet payment is required. Checkout flow : [ POST /checkout — session created ] | v [ assertSufficientBalanceForCheckout(total) ] ............................................ . INSUFFICIENT BALANCE . ............................................ | [ 422 returned immediately ] [ No session created ] [ data: { ] [ walletBalance, ] [ sessionTotal, ] [ shortfall, ] [ recommendedTopUp ] [ } ] ............................................ . SUFFICIENT BALANCE . ............................................ | v [ Ticket hold applied ] | v [ Session status = PENDING_PAYMENT ] [ paymentIntent.provider = WALLET ] | v [ POST /{sessionId}/payment called by client ] | v [ Wallet deducted via double-entry ledger ] [ Escrow account created (ESC-YYYY-NNNNNN)] [ platformFee = 5% of total ] [ sellerAmount = total - platformFee ] | v [ PaymentCompletedEvent published (escrow != null) ] | v [ Booking order created asynchronously ] [ Ticket serials assigned ] [ QR codes generated ] | v [ Session status = COMPLETED ] [ Escrow status = HELD ] [ (Released to organizer on event completion) ] Key rules : Wallet balance is checked at session creation — if insufficient, a 422 is returned with rich balance data ( shortfall , recommendedTopUp ) and no session is created. A second safety-net check runs at actual payment time in case the balance changed between the two calls Maximum 5 payment attempts per session; after that canRetryPayment = false and a new session must be created If a payment attempt fails, the session moves to PAYMENT_FAILED but the ticket hold remains active until the session expires Escrow holds funds in a separate ledger account — the organizer does not receive the money until the platform releases it after the event orderId in the payment response may be null immediately after payment since booking creation is asynchronous — poll GET /checkout/{sessionId} and check createdBookingOrderId to confirm DONATION Tickets What they are : Tickets where the attendee voluntarily chooses the amount they pay. A minimum may or may not be set by the organizer. Checkout flow : [ POST /checkout — session created ] [ donationAmount provided in body ] | v [ System validates DONATION rules ] ..................................... . totalQuantity must be exactly 1 . . otherAttendees must be empty . . salesChannel must be ONLINE_ONLY . ..................................... | v [ Treated as PAID internally ] [ donationAmount used as the ticket price ] [ Wallet deducted for the donation amount ] [ Escrow created for the donation amount ] | v [ Booking order created asynchronously ] [ Ticket serial assigned ] [ QR code generated ] | v [ Session status = COMPLETED ] Key rules : Strictly 1 ticket per order — the system rejects any request with ticketsForMe > 1 or any otherAttendees Online-only — DONATION tickets cannot be sold at the door through any channel The donationAmount in the request body is the amount that will be charged; the system uses it as the effective unit price Despite being a donation, the standard 5% platform fee still applies and an escrow account is created If donationAmount is null or 0 , the system may treat it as a FREE ticket depending on the ticket's configured minimum — confirm with the organizer's ticket setup Ticket Serials — How They Are Assigned Every ticket instance issued by the system receives a unique ticket serial (also called ticketSeries in the response). This serial is the human-readable identifier printed on physical tickets, displayed in QR codes, and used for manual verification at the door. Serial Format [TICKET_TYPE_CODE]-[BOOKING_NUMBER]-[POSITION_LETTER] Examples: VIP-0042-A ← First VIP ticket in booking #42 VIP-0042-B ← Second VIP ticket in booking #42 REG-0199-A ← First Regular ticket in booking #199 GENERAL-0001-A ← First General Admission ticket in booking #1 How Serials Are Generated [ Booking order created ] | v [ System reads totalQuantity from checkout session ] | v [ For each ticket in the order: ] ....................................... . ticketSeries = TYPE_CODE . . + "-" . . + BOOKING_NUMBER . ← zero-padded (e.g., 0042) . + "-" . . + POSITION_LETTER . ← A, B, C, D ... per ticket ....................................... | v [ Each serial stored on the TicketInstance entity ] [ JWT-encoded QR token generated per ticket ] [ QR token embeds: ticketSeries + ticketInstanceId ] | v [ Serials returned in: ] . POST /sell-at-door-ticket response (tickets[].ticketSeries) ] . Booking order details endpoint (separate booking API) ] Serial Assignment per Pricing Type Pricing Type When Serials Are Assigned Who Appears in attendeeName FREE Asynchronously after PaymentCompletedEvent Buyer for ticketsForBuyer tickets; each named attendee for their tickets PAID Asynchronously after payment escrow is created Buyer for ticketsForBuyer tickets; each named attendee for their tickets DONATION Asynchronously after payment (always 1 ticket) Always the buyer only At-Door (any type) Synchronously — returned immediately in the sale response Each attendee in the attendees array; auto-generated name if blank Attendee-to-Serial Mapping When a buyer purchases tickets for themselves and other attendees, each serial maps to exactly one person: Buyer purchases: ticketsForMe = 2 otherAttendees = [ { name: "Jane", quantity: 1 } ] totalQuantity = 3 Serials assigned: VIP-0042-A → Buyer (ticket 1 of 2 for buyer) VIP-0042-B → Buyer (ticket 2 of 2 for buyer) VIP-0042-C → Jane (her 1 ticket) QR Code & Serial Relationship Each ticket's qrCode field in the response is a JWT token that encodes the ticket serial and instance ID. Scanners decode this JWT at check-in time to verify the ticket. The serial alone is readable by humans; the JWT is what the scanner hardware validates cryptographically. QR Code (JWT) decodes to: ........................................... . ticketInstanceId (UUID) . . ticketSeries (e.g. VIP-0042-A) . . eventId (UUID) . . issuedAt (timestamp) . ........................................... | v [ Scanner validates JWT signature ] [ Marks ticket as CHECKED_IN ] [ Returns check-in confirmation ] Quick Reference Endpoint Summary # Method Path Description 1 POST /api/v1/e-events/checkout Create online checkout session 2 GET /api/v1/e-events/checkout/{sessionId} Get checkout session details 3 POST /api/v1/e-events/checkout/{sessionId}/payment Process wallet payment 4 POST /api/v1/e-events/checkout/{sessionId}/cancel Cancel checkout session 5 POST /api/v1/e-events/checkout/sell-at-door-ticket/scanner Scanner at-door sale 6 POST /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer Organizer at-door sale Session Status Reference Status Meaning PENDING_PAYMENT Session created, awaiting payment PAYMENT_PROCESSING External payment initiated, awaiting confirmation PAYMENT_COMPLETED Payment succeeded, booking being created PAYMENT_FAILED Payment attempt failed (retry may be possible) COMPLETED Booking fully created and confirmed CANCELLED Session cancelled by user EXPIRED Session timed out before payment Ticket Pricing Type Behaviour Pricing Type Payment Required At-Door Allowed Notes FREE No Depends on sales channel Auto-processed on session creation PAID Yes (Wallet) Yes Escrow created on payment DONATION Optional amount No (Online only) Max 1 ticket per order; no other attendees Sales Channel Rules Sales Channel Online Checkout At-Door (Scanner) At-Door (Organizer) ONLINE_ONLY ✅ Allowed ❌ Blocked ❌ Blocked AT_DOOR_ONLY ❌ Blocked ✅ Allowed ✅ Allowed BOTH ✅ Allowed ✅ Allowed ✅ Allowed Authentication Reference Bearer Token : Include Authorization: Bearer in all request headers All endpoints require authentication For scanner endpoints, the Bearer token must belong to the account linked to the scanner device Data Format Standards Dates : ISO 8601 format ( 2025-09-23T10:30:45 ) Currency : TZS (Tanzanian Shilling) — decimal values with 2 decimal places UUIDs : Standard UUID v4 format ( xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ) Phone Numbers : Tanzania format only — +255[67]XXXXXXXX Event Booking Orders API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-05-23 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Booking Orders API manages confirmed ticket purchases for events on the Nexgate platform. It allows customers to view their bookings with JWT-signed QR codes, track multi-day check-in history, download ticket PDFs, and enables event organizers to monitor all orders for their events with filtering and revenue stats. Hints : Bookings are created automatically after successful checkout payment — never created directly via this API QR codes are RSA-signed JWTs containing full ticket and event data; they cannot be forged Event details (title, venue, organizer) are snapshotted at booking time and never change on a booking record Ticket series are auto-generated (e.g., VIP-0001 , GENER-0042 ) using a per-ticket-type counter Multi-day events store a full checkIns array per ticket — one record per day attended Customers see their own bookings only; organizers see bookings for their events; admins see all The PDF endpoint returns a binary file, not a JSON response Standard Response Format All JSON endpoints follow a consistent structure using the Globe Response Builder pattern: Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2026-05-23T10:30:45", "data": {} } Error Response Structure { "success": false, "httpStatus": "NOT_FOUND", "message": "Booking not found", "action_time": "2026-05-23T10:30:45", "data": "Booking not found" } Standard Response Fields Field Type Description success boolean true for successful operations, false for errors httpStatus string HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.) message string Human-readable description of the result action_time string ISO 8601 timestamp of when the response was generated data object/string Response payload on success; error message string on failure HTTP Method Badge Standards GET - GET - Green (Safe, read-only operations) POST - POST - Blue (Create new resources) PUT - PUT - Yellow (Update/replace entire resource) PATCH - PATCH - Orange (Partial updates) DELETE - DELETE - Red (Remove resources) Endpoints 1. Get Booking by ID Purpose : Retrieve complete booking details including all tickets, check-in history, and event snapshot for a specific booking. Endpoint : GET {base_url}/e-events/booking-orders/{bookingId} Access Level : 🔒 Protected (Booking Owner, Event Organizer, or Admin) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Path Parameters : Parameter Type Required Description Validation bookingId string (UUID) Yes Unique booking order ID Must be valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Booking retrieved successfully", "action_time": "2026-05-23T10:30:45", "data": { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "event": { "eventId": "770e8400-e29b-41d4-a716-446655440002", "title": "East African Tech Summit 2025", "startDateTime": "2025-12-15T09:00:00", "endDateTime": "2025-12-17T18:00:00", "timezone": "Africa/Nairobi", "location": "KICC Nairobi, Harambee Avenue, Nairobi", "format": "HYBRID", "hasApplicantForm": true, "virtualDetails": { "meetingLink": "https://zoom.us/j/123456789", "meetingId": "123 456 789", "passcode": "summit2025" } }, "organizer": { "name": "TechEvents Kenya", "email": "organizer@techevents.ke", "phone": "+254712345678" }, "customer": { "customerId": "660e8400-e29b-41d4-a716-446655440001", "name": "johndoe", "email": "john@example.com" }, "tickets": [ { "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440010", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "ticketTypeName": "VIP Pass", "ticketSeries": "VIP-0001", "ticketNumber": "VIP-0001", "price": 150.00, "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "attendanceMode": "IN_PERSON", "attendee": { "name": "John Doe", "email": "john@example.com", "phone": "+255712345678" }, "buyer": { "name": "John Doe", "email": "john@example.com", "buyerType": "SYSTEM_USER" }, "checkIns": [ { "checkInTime": "2025-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "dayName": "Day 1 - Opening Day", "scannerId": "SCANNER-001", "checkInMethod": "QR_SCAN" }, { "checkInTime": "2025-12-16T08:45:00+03:00", "checkInLocation": "VIP Entrance", "checkedInBy": "Scanner Operator 2", "dayName": "Day 2 - Conference Day", "scannerId": "SCANNER-003", "checkInMethod": "QR_SCAN" } ], "hasBeenCheckedIn": true, "lastCheckedInAt": "2025-12-16T08:45:00+03:00", "lastCheckedInBy": "Scanner Operator 2", "lastCheckInLocation": "VIP Entrance", "lastCheckInDayName": "Day 2 - Conference Day", "status": "USED", "validFrom": "2025-12-15T09:00:00+03:00", "validUntil": "2025-12-17T18:00:00+03:00" }, { "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440011", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "ticketTypeName": "General Admission", "ticketSeries": "GENER-0042", "ticketNumber": "GENER-0042", "price": 50.00, "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "attendanceMode": "IN_PERSON", "attendee": { "name": "Jane Smith", "email": "jane@example.com", "phone": "+255723456789" }, "buyer": { "name": "John Doe", "email": "john@example.com", "buyerType": "SYSTEM_USER" }, "checkIns": [], "hasBeenCheckedIn": false, "status": "ACTIVE", "validFrom": "2025-12-15T09:00:00+03:00", "validUntil": "2025-12-17T18:00:00+03:00" } ], "totalTickets": 2, "checkedInTicketsCount": 1, "subtotal": 200.00, "total": 200.00, "bookedAt": "2025-12-11T10:30:45", "cancelledAt": null } } Success Response Fields : Field Description bookingId Unique booking UUID bookingReference Short readable code (e.g., EVT-A3F4B21C ) status Booking status: CONFIRMED , CANCELLED formResponseId UUID of the buyer's applicant form response — null if the event had no form event.eventId Event UUID event.title Event title at time of booking (immutable snapshot) event.startDateTime Event start — LocalDateTime (no timezone) event.endDateTime Event end — LocalDateTime (no timezone) event.timezone IANA timezone string (e.g., Africa/Nairobi ) event.location Full venue address snapshotted at booking event.format IN_PERSON , ONLINE , or HYBRID event.hasApplicantForm true if the event had an applicant form at time of booking event.virtualDetails Virtual meeting info — only present for ONLINE / HYBRID events event.virtualDetails.meetingLink Full meeting URL event.virtualDetails.meetingId Meeting ID (optional) event.virtualDetails.passcode Meeting passcode (optional) organizer.name Organizer name snapshotted at booking organizer.email Organizer email organizer.phone Organizer phone customer.customerId Customer account UUID customer.name Customer username customer.email Customer email tickets Array of all booked ticket instances tickets[].ticketInstanceId Unique ticket instance UUID tickets[].formResponseId UUID of the applicant form response linked to this ticket — same as order-level formResponseId tickets[].ticketTypeName Ticket type name (e.g., VIP Pass ) tickets[].ticketSeries Auto-generated series (e.g., VIP-0001 ) tickets[].ticketNumber Same as ticketSeries — used for PDF labeling tickets[].price Price paid for this ticket tickets[].qrCode RSA-signed JWT — used for gate check-in scanning tickets[].attendanceMode IN_PERSON or ONLINE (relevant for hybrid events) tickets[].attendee Person this ticket is for tickets[].attendee.name Attendee full name tickets[].attendee.email Attendee email tickets[].attendee.phone Attendee phone tickets[].buyer Person who purchased this ticket tickets[].buyer.name Buyer name tickets[].buyer.email Buyer email (null for AT_DOOR purchases) tickets[].buyer.buyerType SYSTEM_USER (online) or AT_DOOR (sold at gate) tickets[].checkIns Full check-in history — one record per day attended tickets[].checkIns[].checkInTime ZonedDateTime of check-in tickets[].checkIns[].checkInLocation Where checked in (e.g., Main Gate , VIP Entrance ) tickets[].checkIns[].checkedInBy Staff/scanner operator name tickets[].checkIns[].dayName Event day label (e.g., Day 1 - Opening Day ) tickets[].checkIns[].scannerId Scanner device ID tickets[].checkIns[].checkInMethod QR_SCAN (default), MANUAL , or NFC tickets[].hasBeenCheckedIn true if ticket has at least one check-in tickets[].lastCheckedInAt Most recent check-in time (ZonedDateTime, null if none) tickets[].lastCheckedInBy Who performed the most recent check-in (null if none) tickets[].lastCheckInLocation Location of most recent check-in (null if none) tickets[].lastCheckInDayName Day label of most recent check-in (null if none) tickets[].status ACTIVE , USED , or CANCELLED tickets[].validFrom Ticket validity start — ZonedDateTime tickets[].validUntil Ticket validity end — ZonedDateTime totalTickets Total ticket count in this booking checkedInTicketsCount Count of tickets with at least one check-in subtotal Subtotal before any deductions total Total amount paid bookedAt When the booking was created — LocalDateTime cancelledAt When cancelled — LocalDateTime (null if still active) Error Response JSON Sample : { "success": false, "httpStatus": "NOT_FOUND", "message": "Booking not found: 550e8400-e29b-41d4-a716-446655440000", "action_time": "2026-05-23T10:30:45", "data": "Booking not found: 550e8400-e29b-41d4-a716-446655440000" } Standard Error Types : 401 UNAUTHORIZED : Missing or expired Bearer token 403 FORBIDDEN : Authenticated user is not the booking owner, event organizer, or admin 404 NOT_FOUND : Booking with given ID does not exist 2. Get My Bookings Purpose : Retrieve a summary list of all bookings for the authenticated user, sorted newest first. Endpoint : GET {base_url}/e-events/booking-orders/my-bookings Access Level : 🔒 Protected (Authenticated Users) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Bookings retrieved successfully", "action_time": "2026-05-23T10:30:45", "data": [ { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "eventTitle": "East African Tech Summit 2025", "eventStartDateTime": "2025-12-15T09:00:00", "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi", "totalTickets": 2, "checkedInTickets": 1, "total": 200.00, "bookedAt": "2025-12-11T10:30:45", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, { "bookingId": "550e8400-e29b-41d4-a716-446655440001", "bookingReference": "EVT-B5D2E12F", "status": "CONFIRMED", "eventTitle": "Dar es Salaam Food Festival", "eventStartDateTime": "2025-12-20T11:00:00", "eventLocation": "Mlimani City, Sam Nujoma Road, Dar es Salaam", "totalTickets": 4, "checkedInTickets": 0, "total": 150.00, "bookedAt": "2025-12-10T14:20:30", "formResponseId": null } ] } Success Response Fields : Field Description bookingId Unique booking UUID bookingReference Short readable code (e.g., EVT-A3F4B21C ) status CONFIRMED or CANCELLED eventTitle Event title snapshotted at booking time eventStartDateTime Event start date/time — LocalDateTime (no timezone) eventLocation Venue address snapshotted at booking time totalTickets Total number of tickets in this booking checkedInTickets Number of tickets with at least one check-in total Total amount paid bookedAt When booking was created — LocalDateTime formResponseId UUID of the applicant form response — null if the event had no form Error Response JSON Sample : { "success": false, "httpStatus": "NOT_FOUND", "message": "User not found", "action_time": "2026-05-23T10:30:45", "data": "User not found" } Standard Error Types : 401 UNAUTHORIZED : Missing or expired Bearer token 404 NOT_FOUND : Authenticated user account not found 3. Download Ticket PDF Purpose : Download or preview the PDF ticket for a specific ticket instance belonging to the authenticated user. Endpoint : GET {base_url}/e-events/booking-orders/tickets/{ticketInstanceId}/pdf Access Level : 🔒 Protected (Ticket Owner) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Path Parameters : Parameter Type Required Description Validation ticketInstanceId string (UUID) Yes Unique ID of the ticket to download Must be valid UUID Query Parameters : Parameter Type Required Description Default mode string No download prompts a save dialog; inline opens the PDF in the browser tab download Success Response : Binary PDF file (not JSON) Success Response Headers : Header Value Content-Type application/pdf Content-Disposition attachment; filename="ticket-{series}.pdf" or inline; ... by mode PDF Content Includes : Event name, date, and venue Attendee name Ticket series and type QR code (RSA-signed JWT) for gate scanning Ticket validity period Error Response JSON Sample : { "success": false, "httpStatus": "FORBIDDEN", "message": "You don't have permission to access this ticket", "action_time": "2026-05-23T10:30:45", "data": "You don't have permission to access this ticket" } Standard Error Types : 401 UNAUTHORIZED : Missing or expired Bearer token 403 FORBIDDEN : Ticket does not belong to the authenticated user 404 NOT_FOUND : Ticket with given ID does not exist 500 INTERNAL_SERVER_ERROR : PDF generation failed 4. Get Event Orders (Organizer) Purpose : Retrieve a paginated list of all booking orders for a specific event with filtering by status, buyer type, and ticket type, plus aggregated revenue and order stats. Endpoint : GET {base_url}/e-events/booking-orders/event/{eventId} Access Level : 🔒 Protected (Event Organizer or Admin) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer token (format: Bearer ) Path Parameters : Parameter Type Required Description Validation eventId string (UUID) Yes Target event UUID Must be valid UUID Query Parameters : Parameter Type Required Description Validation Default status string (enum) No Filter by booking status CONFIRMED , CANCELLED — buyerType string (enum) No Filter by how the ticket was purchased SYSTEM_USER , AT_DOOR — ticketTypeId string (UUID) No Filter orders that include a specific ticket type Must be valid UUID — search string No Search by buyer name, email, or booking reference — — page integer No Zero-based page index Min: 0 0 size integer No Number of orders per page Min: 1 20 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Event orders retrieved", "action_time": "2026-05-23T10:30:45", "data": { "eventId": "770e8400-e29b-41d4-a716-446655440002", "eventTitle": "East African Tech Summit 2025", "appliedFilters": { "status": "CONFIRMED", "buyerType": null, "ticketTypeId": null, "ticketTypeName": null, "search": null }, "stats": { "totalOrders": 142, "totalTicketsSold": 318, "totalRevenue": 47500.00, "confirmedOrders": 139, "cancelledOrders": 3, "refundedOrders": 0, "onlineOrders": 128, "atDoorOrders": 14 }, "orders": [ { "bookingId": "550e8400-e29b-41d4-a716-446655440000", "bookingReference": "EVT-A3F4B21C", "status": "CONFIRMED", "bookedAt": "2025-12-11T10:30:45", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "buyer": { "buyerName": "John Doe", "buyerEmail": "john@example.com", "buyerType": "SYSTEM_USER" }, "totalTickets": 2, "checkedInCount": 1, "total": 200.00, "ticketTypes": [ { "ticketTypeName": "VIP Pass", "quantity": 1 }, { "ticketTypeName": "General Admission", "quantity": 1 } ] }, { "bookingId": "550e8400-e29b-41d4-a716-446655440099", "bookingReference": "EVT-C7E1F34A", "status": "CONFIRMED", "bookedAt": "2025-12-10T18:15:00", "formResponseId": null, "buyer": { "buyerName": "Grace Njeri", "buyerEmail": null, "buyerType": "AT_DOOR" }, "totalTickets": 1, "checkedInCount": 1, "total": 50.00, "ticketTypes": [ { "ticketTypeName": "General Admission", "quantity": 1 } ] } ], "pagination": { "currentPage": 0, "pageSize": 20, "totalPages": 8, "totalElements": 142, "hasNext": true, "hasPrevious": false } } } Success Response Fields : Field Description eventId UUID of the queried event eventTitle Event title appliedFilters Echo of the active filter values (null = not filtered) appliedFilters.status Active booking status filter appliedFilters.buyerType Active buyer type filter appliedFilters.ticketTypeId Active ticket type UUID filter appliedFilters.ticketTypeName Resolved name of the filtered ticket type appliedFilters.search Active search string stats Aggregated stats across ALL orders for the event (not just current page) stats.totalOrders Total booking count stats.totalTicketsSold Total individual tickets across all bookings stats.totalRevenue Sum of all order totals stats.confirmedOrders Orders with status CONFIRMED stats.cancelledOrders Orders with status CANCELLED stats.refundedOrders Orders with status REFUNDED stats.onlineOrders Orders placed online ( SYSTEM_USER buyer type) stats.atDoorOrders Orders sold at the gate ( AT_DOOR buyer type) orders Paginated list of order rows orders[].bookingId Booking UUID orders[].bookingReference Short readable code (e.g., EVT-A3F4B21C ) orders[].status Booking status string orders[].bookedAt When booking was created — LocalDateTime orders[].formResponseId UUID of the buyer's applicant form response — null if no form or not yet submitted orders[].buyer.buyerName Buyer's name orders[].buyer.buyerEmail Buyer's email (null for AT_DOOR) orders[].buyer.buyerType SYSTEM_USER or AT_DOOR orders[].totalTickets Number of tickets in this order orders[].checkedInCount Number of tickets checked in at least once orders[].total Order total amount orders[].ticketTypes Breakdown of ticket types in this order orders[].ticketTypes[].ticketTypeName Name of the ticket type orders[].ticketTypes[].quantity How many of this type are in the order pagination.currentPage Zero-based current page index pagination.pageSize Number of items per page pagination.totalPages Total number of pages pagination.totalElements Total matching orders pagination.hasNext true if a next page exists pagination.hasPrevious true if a previous page exists Error Response JSON Sample : { "success": false, "httpStatus": "FORBIDDEN", "message": "Access denied: you are not the organizer of this event", "action_time": "2026-05-23T10:30:45", "data": "Access denied: you are not the organizer of this event" } Standard Error Types : 401 UNAUTHORIZED : Missing or expired Bearer token 403 FORBIDDEN : Authenticated user is not the event organizer or an admin 404 NOT_FOUND : Event with given ID does not exist Ticket Series Generation Format : {TICKET_CODE}-{COUNTER} The ticket code is the first 5 characters of the ticket type name (uppercased, spaces removed). The counter is a zero-padded incrementing integer per ticket type, managed with pessimistic locking to prevent duplicates. Examples : VIP Pass → VIP-0001 , VIP-0002 , VIP-0003 General Admission → GENER-0001 , GENER-0042 Early Bird → EARLY-0001 The counter never resets — ticket series are globally unique per type forever. JWT-Signed QR Codes Each ticket's qrCode field is a full RSA-signed JWT (2048-bit key pair generated when the event is published). The JWT payload contains: ticketInstanceId , ticketTypeId , ticketTypeName , ticketSeries eventId , eventName , eventStartDateTime attendeeName , attendeeEmail , attendeePhone , attendanceMode bookingReference eventSchedules — array of day objects for multi-day events: [ { "dayName": "Day 1 - Opening Day", "startDateTime": "2025-12-15T09:00:00+03:00", "endDateTime": "2025-12-15T18:00:00+03:00", "description": "Conference Opening & Keynotes" } ] validFrom , validUntil Why JWT? Scanners can verify the ticket offline using the public key without a database lookup. Any tampering breaks the signature. The embedded eventSchedules lets scanners enforce per-day check-in rules without an internet connection. Multi-Day Check-Ins For multi-day events, each ticket can be checked in once per day. The checkIns array stores the full history. The hasBeenCheckedIn , lastCheckedInAt , lastCheckedInBy , lastCheckInLocation , and lastCheckInDayName convenience fields reflect the most recent entry. dayName in a check-in record must match one of the eventSchedules day names embedded in the JWT A ticket cannot be checked in twice on the same day Ticket status stays ACTIVE across days and transitions to USED after all expected check-ins Event Snapshots Event details (title, venue, times, organizer contacts) are captured into the booking record at the moment of purchase and never change. This means: PDF tickets always show what the customer actually bought Bookings remain accurate even if the organizer later renames or reschedules the event The event and organizer blocks in BookingOrderResponse always reflect booking-time state Booking Creation Flow Bookings are created automatically by EventPaymentCompletedListener after a successful payment — never through a direct API call. The flow: Fetch checkout session, event, and ticket type from the database Generate a unique booking reference ( EVT-{8-char-UUID} ) Create ticket instances (one per quantity unit, tagged with attendee and buyer info) Sign a JWT QR code for each ticket using the event's RSA private key Snapshot current event and organizer details into the booking record Save the booking and mark the checkout session as COMPLETED Publish a BookingCreatedEvent to trigger email notifications to buyer, attendees, and organizer The entire flow runs in a single transaction — no partial bookings are created. Access Control Rules Actor Endpoint Allowed? Booking owner (customer) GET /booking-orders/{id} Yes Event organizer GET /booking-orders/{id} Yes Admin (SUPER_ADMIN/STAFF_ADMIN) GET /booking-orders/{id} Yes Other authenticated user GET /booking-orders/{id} No — 403 Any authenticated user GET /my-bookings Yes (own bookings only) Ticket owner GET /tickets/{id}/pdf Yes Other authenticated user GET /tickets/{id}/pdf No — 403 Event organizer GET /booking-orders/event/{id} Yes Admin GET /booking-orders/event/{id} Yes Non-organizer user GET /booking-orders/event/{id} No — 403 Booking and Ticket Status Booking Status : Status Description CONFIRMED Active booking, tickets are valid CANCELLED Booking cancelled (placeholder — cancellation logic not yet implemented) Ticket Instance Status : Status Description ACTIVE Ticket ready for use; can be scanned USED Ticket has been checked in; still scannable for additional days CANCELLED Ticket cancelled; cannot be used for entry Date/Time Formats Type Format Example Used For LocalDateTime YYYY-MM-DDTHH:mm:ss 2025-12-15T09:00:00 bookedAt , cancelledAt , event snapshots ZonedDateTime YYYY-MM-DDTHH:mm:ss±HH:mm 2025-12-15T09:00:00+03:00 validFrom , validUntil , checkInTime ZonedDateTime fields carry the event's local timezone offset and display correctly regardless of the viewer's locale. Endpoint Summary # Endpoint Access 1 GET /e-events/booking-orders/{bookingId} Owner / Organizer / Admin 2 GET /e-events/booking-orders/my-bookings Any authenticated user 3 GET /e-events/booking-orders/tickets/{ticketInstanceId}/pdf Ticket owner 4 GET /e-events/booking-orders/event/{eventId} Event organizer / Admin Event Check-In System API Author : Josh, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Check-In System API provides secure ticket validation and scanner management for event entry. This API enables organizers to generate registration tokens (like WhatsApp's "Link Device"), scanners to register for events, and perform real-time ticket validation with JWT signature verification. The system supports multi-day events with per-day check-in tracking, device fingerprinting for security, automatic scanner revocation, and comprehensive duplicate detection. Hints : Registration Flow : Organizer generates token → Scanner scans QR → Scanner registers → Automatic revocation of old scanners Device Security : Fingerprint validation prevents credential theft JWT Validation : RSA-signed tickets verified offline-capable Multi-Day Events : Separate check-in per event day One Device Rule : One ACTIVE scanner per device across all events Check-In Strategies : 5 strategies (HOURS_BEFORE, SPECIFIC_TIME, ALL_DAY, EXACT_TIME, AS_DAY_START) Duplicate Detection : Prevents same-day re-entry per event day Scanner Stats : Tracks successful/failed scans automatically Auto-Revocation : Old scanners revoked when device registers for new event Organizer Only : Only event organizers can generate tokens and manage scanners API Overview Registration Token Endpoints POST /check-in/tokens/generate - Generate registration token (organizer) GET /check-in/tokens/validate/{token} - Validate registration token Scanner Management Endpoints POST /check-in/scanners/register - Register scanner device GET /check-in/scanners/event/{eventId} - Get all scanners for event GET /check-in/scanners/event/{eventId}/active - Get active scanners POST /check-in/scanners/{scannerId}/revoke - Revoke scanner Ticket Validation Endpoint POST /check-in/validate - Validate ticket and check-in Response Structures RegistrationTokenResponse { "tokenId": "uuid", "token": "REG-ABC12345-XYZ67890", "eventId": "uuid", "eventName": "East African Tech Summit 2025", "scannerName": "Gate A - Main Entrance", "expiresAt": "2025-12-11T10:35:00Z", "validityMinutes": 5, "remainingSeconds": 240, "qrCodeData": "scannerapp://register?token=REG-ABC12345-XYZ67890", "isValid": true, "used": false } ScannerResponse { "scannerId": "uuid-string", "name": "Gate A - Main Entrance", "eventId": "uuid", "eventName": "East African Tech Summit 2025", "status": "ACTIVE", "deviceFingerprint": "abc123def456...", "createdAt": "2025-12-11T10:30:00Z", "credentials": "eyJhbGc...", "publicKey": "MIIBIjANBgkqhkiG9w0...", "revocationReason": null } ValidateTicketResponse { "valid": true, "status": "VALID", "message": "✅ Entry granted for Day 1 - Opening Day. Welcome!", "ticketInstanceId": "uuid", "ticketTypeName": "VIP Pass", "ticketSeries": "VIP-0001", "attendeeName": "John Doe", "attendeeEmail": "john@example.com", "eventName": "East African Tech Summit 2025", "bookingReference": "EVT-A3F4B21C", "alreadyCheckedIn": false, "previousCheckInTime": null, "previousCheckInLocation": null, "currentCheckInTime": "2025-12-15T09:15:00+03:00", "validationMode": "ONLINE", "scannerName": "Gate A - Main Entrance", "dayName": "Day 1 - Opening Day" } Endpoints 1. Generate Registration Token Endpoint : POST /check-in/tokens/generate Access : 🔒 Event Organizer Only Request : { "eventId": "uuid", "scannerName": "Gate A - Main Entrance" } Success Response : Returns RegistrationTokenResponse Behavior : Validates user is event organizer Validates event has RSA keys (must be published) Generates token: REG-{8-UUID}-{8-UUID} Sets expiry: 5 minutes (configurable) Returns QR code data for scanner app Errors : 403 FORBIDDEN : Not event organizer 404 NOT_FOUND : Event not found 422 : Event not published or no RSA keys 2. Validate Registration Token Endpoint : GET /check-in/tokens/validate/{token} Access : 🔓 Public (for scanner apps) Success Response : Returns RegistrationTokenResponse with validity status Use Case : Scanner app validates token before registration 3. Register Scanner Endpoint : POST /check-in/scanners/register Access : 🔓 Public (uses registration token) Request : { "registrationToken": "REG-ABC12345-XYZ67890", "deviceFingerprint": "abc123def456hash", "scannerName": "Gate A - Main Entrance", "deviceInfo": "{\"model\":\"iPhone 13\",\"os\":\"iOS 15\"}" } Success Response : Returns ScannerResponse with credentials Behavior : Validates device fingerprint (10-255 chars) Validates token (not used, not expired) Auto-revokes any ACTIVE scanner with same device fingerprint Generates scanner ID (UUID) Generates JWT credentials (1 year validity) Marks token as used (one-time use) Key Rule : One device → One ACTIVE scanner (across all events) Errors : 400 BAD_REQUEST : Invalid fingerprint, token used/expired 404 NOT_FOUND : Token not found 4. Get Scanners for Event Endpoint : GET /check-in/scanners/event/{eventId} Access : 🔒 Event Organizer Only Success Response : Returns array of ScannerResponse Includes : All scanners (ACTIVE + REVOKED) 5. Get Active Scanners Endpoint : GET /check-in/scanners/event/{eventId}/active Access : 🔒 Event Organizer Only Success Response : Returns array of ScannerResponse (ACTIVE only) 6. Revoke Scanner Endpoint : POST /check-in/scanners/{scannerId}/revoke?reason=Suspicious activity Access : 🔒 Event Organizer Only Success Response : 200 OK with confirmation message Behavior : Changes status to REVOKED (permanent) Records revocation reason and timestamp Scanner can no longer validate tickets 7. Validate Ticket and Check-In Endpoint : POST /check-in/validate Access : 🔒 Scanner credentials required Request : { "jwtToken": "eyJhbGc...", "scannerId": "uuid-string", "deviceFingerprint": "abc123def456hash", "checkInLocation": "Gate A" } Success Response : Returns ValidateTicketResponse Validation Flow : Validate Scanner : Active status, fingerprint match Verify JWT : RSA signature with event's public key Find Current Day : Match current time to event schedules Find Booking : Locate ticket in database Check Duplicate : Already checked in for this day? Create Check-In : Add check-in record for current day Update Stats : Increment scanner counters Check-In Strategies : HOURS_BEFORE : X hours before event + late grace period SPECIFIC_TIME : Daily window (e.g., 08:00-23:00) ALL_DAY : Anytime on event date (00:00-23:59) EXACT_TIME : Only during event start-end AS_DAY_START : From day start (00:00) until event end + grace Validation Statuses : VALID : ✅ Entry granted DUPLICATE : ❌ Already checked in for this day INVALID_SIGNATURE : ❌ JWT signature failed EXPIRED : ❌ Ticket validity expired NOT_FOUND : ❌ Ticket not in database REVOKED : ❌ Scanner revoked Response Examples : Success (VALID) : { "success": true, "message": "✅ Entry granted for Day 1 - Opening Day. Welcome!", "data": { "valid": true, "status": "VALID", "attendeeName": "John Doe", "dayName": "Day 1 - Opening Day", "currentCheckInTime": "2025-12-15T09:15:00+03:00" } } Duplicate Check-In : { "success": false, "message": "❌ Ticket already used for Day 1 - Opening Day. Entry denied.", "data": { "valid": false, "status": "DUPLICATE", "alreadyCheckedIn": true, "previousCheckInTime": "2025-12-15T09:00:00+03:00", "previousCheckInLocation": "Gate A" } } System Flows Registration Flow Step 1: Organizer Generates Token Organizer → POST /tokens/generate ← Returns: Token + QR code data Step 2: Scanner Scans QR Code Scanner App → Scans QR code Extracts: "scannerapp://register?token=REG-..." Step 3: Scanner Validates Token (Optional) Scanner App → GET /tokens/validate/{token} ← Confirms: Token valid, event details Step 4: Scanner Registers Scanner App → POST /scanners/register Sends: Token, device fingerprint, name ← Receives: Scanner credentials (JWT) Step 5: Scanner Stores Credentials Scanner App → Saves: credentials, publicKey Ready to validate tickets offline Ticket Validation Flow Step 1: Scanner Scans Ticket QR Scanner → Reads JWT from QR code Step 2: Offline Validation (Optional) Scanner → Verifies JWT signature with public key Checks: Expiry, event match Step 3: Online Check-In Scanner → POST /validate Sends: JWT, scannerId, fingerprint Step 4: System Validates System → Validates scanner status System → Verifies JWT signature System → Finds current event day System → Checks duplicate System → Records check-in Step 5: Response to Scanner System → Returns validation result Scanner → Shows success/error to staff Multi-Day Event Support How It Works Single-Day Event : One check-in expected Status: ACTIVE → USED after check-in Multi-Day Event (e.g., 3-day festival): Multiple check-ins allowed (one per day) Each day tracked separately Ticket stays ACTIVE throughout Example: 3-Day Festival JWT contains schedules: { "eventSchedules": [ { "dayName": "Day 1 - Friday Night", "startDateTime": "2025-12-15T18:00:00+03:00", "endDateTime": "2025-12-15T23:59:00+03:00" }, { "dayName": "Day 2 - Saturday", "startDateTime": "2025-12-16T10:00:00+03:00", "endDateTime": "2025-12-16T23:59:00+03:00" }, { "dayName": "Day 3 - Sunday", "startDateTime": "2025-12-17T10:00:00+03:00", "endDateTime": "2025-12-17T20:00:00+03:00" } ] } Check-In Timeline : Friday 18:30 → Check-in for "Day 1 - Friday Night" ✅ Friday 19:00 → Duplicate for "Day 1" ❌ Saturday 11:00 → Check-in for "Day 2 - Saturday" ✅ Saturday 15:00 → Duplicate for "Day 2" ❌ Sunday 12:00 → Check-in for "Day 3 - Sunday" ✅ Validation Logic : System finds current time: Saturday 11:00 Matches to event schedule: "Day 2 - Saturday" Checks if ticket already checked in for "Day 2" If no → Allow check-in If yes → Reject as duplicate Check-In Window Strategies 1. HOURS_BEFORE (Default) Configuration : earlyCheckInHours: 2 (default) lateCheckInMinutes: 30 (default) Window : 2 hours before event until 30 minutes after event ends Example : Event: 09:00 - 18:00 Check-in allowed: 07:00 - 18:30 Use Case : Conferences, concerts 2. SPECIFIC_TIME Configuration : checkInOpensAt: "08:00" checkInClosesAt: "23:00" Window : Same time window each event day Example : 3-day event (Dec 15-17) Check-in allowed: 08:00-23:00 each day Use Case : Multi-day festivals with consistent entry hours 3. ALL_DAY Configuration : None needed Window : Entire event date (00:00 - 23:59) Example : Event date: Dec 15 Check-in allowed: Dec 15 00:00 - Dec 15 23:59 Use Case : All-day events, exhibitions 4. EXACT_TIME Configuration : None Window : Only during event start-end times Example : Event: 14:00 - 17:00 Check-in allowed: 14:00 - 17:00 only Use Case : Strict timing events 5. AS_DAY_START Configuration : lateCheckInMinutes: 30 (default) Window : From start of event day (00:00) until event end + grace Example : Event: Dec 15 18:00 - 23:00 Check-in allowed: Dec 15 00:00 - 23:30 Use Case : Evening events with early arrival Security Features Device Fingerprinting Purpose : Prevent credential theft How It Works : Scanner generates fingerprint from device hardware Fingerprint sent with every request System validates fingerprint matches registered device Mismatch → Reject (credentials stolen) Fingerprint Components (example): const fingerprint = SHA256( deviceModel + osVersion + hardwareId + appInstallId ); JWT Credentials Scanner Credentials (1 year validity): { "scannerId": "uuid", "eventId": "uuid", "type": "scanner_credential", "iat": 1702300000, "exp": 1733836000 } Signed with : Event's RSA private key Used for : Scanner authentication to API Ticket JWT Verification Process : Scanner receives ticket JWT (QR code) Scanner verifies signature with event's public key Scanner can validate offline (no internet needed) Scanner sends to API for check-in recording Benefits : Offline validation capability Cannot forge tickets Cannot reuse tokens (duplicate tracking) Tamper-proof (signature validation) Auto-Revocation System The Rule One device → One ACTIVE scanner at a time (across ALL events) Scenarios Scenario 1: Device Registers for Different Event Device ABC has ACTIVE scanner for Event 1 Device ABC registers for Event 2 → System revokes Event 1 scanner automatically → Creates new scanner for Event 2 Scenario 2: Same Event Re-Registration Device ABC has ACTIVE scanner for Event 1 Device ABC registers again for Event 1 → System revokes old scanner → Creates new scanner (new credentials) Revocation Message : "Automatically revoked: Device registered as new scanner for event 'East African Tech Summit 2025'" Why This Rule? Prevents : Device scanning tickets for multiple events simultaneously Confusion about which event device is working Credential misuse across events Allows : Device switching between events (auto-handled) Fresh start for each event Clean scanner sessions Scanner Statistics Auto-Tracked Metrics totalScans : All scan attempts successfulScans : Valid entries failedScans : Duplicates, invalid tickets lastScanAt : Most recent scan timestamp lastSyncedAt : Last API contact Success Rate Calculation successRate = (successfulScans / totalScans) * 100 Updated Automatically Each validation attempt updates counters Successful → successfulScans++, totalScans++ Failed → failedScans++, totalScans++ Error Codes Summary Registration Token Errors 400 : Token expired/used, invalid format 403 : Not event organizer 404 : Token/event not found 422 : Event not published, no RSA keys Scanner Registration Errors 400 : Invalid fingerprint (too short/long) 400 : Token expired/used 404 : Token not found 422 : Invalid scanner name Ticket Validation Errors INVALID_SIGNATURE : JWT signature failed DUPLICATE : Already checked in for this day EXPIRED : Ticket validity expired NOT_FOUND : Ticket not in database REVOKED : Scanner revoked Best Practices For Organizers ✅ Generate tokens right before scanner setup ✅ Use descriptive scanner names (e.g., "Gate A - Main") ✅ Monitor scanner activity via dashboard ✅ Revoke suspicious scanners immediately ✅ Test scanner before event starts For Scanner App Developers ✅ Generate stable device fingerprint ✅ Store credentials securely (encrypted) ✅ Implement offline validation first ✅ Sync check-ins when online ✅ Show clear success/error messages ✅ Handle device fingerprint mismatch gracefully For Event Staff ✅ Keep scanners charged ✅ Verify scanner name matches gate ✅ Watch for duplicate warnings ✅ Report technical issues immediately ✅ Use backup manual verification if needed Quick Reference Token Lifetime Registration token: 5 minutes Scanner credentials: 1 year Ticket JWT: Event validity period Device Fingerprint Min length: 10 characters Max length: 255 characters Format: Stable hash of device properties Scanner Name Min length: 3 characters Max length: 200 characters Examples: "Gate A", "VIP Entrance", "Main Hall Scanner" Status Flow Registration Token: unused → used (one-time) Scanner: ACTIVE → REVOKED (permanent) Validation: VALID | DUPLICATE | INVALID_SIGNATURE | EXPIRED | NOT_FOUND | REVOKED Integration Guide Organizer Dashboard Integration // 1. Generate token POST /check-in/tokens/generate { eventId: "uuid", scannerName: "Gate A" } // 2. Display QR code // 3. Show token expiry countdown remainingTime = response.remainingSeconds // 4. List active scanners GET /check-in/scanners/event/{eventId}/active Scanner App Integration // 1. Scan registration QR code const token = extractTokenFromQR(qrData); // 2. Register device POST /check-in/scanners/register { registrationToken: token, deviceFingerprint: generateFingerprint(), scannerName: "Gate A", deviceInfo: JSON.stringify(deviceDetails) } // 3. Store credentials secureStorage.save('credentials', response.credentials); secureStorage.save('publicKey', response.publicKey); secureStorage.save('scannerId', response.scannerId); // 4. Validate tickets while (eventActive) { const ticketJWT = scanTicketQR(); // Offline validation const offlineValid = verifyJWT(ticketJWT, publicKey); if (offlineValid) { // Online check-in POST /check-in/validate { jwtToken: ticketJWT, scannerId: scannerId, deviceFingerprint: generateFingerprint(), checkInLocation: "Gate A" } } } Conclusion The Event Check-In System provides enterprise-grade security with: ✅ WhatsApp-Style Registration : Scan QR to link device ✅ Device Security : Fingerprint validation prevents theft ✅ Offline Capable : JWT verification without internet ✅ Multi-Day Support : Per-day check-in tracking ✅ Auto-Revocation : Clean scanner sessions per event ✅ Flexible Timing : 5 check-in window strategies ✅ Duplicate Prevention : Same-day re-entry blocked ✅ Real-Time Stats : Automatic success/fail tracking Organizer Analytics API Author : Josh, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Organizer Analytics API provides comprehensive financial and performance insights for event organizers. This API enables organizers to track total collections across all events, analyze revenue by event with filters, monitor individual event performance with detailed metrics, and visualize revenue trends over time with monthly/yearly breakdowns. The system calculates escrow holdings, released payments, ticket sales, attendance rates, and sell-out percentages automatically. Hints : Organizer Only : All endpoints restricted to event organizers Auto-Calculations : Revenue, attendance, sell-out rates computed automatically Escrow Tracking : Separate tracking for held vs released funds Multi-Event : Aggregates data across all organizer's events Time Filters : Filter by status, date range, year Pagination : Event lists paginated (default 20 per page) Top Performer : Identifies highest revenue event Trend Analysis : Monthly/yearly revenue patterns Real-Time : Updates reflect latest bookings and check-ins Response Structures CollectionSummaryResponse { "eventMetrics": { "totalEvents": 15, "upcomingEvents": 5, "ongoingEvents": 1, "completedEvents": 8, "cancelledEvents": 1 }, "collectionMetrics": { "totalTicketsSold": 2500, "totalRevenue": 5000000.00, "inEscrow": 1200000.00, "released": 3800000.00, "refunded": 0.00, "pendingRefunds": 0.00 }, "topEvent": { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "revenue": 1500000.00, "ticketsSold": 500, "attendanceRate": 92.5 } } EventRevenueResponse { "events": [ { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "eventDate": "2025-12-15T09:00:00", "status": "PUBLISHED", "ticketsSold": 500, "totalRevenue": 1500000.00, "inEscrow": 1500000.00, "released": 0.00, "refunded": 0.00, "attendanceRate": 0.0, "totalCapacity": 1000, "sellOutPercentage": 50.0 } ], "pagination": { "currentPage": 0, "pageSize": 20, "totalPages": 3, "totalElements": 45, "hasNext": true, "hasPrevious": false } } EventPerformanceResponse { "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "eventDate": "2025-12-15T09:00:00", "status": "COMPLETED", "financials": { "totalRevenue": 1500000.00, "inEscrow": 0.00, "released": 1500000.00, "refunded": 0.00, "averageTicketPrice": 3000.00 }, "ticketMetrics": { "totalCapacity": 1000, "totalSold": 500, "totalRemaining": 500, "sellOutPercentage": 50.0 }, "attendanceMetrics": { "totalTickets": 500, "checkedIn": 462, "noShows": 38, "attendanceRate": 92.4 }, "timeline": { "createdAt": "2025-10-01T10:00:00", "publishedAt": "2025-10-05T14:30:00", "firstSaleAt": "2025-10-06T09:15:00", "eventDate": "2025-12-15T09:00:00", "completedAt": "2025-12-15T18:00:00" } } RevenueTrendResponse { "period": "MONTHLY", "totalEvents": 12, "trends": [ { "label": "JAN", "year": 2025, "month": 1, "eventsCount": 2, "ticketsSold": 350, "revenue": 875000.00, "inEscrow": 0.00, "released": 875000.00, "averageAttendanceRate": 88.5, "averageSellOutRate": 62.0 } ] } Endpoints 1. Get Collection Summary Endpoint : GET /analytics/collections/summary Access : 🔒 Organizer Only Success Response : Returns CollectionSummaryResponse Success Response Message : "Collection summary retrieved" Behavior : Aggregates ALL organizer's events Calculates escrow (PUBLISHED, HAPPENING events) Calculates released (COMPLETED events) Identifies top performer by revenue Real-time metrics from latest data Metrics Included : Event counts by status Total tickets sold Total revenue (all time) Money in escrow (upcoming/ongoing) Money released (completed) Refunds (placeholder) Top performing event 2. Get Event Revenue Endpoint : GET /analytics/collections/by-event?status=PUBLISHED&startDate=2025-01-01&endDate=2025-12-31&page=0&size=20 Access : 🔒 Organizer Only Query Parameters : Parameter Type Required Description status string No Filter by event status (PUBLISHED, COMPLETED, etc.) startDate date (ISO) No Filter events from this date (2025-01-01) endDate date (ISO) No Filter events until this date (2025-12-31) page integer No Page number (0-indexed, default: 0) size integer No Items per page (default: 20) Success Response : Returns EventRevenueResponse with pagination Success Response Message : "Event revenue retrieved" Behavior : Lists organizer's events with revenue details Filters by status and date range Sorted by event date (newest first) Paginated results Includes capacity and sell-out metrics 3. Get Event Performance Endpoint : GET /analytics/performance/{eventId} Access : 🔒 Event Organizer Only Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Success Response : Returns EventPerformanceResponse Success Response Message : "Event performance retrieved" Behavior : Validates organizer owns event Calculates comprehensive metrics Shows financial breakdown Displays ticket sales vs capacity Tracks attendance from check-ins Provides event timeline Metrics Sections : Financials : Revenue, escrow, released, average price Tickets : Capacity, sold, remaining, sell-out % Attendance : Total, checked-in, no-shows, rate Timeline : Key dates from creation to completion Errors : 403 FORBIDDEN : Not event organizer 404 NOT_FOUND : Event not found 4. Get Revenue Trends Endpoint : GET /analytics/trends?period=MONTHLY&year=2025 Access : 🔒 Organizer Only Query Parameters : Parameter Type Required Description period string No MONTHLY or YEARLY (default: MONTHLY) year integer No Year to analyze (default: current year) Success Response : Returns RevenueTrendResponse Success Response Message : "Revenue trends retrieved" Behavior : MONTHLY Period : Shows all 12 months for specified year Data per month: events, tickets, revenue, escrow, released Calculates average attendance and sell-out rates YEARLY Period : Shows all years organizer has events Data per year: total events, tickets, revenue Year-over-year comparison Each Period Includes : Event count Tickets sold Total revenue Money in escrow Money released Average attendance rate Average sell-out rate Key Calculations Escrow vs Released In Escrow (Not yet paid to organizer): PUBLISHED events (upcoming) HAPPENING events (ongoing) Formula: Sum of booking totals for non-completed events Released (Paid to organizer): COMPLETED events only Formula: Sum of booking totals for completed events Example : Event A: PUBLISHED, Revenue: 500,000 TZS → In Escrow Event B: HAPPENING, Revenue: 300,000 TZS → In Escrow Event C: COMPLETED, Revenue: 1,200,000 TZS → Released Total Revenue: 2,000,000 TZS In Escrow: 800,000 TZS Released: 1,200,000 TZS Attendance Rate Attendance Rate = (Checked-In Tickets / Total Tickets) × 100 Example : Total tickets: 500 Checked-in: 462 Attendance: 92.4% Sell-Out Percentage Sell-Out % = (Tickets Sold / Total Capacity) × 100 Example : Capacity: 1000 Sold: 500 Sell-out: 50% Average Ticket Price Average Price = Total Revenue / Total Tickets Example : Revenue: 1,500,000 TZS Tickets: 500 Average: 3,000 TZS Event Status Flow DRAFT → PUBLISHED → HAPPENING → COMPLETED ↓ ↓ CANCELLED (Revenue Released) Financial Impact by Status : DRAFT : No bookings, no revenue PUBLISHED : Bookings allowed, revenue in escrow HAPPENING : Ongoing, revenue still in escrow COMPLETED : Revenue released to organizer CANCELLED : Refunds processed (if applicable) Top Performer Logic Selection Criteria : Highest total revenue Metrics Included : Event ID and title Total revenue (highest wins) Tickets sold Attendance rate Use Case : Dashboard highlight showing best-performing event Use Cases Dashboard Overview GET /analytics/collections/summary Shows: - Total events across all statuses - Total revenue (all time) - Current escrow balance - Released payments - Top performing event Event List with Filters GET /analytics/collections/by-event?status=COMPLETED&page=0&size=20 Shows: - All completed events - Revenue details per event - Attendance and sell-out rates - Paginated for easy navigation Deep Dive on Specific Event GET /analytics/performance/550e8400-e29b-41d4-a716-446655440000 Shows: - Complete financial breakdown - Ticket sales metrics - Attendance statistics - Event timeline Monthly Revenue Analysis GET /analytics/trends?period=MONTHLY&year=2025 Shows: - Revenue per month in 2025 - Event count trends - Attendance patterns - Sell-out trends Year-Over-Year Comparison GET /analytics/trends?period=YEARLY Shows: - Revenue by year - Growth trends - Performance evolution Best Practices For Organizers ✅ Check collection summary regularly ✅ Monitor escrow balance (upcoming payouts) ✅ Track attendance rates to improve future events ✅ Use trends to identify peak seasons ✅ Review individual event performance post-event For Developers ✅ Cache collection summary (refresh hourly) ✅ Paginate event lists (default 20 items) ✅ Display financial amounts clearly (currency formatting) ✅ Show percentage metrics with 1 decimal (92.4%) ✅ Provide export functionality for trends Quick Reference HTTP Status Codes 200 OK : Successful request 401 UNAUTHORIZED : Authentication required 403 FORBIDDEN : Not event organizer 404 NOT_FOUND : Event not found Date Formats Event Date : LocalDateTime (2025-12-15T09:00:00) Query Params : ISO Date (2025-01-01) Currency All amounts in TZS (Tanzanian Shilling) Format: 1500000.00 (2 decimals) Percentage Format Format: 92.4 (1 decimal) Range: 0.0 - 100.0 Pagination Zero-indexed pages (0, 1, 2...) Default size: 20 Max size: 100 (recommended) Conclusion The Organizer Analytics API provides comprehensive insights with: ✅ Collection Summary : Total revenue, escrow, and top performers ✅ Event Revenue : Filterable, paginated event list ✅ Event Performance : Deep dive into individual event metrics ✅ Revenue Trends : Monthly/yearly pattern analysis ✅ Real-Time : Auto-calculated from latest bookings ✅ Organizer-Focused : Restricted to event owners only Events Applicant Form API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-05-22 Version : v1.2 Base URL : https://your-api-domain.com/api/v1/e-events/applicant-form Short Description : The Applicant Form API allows event organizers to attach an optional custom multi-page form to their event. When enabled, attendees are prompted to fill in the form as part of the registration flow. The form is fully scoped to the event — all endpoints use eventId as the primary key, so the organizer never needs to track a separate formId . The entire feature is optional; if never enabled, the event registration proceeds with no form step. Hints : All endpoints require a valid Bearer token. Form setup and management (enable, pages, fields, options) require the authenticated user to be the event organizer . Any other user gets 403 . Attendee submission endpoints ( /start , /pages/{pageId}/save , /submit , /my-response ) require the event to be PUBLISHED and within its registration window ( registrationOpensAt → registrationClosesAt ). enableForm creates the underlying form with no pages . Pages must be added by the organizer via the page endpoints. The form must have at least one page before the event can be published. Use GET /events/{eventId}/full-form to load the complete form state (all pages, fields, and options) in a single call — intended for the form builder UI. startSubmission is idempotent — if the attendee already has a draft response it returns the existing one instead of creating a new one. savePage accepts a ?moveToNextPage=false/true query parameter that controls validation behaviour. When false (default), answers are written to the draft with no validation — use this for background auto-save. When true , the page is validated first; if any field fails, errors are returned and nothing is saved; if all fields pass, answers are saved, the page is marked complete, and the index advances to the next page. This is the "Next" button action. submit requires every page to have been completed via ?moveToNextPage=true at least once. Pages that were only auto-saved (without passing "Next" validation) will block submission with a PAGE_INCOMPLETE error. This ensures every page was validated before the form is accepted. Delete endpoints accept a ?hard=false query parameter. Soft delete ( hard=false , default) preserves historical response data. Hard delete ( hard=true ) permanently removes the record. DROPDOWN , RADIO , and CHECKBOX field types require options to be added separately after the field is created. The HEADER field type is a display-only section divider — it has no required flag and stores no answer data. Form Structure — Layers ┌─────────────────────────────────────────────────────────────────────────┐ │ FORM │ │ title · description · settings · cover page │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ PAGE 1 │ │ │ │ title · description · action button text │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │ │ FIELD (e.g. TEXT, EMAIL, DATE, FILE, RATING …) │ │ │ │ │ │ label · placeholder · required · validation rules │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │ │ FIELD (DROPDOWN / RADIO / CHECKBOX) │ │ │ │ │ │ label · required │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ │ │ Option 1 │ │ Option 2 │ │ Option 3 │ … │ │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │ │ FIELD (HEADER — display only, no answer stored) │ │ │ │ │ │ label (section divider text) │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ PAGE 2 │ │ │ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ │ │ FIELD │ │ FIELD │ … │ │ │ │ └──────────────────────────┘ └──────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ PAGE N … │ │ │ └───────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ Rules at a glance: A form has one or more pages displayed in displayOrder . Each page has one or more fields displayed in displayOrder . Fields of type DROPDOWN , RADIO , or CHECKBOX must have options added after the field is created — no other field type has options. HEADER is the only field type that stores no answer and cannot be marked required. It acts purely as a section label between other fields. Every other field type ( TEXT , TEXTAREA , EMAIL , PHONE , NUMBER , URL , DATE , TIME , DATETIME , RATING , FILE ) stores exactly one answer per attendee. Organizer Workflow [Organizer] │ │ POST /events/{eventId}/enable ▼ ┌─────────────────────────────────────┐ │ Form ENABLED (empty) │ │ - Form created internally │ │ - No pages yet (pages: []) │ │ - Config saved (displayTime etc.) │ └──────────────┬──────────────────────┘ │ │ Load builder state (on re-open): │ GET /events/{eventId}/full-form │ │ Build the form structure: │ │ POST /events/{eventId}/pages │ POST /events/{eventId}/pages/{pageId}/fields │ POST /events/{eventId}/fields/{fieldId}/options │ (DROPDOWN / RADIO / CHECKBOX only) │ │ (Optional bulk / clone shortcuts) │ POST /events/{eventId}/pages/bulk │ POST /events/{eventId}/pages/{pageId}/fields/bulk │ POST /events/{eventId}/pages/{pageId}/clone │ POST /events/{eventId}/fields/{fieldId}/clone │ │ Preview before publishing: │ GET /events/{eventId}/preview/metadata │ GET /events/{eventId}/preview/pages/{pageNumber} │ POST /events/{eventId}/preview/pages/{pageNumber}/validate ▼ ┌─────────────────────────────────────┐ │ Form ready — event can publish │ │ ⚠ Publish blocked if 0 pages │ └─────────────────────────────────────┘ Attendee Submission Journey [Attendee — event must be PUBLISHED and within registration window] │ │ POST /events/{eventId}/start ▼ ┌──────────────────────────────────────────────────┐ │ Response created (DRAFT) │ │ or existing draft returned (idempotent) │ │ status: DRAFT, completedPageIds: [], index: 0 │ └──────────────┬───────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ For each page (repeat until all pages completed): │ │ │ │ │ │ [Background auto-save while typing] │ │ │ PUT /pages/{pageId}/save?moveToNextPage=false │ │ │ → No validation. Saves whatever is there. Always 200. │ │ │ → completedPageIds unchanged. │ │ │ │ │ │ [User clicks "Next"] │ │ │ PUT /pages/{pageId}/save?moveToNextPage=true │ │ │ ├─ Validation FAILS → returns errors, nothing │ │ │ │ saved, page stays, success: false │ │ │ └─ Validation PASSES → answers saved, page │ │ │ added to completedPageIds, index advances │ │ └──────────────────────────────────────────────────────────┘ │ │ POST /events/{eventId}/submit │ ├─ Any page NOT in completedPageIds → 422 PAGE_INCOMPLETE │ └─ All pages complete + required fields present → SUBMITTED ▼ ┌──────────────────────────────────────────────────┐ │ Response SUBMITTED │ │ status: SUBMITTED, completionTimeSeconds set │ └──────────────────────────────────────────────────┘ Response Status Flow DRAFT ──────────────────────────────► SUBMITTED │ │ │ (withdraw — not in this API) │ (organizer action — outside scope) ▼ ▼ WITHDRAWN UNDER_REVIEW → APPROVED / REJECTED Standard Response Format All API responses follow a consistent structure using the Globe Response Builder pattern. Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2025-02-17T10:30:45", "data": { } } Error Response Structure { "success": false, "httpStatus": "BAD_REQUEST", "message": "Error description", "action_time": "2025-02-17T10:30:45", "data": "Error description" } Standard Response Fields Field Type Description success boolean true for successful operations, false for errors httpStatus string HTTP status name (OK, CREATED, BAD_REQUEST, etc.) message string Human-readable description of the result action_time string ISO 8601 timestamp of when the response was generated data object / string Response payload on success; error detail on failure HTTP Method Badge Standards GET — Green ( #28a745 ) — Safe, read-only operations POST — Blue ( #007bff ) — Create new resources PUT — Yellow ( #ffc107 , black text) — Update / replace PATCH — Orange ( #fd7e14 ) — Partial update DELETE — Red ( #dc3545 ) — Remove resource Shared Response Object Definitions A. ApplicantFormSetupResponse Returned only by the Enable Form endpoint (Endpoint 1). Field Type Description config object The saved EventApplicantFormEntity — see config fields below config.id UUID Config record ID config.formId UUID Internal Form Builder form ID (not needed for subsequent API calls) config.displayTime string BEFORE_CHECKOUT or AFTER_CHECKOUT config.isRequiredOnline boolean Whether online buyers must complete the form config.applyToAtDoor boolean Whether walk-in attendees see the form config.createdAt ZonedDateTime Config creation timestamp config.updatedAt ZonedDateTime Last update timestamp form object Full FormResponse — see FormResponse definition below B. EventApplicantFormEntity (Config Object) Returned by the Update Settings endpoint. Field Type Description id UUID Config record ID formId UUID Internal Form Builder form ID displayTime string BEFORE_CHECKOUT or AFTER_CHECKOUT isRequiredOnline boolean Whether online buyers must complete the form applyToAtDoor boolean Whether walk-in attendees see the form createdAt ZonedDateTime ISO 8601 with offset updatedAt ZonedDateTime ISO 8601 with offset C. FormResponse (Full Form Object) Field Type Description formId UUID Form identifier (Form Builder internal ID) title string Auto-set to "Attendee Questions - {event title}" on enable description string Form description settings object Form open/close settings (see FormSettings below) settings.acceptResponses boolean Whether the form is accepting responses settings.allowMultipleSubmissions boolean Whether a user can submit more than once settings.responseStartTime Instant UTC datetime when responses open settings.responseDeadline Instant UTC datetime when responses close settings.allowSaveDraft boolean Whether respondents can save progress coverPage object Optional cover page config createdBy string Username of creator createdAt LocalDateTime Creation timestamp pages[] array Ordered list of PageResponse objects D. PageResponse Field Type Description pageId UUID Page identifier title string Page heading shown to attendee description string Subheading or instruction text displayOrder integer 1-based position within the form actionButtonText string Label for the next/submit button fields[] array Ordered list of FieldResponse objects (soft-deleted excluded) E. FieldResponse Field Type Description fieldId UUID Field identifier type string TEXT , TEXTAREA , EMAIL , PHONE , NUMBER , URL , DATE , TIME , DATETIME , DROPDOWN , RADIO , CHECKBOX , FILE , RATING , HEADER label string Field label. For HEADER type this is the section title description string Helper text shown below the field placeholder string Input placeholder text displayOrder integer 1-based order within the page required boolean Whether the field must be answered. Always false for HEADER validation object See FieldValidation below options[] array OptionResponse entries — only for DROPDOWN , RADIO , CHECKBOX FieldValidation Object Field Applicable To Description minLength / maxLength TEXT , TEXTAREA Character count constraints pattern / patternMessage TEXT Regex pattern and custom error message min / max NUMBER Numeric range minDate / maxDate DATE Date range ( YYYY-MM-DD ) minSelections / maxSelections CHECKBOX Selection count constraints maxSizeMb / accept FILE File size limit and accepted MIME types F. OptionResponse Field Type Description optionId UUID Option identifier label string Display label shown to attendee displayOrder integer 1-based order within the field G. FormResponseObject (Attendee Submission) Field Type Description responseId UUID Unique response identifier formId UUID Internal form ID submittedBy string Username of the attendee status string DRAFT , SUBMITTED , UNDER_REVIEW , APPROVED , REJECTED , WITHDRAWN completedPageIds array UUIDs of pages marked complete currentPageIndex integer 0-based index of current page startedAt LocalDateTime When the attendee started the form submittedAt LocalDateTime When the response was submitted completionTimeSeconds integer Total seconds from start to submit answers[] array See AnswerResponse below AnswerResponse Field Type Description answerId UUID Answer identifier fieldId UUID Field ID (null if field was hard-deleted) fieldLabel string Label snapshot — preserved even if field is later deleted fieldType string Type snapshot — preserved even if field is later deleted fieldDeleted boolean true when the source field has been soft-deleted value any Submitted value. String for text/single-choice, array of strings for checkbox, number for NUMBER/RATING answeredAt LocalDateTime When this answer was saved fileUrl / fileName / fileSize / fileType various File upload metadata (FILE fields only) H. BulkOperationResult Field Type Description successCount integer Number of items successfully processed failureCount integer Number of items that failed errors[] array Error message per failed item createdFields[] array FieldResponse objects (bulk field operations) createdPages[] array PageResponse objects (bulk page operations) I. BulkDeleteResult Field Type Description successCount integer Number of items successfully deleted failureCount integer Number of items that failed errors[] array Error message per failed item deletedIds[] array UUIDs of successfully deleted items J. SavePageResponse Returned by the Save Page Answers endpoint (Endpoint 27). The shape of the response is the same whether the save succeeded or validation failed — the client always reads saved and isValid to decide what to do next. Field Type Description eventId UUID The event ID saved boolean true if answers were actually written to the database. false if validation failed and nothing was saved savedAt LocalDateTime Timestamp of the save. null when saved is false isValid boolean true if all fields on the page passed validation. Mirrors saved errors array / null null when isValid is true . Array of FieldValidationError when validation failed errors[].fieldId UUID The field that failed validation errors[].fieldLabel string Label of the failing field (e.g. "Email" ) errors[].errorCode string Machine-readable error type: REQUIRED , INVALID_FORMAT , INVALID_TYPE , VALIDATION_FAILED errors[].message string Human-readable error description (e.g. "must be valid email" ) Note : totalFieldsOnPage , answeredFieldsOnPage , and overallProgress are reserved fields in the response object but are not currently populated — they will be null and omitted from the JSON ( @JsonInclude(NON_NULL) ). K. FormAnalytics Field Type Description formId UUID Form identifier formTitle string Form title stats.totalStarted integer Total responses started (including drafts) stats.totalDrafts integer Responses still in DRAFT stats.totalSubmitted integer Responses with SUBMITTED status stats.totalWithdrawn integer Withdrawn responses stats.completionRate double Submitted / totalStarted × 100 stats.dropOffRate double Inverse of completion rate stats.avgCompletionTimeSeconds double Mean time from start to submit stats.fastestTimeSeconds integer Fastest submission time stats.slowestTimeSeconds integer Slowest submission time fieldAnalytics[] array Per-field breakdown fieldAnalytics[].fieldId UUID Field ID fieldAnalytics[].fieldLabel string Field label snapshot fieldAnalytics[].fieldType string Field type snapshot fieldAnalytics[].fieldDeleted boolean Whether field is soft-deleted fieldAnalytics[].totalResponses integer Total answers for this field fieldAnalytics[].choiceDistribution[] array { option, count, percentage } — choice fields fieldAnalytics[].numericStats object { min, max, avg, median } — NUMBER/RATING fields fieldAnalytics[].textResponses[] array Raw text answers — TEXT/TEXTAREA fields dailySubmissions[] array { date, count } — submissions per day Standard Error Types Application-Level Exceptions (400–499) 400 BAD_REQUEST — Form already enabled for this event, field type does not support options, already submitted 401 UNAUTHORIZED — Missing, expired, or malformed Bearer token 403 FORBIDDEN — Authenticated but not the event organizer; or registration window not open/already closed 404 NOT_FOUND — Event not found, form not enabled, page/field/option not found, no response found 422 UNPROCESSABLE_ENTITY — Bean validation failures; or submit called when one or more pages were not completed via ?moveToNextPage=true ( PAGE_INCOMPLETE error type); or required field still unanswered at submit time ( REQUIRED error type). For save validation failures ( ?moveToNextPage=true ), the response returns HTTP 200 with success: false — not a 422 — because the client needs to display inline errors without treating it as a hard failure Server-Level Exceptions (500+) 500 INTERNAL_SERVER_ERROR — Unexpected runtime error Shared Error Response Examples 401 — Unauthorized: { "success": false, "httpStatus": "UNAUTHORIZED", "message": "Token has expired", "action_time": "2025-02-17T10:30:45", "data": "Token has expired" } 403 — Forbidden (not organizer): { "success": false, "httpStatus": "FORBIDDEN", "message": "Only organizer can manage form", "action_time": "2025-02-17T10:30:45", "data": "Only organizer can manage form" } 403 — Forbidden (registration closed): { "success": false, "httpStatus": "FORBIDDEN", "message": "Registration closed", "action_time": "2025-02-17T10:30:45", "data": "Registration closed" } 404 — Form not enabled: { "success": false, "httpStatus": "NOT_FOUND", "message": "Form not enabled", "action_time": "2025-02-17T10:30:45", "data": "Form not enabled" } Endpoints Form Setup 1. Enable Applicant Form Purpose : Enables the applicant form for an event. Internally creates a Form Builder form titled "Attendee Questions - {event title}" with no pages. Pages are added separately by the organizer. Only the event organizer can call this. Returns the empty form structure alongside the config. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/enable Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event to enable the form for Must be a valid UUID Request JSON Sample : { "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false } Request Body Parameters : Parameter Type Required Description Validation displayTime string No When attendees see the form in the UI flow Enum: BEFORE_CHECKOUT , AFTER_CHECKOUT . Defaults to BEFORE_CHECKOUT isRequiredOnline boolean No Whether online ticket buyers must complete the form before proceeding Defaults to false applyToAtDoor boolean No Whether at-door / walk-in attendees also see the form Defaults to false Success Response JSON Sample : { "success": true, "httpStatus": "CREATED", "message": "Applicant form enabled", "action_time": "2025-02-17T10:30:45", "data": { "config": { "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false, "createdAt": "2025-02-17T10:30:45+03:00", "updatedAt": null }, "form": { "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025", "description": null, "settings": null, "coverPage": null, "createdBy": "amina.hassan", "createdAt": "2025-02-17T10:30:45", "pages": [] } } } Success Response Fields : data is an ApplicantFormSetupResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer, or form is already enabled for this event 404 Event not found 2. Update Form Settings Purpose : Updates the form configuration for the event — displayTime , isRequiredOnline , and applyToAtDoor . Does not affect the form structure (pages/fields). All fields are optional. Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/settings Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 4. Get Full Form Purpose : Returns the complete form structure — all pages, fields, and options — in a single call. Intended for the form builder UI to load the current state when the organizer opens the builder. Only the event organizer can call this. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/full-form Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Form retrieved", "action_time": "2026-05-21T10:30:45", "data": { "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "title": "Attendee Questions - Dar es Salaam Jazz Festival 2025", "description": null, "settings": null, "coverPage": null, "createdBy": "amina.hassan", "createdAt": "2026-05-21T10:30:45", "pages": [ { "pageId": "1a2b3c4d-...", "title": "Personal Details", "description": null, "displayOrder": 1, "actionButtonText": "Next", "fields": [ { "fieldId": "c3d4e5f6-...", "type": "TEXT", "label": "Full Name", "description": "As it appears on your ID.", "placeholder": "e.g. Amina Hassan", "displayOrder": 1, "required": true, "validation": { "minLength": 2, "maxLength": 100 }, "options": [] }, { "fieldId": "d4e5f6a7-...", "type": "DROPDOWN", "label": "T-Shirt Size", "displayOrder": 2, "required": true, "validation": null, "options": [ { "optionId": "a1b2c3d4-...", "label": "Small (S)", "displayOrder": 1 }, { "optionId": "b2c3d4e5-...", "label": "Medium (M)", "displayOrder": 2 } ] } ] }, { "pageId": "2b3c4d5e-...", "title": "Dietary Requirements", "displayOrder": 2, "actionButtonText": "Submit", "fields": [] } ] } } Success Response Fields : data is a FormResponse with all pages, fields, and options fully nested. Soft-deleted items are excluded. Pages and fields are ordered by displayOrder . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled Page Management 4. Add Page Purpose : Appends a new blank page to the form. displayOrder is auto-assigned as the next position. Fields are added to the page separately. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/pages Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer 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 23. Get Preview Metadata Purpose : Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used by the attendee to render a progress indicator before filling. Also returns the attendee's current response state ( responseId , currentPageIndex , completedPageIds ) if they have an existing draft. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/metadata Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Metadata retrieved", "data": { "formId": "uuid", "formTitle": "Attendee Questions - Tech Summit 2025", "totalPages": 3, "pageNumbers": [ { "pageNumber": 1, "pageTitle": "Personal Info" }, { "pageNumber": 2, "pageTitle": "Experience" }, { "pageNumber": 3, "pageTitle": "Preferences" } ], "responseId": "uuid", "responseStatus": "DRAFT", "currentPageIndex": 1, "completedPageIds": ["page-uuid-1"] } } Success Response Fields : Field Description formId Internal form ID formTitle Form title totalPages Total active pages pageNumbers[].pageNumber 1-based page number pageNumbers[].pageTitle Page heading Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled 24. Preview Page by Number Purpose : Returns a specific page by its 1-based position number, including all its active fields and options. Used by attendees to load each page as they fill the form. Also accessible to the organizer for previewing before publishing. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber} Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageNumber integer Yes 1-based page position Must be ≥ 1 and ≤ total active pages Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Preview page retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "PageResponse object" } } Success Response Fields : data is a PageResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, form not enabled, or pageNumber out of range 25. Validate Preview Page Answers Purpose : Runs validation on a set of answers for a specific page number without creating or saving any data. Lets the organizer test field validation rules before the form goes live. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}/validate Access Level : 🔒 Protected (Organizer always; attendee when event is PUBLISHED) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageNumber integer Yes 1-based page number to validate against Must be ≥ 1 and ≤ total active pages Request JSON Sample : { "fieldId-fullname": { "value": "" }, "fieldId-email": { "value": "not-an-email" } } Request Body : A flat map of fieldId (UUID) → AnswerValue . AnswerValue has value (any), plus optional fileUrl , fileName , fileSize , fileType for FILE fields. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Validation complete", "action_time": "2025-02-17T10:30:45", "data": { "valid": false, "errors": [ { "pageId": "1a2b3c4d-...", "pageTitle": "Attendee Information", "fieldId": "fieldId-fullname", "fieldLabel": "Full Name", "errorMessage": "Full Name is required", "errorType": "REQUIRED" }, { "pageId": "1a2b3c4d-...", "pageTitle": "Attendee Information", "fieldId": "fieldId-email", "fieldLabel": "Email", "errorMessage": "Email must be valid email", "errorType": "INVALID_FORMAT" } ] } } Success Response Fields : Field Description data.valid true if all fields passed validation data.errors[].pageId Page UUID data.errors[].pageTitle Page title data.errors[].fieldId Field UUID data.errors[].fieldLabel Field label data.errors[].errorMessage Human-readable error data.errors[].errorType REQUIRED , INVALID_FORMAT , INVALID_TYPE , VALIDATION_FAILED Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found, form not enabled, or pageNumber out of range Attendee Submission 26. Start Submission Purpose : Initiates the attendee's form response session. Creates a new DRAFT response tied to this event's form. If the attendee already has a draft or submitted response for this event's form, the existing response is returned — this endpoint is idempotent. Requires the event to be PUBLISHED and within its registration window. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/start Access Level : 🔒 Protected (Any authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The published event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Form submission started", "action_time": "2025-02-17T10:30:45", "data": { "responseId": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b", "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "submittedBy": "john.doe", "status": "DRAFT", "completedPageIds": [], "currentPageIndex": 0, "startedAt": "2025-02-17T10:30:45", "submittedAt": null, "completionTimeSeconds": null, "answers": [] } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 403 Event not published, registration not yet open, or registration already closed 404 Event not found or form not enabled 27. Save Page Answers Purpose : Saves the attendee's answers for a specific page. Behaviour is controlled by the ?moveToNextPage query parameter which maps directly to two distinct client actions — background auto-save and the "Next" button. Endpoint : PUT /api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/save Access Level : 🔒 Protected (Attendee — response owner) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Content-Type string Yes application/json Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID pageId UUID Yes The page being answered Must be a valid UUID belonging to this event's form Query Parameters : Parameter Type Required Description Default moveToNextPage boolean No Controls validation and page advancement. See behaviour table below false moveToNextPage Behaviour Value Intended Use Validation What Happens on Success What Happens on Failure false Background auto-save while the user is typing None — answers are stored as-is regardless of format or required state Answers written. completedPageIds unchanged. Always success: true N/A — cannot fail true User clicks "Next" to advance to the next page Full — all fields on the page are validated (required + format + type) Answers written, page added to completedPageIds , currentPageIndex advances. success: true Nothing saved, nothing advanced. success: false with errors[] in data Key rule : A page only enters completedPageIds through a successful ?moveToNextPage=true call. submit will reject the form if any page is missing from completedPageIds . Request Body : A flat Map where each key is a fieldId . Key Type Description {fieldId} UUID (map key) The field being answered .value any Answer value. See answer value types below .fileUrl string FILE fields — uploaded file URL .fileName string FILE fields — original file name .fileSize long FILE fields — file size in bytes .fileType string FILE fields — MIME type Answer Value Types Field Type Expected Value TEXT , TEXTAREA , EMAIL , PHONE , URL String NUMBER Number (integer or decimal) DATE String — YYYY-MM-DD TIME String — HH:mm DATETIME String — ISO 8601 datetime DROPDOWN , RADIO String — UUID of selected option CHECKBOX Array of strings — UUIDs of selected options RATING Integer — 1 to 5 FILE null — metadata goes in fileUrl , fileName , etc. HEADER Omit entirely — no answer needed Example 1 — Auto-save ( moveToNextPage=false ) Request : PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false { "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" }, "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" } } Response — always 200 OK , always success: true , no validation applied: { "success": true, "httpStatus": "OK", "message": "Page saved", "action_time": "2026-05-22T14:30:00", "data": { "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c", "saved": true, "savedAt": "2026-05-22T14:30:00", "isValid": true, "errors": null } } Example 2 — User clicks "Next", validation fails ( moveToNextPage=true ) Request : PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true { "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" }, "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "notanemail@@bad" }, "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "" }, "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" } } Response — 200 OK but success: false . Nothing saved. Client shows inline errors and keeps user on the same page: { "success": false, "httpStatus": "OK", "message": "Validation failed", "action_time": "2026-05-22T14:31:00", "data": { "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c", "saved": false, "savedAt": null, "isValid": false, "errors": [ { "fieldId": "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e", "fieldLabel": "Email", "errorCode": "INVALID_FORMAT", "message": "must be valid email" }, { "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42", "fieldLabel": "Phone Number", "errorCode": "REQUIRED", "message": "Phone Number is required" } ] } } Example 3 — User clicks "Next", validation passes ( moveToNextPage=true ) Request : PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true { "091467dd-50f9-413f-aa5f-0feb92682945": { "value": "Eddie Murphy" }, "6246ef54-a1b1-4b06-bd7c-cc1bc7950b8e": { "value": "graphereddy@gmail.com" }, "956b1e2e-3e47-4ba9-8248-a6668305ca42": { "value": "0627489964" }, "7bfb4439-1d4d-43e5-a6df-a7dba86f45ea": { "value": "Male" } } Response — 200 OK , success: true . Page written, added to completedPageIds , index advances: { "success": true, "httpStatus": "OK", "message": "Page saved", "action_time": "2026-05-22T14:32:00", "data": { "eventId": "d7c52ae3-627f-42b4-a069-dd7c2d7cd78c", "saved": true, "savedAt": "2026-05-22T14:32:00", "isValid": true, "errors": null } } Error Codes in errors[].errorCode errorCode Meaning REQUIRED Field is marked required and no value was provided INVALID_FORMAT Value format is wrong (e.g. bad email, invalid phone pattern) INVALID_TYPE Value is the wrong data type for the field (e.g. passing a string to a DROPDOWN expecting a UUID) VALIDATION_FAILED Value fails a custom rule (minLength, maxLength, min, max, minSelections, etc.) Success Response Fields : data is a SavePageResponse . Possible Error Responses : Status Scenario 401 No or expired token 403 Registration window closed or event not published 404 Event not found, form not enabled, or no active draft found — call /start first 28. Get My Response Purpose : Returns the authenticated attendee's current response for the event's form — whether still in draft or already submitted. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/my-response Access Level : 🔒 Protected (Any authenticated attendee) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Response retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject" } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 404 No response found for this attendee on this event's form 29. Submit Purpose : Finalises and submits the attendee's response. The server performs a two-stage validation before accepting: first it checks that every page is in completedPageIds (meaning the attendee clicked "Next" and passed validation for each page), then it checks that all required fields still have answers. If either check fails the response is rejected with 422 and a list of field-level errors in data . On success, status transitions to SUBMITTED and completionTimeSeconds is recorded. Endpoint : POST /api/v1/e-events/applicant-form/events/{eventId}/submit Access Level : 🔒 Protected (Attendee — response owner) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Form submitted successfully", "action_time": "2026-05-22T14:35:00", "data": { "responseId": "8a067ebe-5eff-496c-a7eb-e3f8ad853b9f", "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "submittedBy": "john.doe", "status": "SUBMITTED", "completedPageIds": ["a64a2f29-428e-41fc-8d14-043841d857a2"], "currentPageIndex": 1, "startedAt": "2026-05-22T14:30:00", "submittedAt": "2026-05-22T14:35:00", "completionTimeSeconds": 312, "answers": [ "..." ] } } Success Response Fields : data is a FormResponseObject . data.status will be "SUBMITTED" . Error Response — page not completed via "Next" ( 422 ) : { "success": false, "httpStatus": "UNPROCESSABLE_ENTITY", "message": "Please complete all required pages", "action_time": "2026-05-22T14:34:00", "data": [ { "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2", "pageTitle": "Personal Info", "fieldId": null, "fieldLabel": null, "errorType": "PAGE_INCOMPLETE", "errorMessage": "Page not completed — please review all fields and click Next" } ] } Error Response — required field still unanswered ( 422 ) : { "success": false, "httpStatus": "UNPROCESSABLE_ENTITY", "message": "Please complete all required pages", "action_time": "2026-05-22T14:34:00", "data": [ { "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2", "pageTitle": "Personal Info", "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42", "fieldLabel": "Phone Number", "errorType": "REQUIRED", "errorMessage": "This field is required" } ] } Possible Error Responses : Status Scenario 401 No or expired token 403 Already submitted, or registration window closed 404 Event not found, form not enabled, or no draft response found 422 One or more pages not in completedPageIds ( PAGE_INCOMPLETE ); or a required field has no answer ( REQUIRED ). Error list is in data[] Organizer Response Management 30. Get Response by ID Purpose : Retrieves a specific attendee response by ID. Available to the event organizer only. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses/{responseId} Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID responseId UUID Yes The response to retrieve Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Response retrieved", "action_time": "2025-02-17T10:30:45", "data": { "...": "Full FormResponseObject" } } Success Response Fields : data is a FormResponseObject . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or response not found 31. Get All Responses Purpose : Returns a paginated list of all attendee responses for the event's form. For organizer review and export. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/responses Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Query Parameters : Parameter Type Required Description Validation Default page integer No Page number (0-based) Min: 0 0 size integer No Items per page Min: 1 20 Note : This endpoint uses 0-based pagination ( page=0 is the first page), matching the Spring Pageable default passed from the controller. Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Responses retrieved", "action_time": "2025-02-17T10:30:45", "data": { "content": [ "[ FormResponseObject entries ]" ], "totalElements": 48, "totalPages": 3, "first": true, "last": false, "empty": false } } Success Response Fields : data.content[] contains FormResponseObject entries. Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled 32. Get Analytics Purpose : Returns full submission analytics for the event's form — stats overview, per-field distributions, and daily submission trends. Endpoint : GET /api/v1/e-events/applicant-form/events/{eventId}/analytics Access Level : 🔒 Protected (Event organizer only) Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes The event Must be a valid UUID Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Analytics retrieved", "action_time": "2025-02-17T10:30:45", "data": { "formId": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "formTitle": "Attendee Questions - Dar es Salaam Jazz Festival 2025", "stats": { "totalStarted": 120, "totalDrafts": 18, "totalSubmitted": 95, "totalWithdrawn": 7, "completionRate": 79.2, "dropOffRate": 20.8, "avgCompletionTimeSeconds": 312.0, "fastestTimeSeconds": 45, "slowestTimeSeconds": 1890 }, "fieldAnalytics": [ { "fieldId": "c3d4e5f6-...", "fieldLabel": "Full Name", "fieldType": "TEXT", "fieldDeleted": false, "totalResponses": 95, "uniqueResponses": 95, "textResponses": ["Amina Hassan", "John Doe", "..."] }, { "fieldId": "e5f6a7b8-...", "fieldLabel": "Arrival method", "fieldType": "DROPDOWN", "fieldDeleted": false, "totalResponses": 95, "choiceDistribution": [ { "option": "By Car", "count": 42, "percentage": 44.2 }, { "option": "Public Transport", "count": 35, "percentage": 36.8 }, { "option": "On Foot", "count": 18, "percentage": 18.9 } ] } ], "dailySubmissions": [ { "date": "2025-07-15", "count": 12 }, { "date": "2025-07-16", "count": 28 } ] } } Success Response Fields : data is a FormAnalytics . Possible Error Responses : Status Scenario 401 No or expired token 403 Not the event organizer 404 Event not found or form not enabled Quick Reference — Endpoint Summary # Method Path Auth Who Description Form Setup 1 POST /events/{eventId}/enable 🔒 Organizer Enable form (empty — no pages) 2 PUT /events/{eventId}/settings 🔒 Organizer Update form display settings 3 DELETE /events/{eventId}/disable 🔒 Organizer Disable and remove form 4 GET /events/{eventId}/full-form 🔒 Organizer Load full form (all pages + fields) for builder Page Management 5 POST /events/{eventId}/pages 🔒 Organizer Add a page 6 PUT /events/{eventId}/pages/{pageId} 🔒 Organizer Update a page 7 DELETE /events/{eventId}/pages/{pageId}?hard=false 🔒 Organizer Delete a page (soft or hard) 8 POST /events/{eventId}/pages/bulk 🔒 Organizer Bulk add pages with fields 9 DELETE /events/{eventId}/pages/bulk?hard=false 🔒 Organizer Bulk delete pages 10 POST /events/{eventId}/pages/{pageId}/clone 🔒 Organizer Clone a page Field Management 11 POST /events/{eventId}/pages/{pageId}/fields 🔒 Organizer Add a field to a page 12 PUT /events/{eventId}/fields/{fieldId} 🔒 Organizer Update a field 13 DELETE /events/{eventId}/fields/{fieldId}?hard=false 🔒 Organizer Delete a field (soft or hard) 14 POST /events/{eventId}/pages/{pageId}/fields/bulk 🔒 Organizer Bulk add fields 15 DELETE /events/{eventId}/pages/{pageId}/fields/bulk?hard=false 🔒 Organizer Bulk delete fields 16 PATCH /events/{eventId}/fields/bulk 🔒 Organizer Bulk update fields 17 POST /events/{eventId}/fields/{fieldId}/clone 🔒 Organizer Clone a field Option Management 18 POST /events/{eventId}/fields/{fieldId}/options 🔒 Organizer Add option to a field 19 PUT /events/{eventId}/options/{optionId} 🔒 Organizer Update an option 20 DELETE /events/{eventId}/options/{optionId}?hard=false 🔒 Organizer Delete an option (soft or hard) 21 DELETE /events/{eventId}/fields/{fieldId}/options/bulk?hard=false 🔒 Organizer Bulk delete options 22 PATCH /events/{eventId}/options/bulk 🔒 Organizer Bulk update option labels Preview 23 GET /events/{eventId}/preview/metadata 🔒 Organizer + Attendee* Form page metadata + attendee progress 24 GET /events/{eventId}/preview/pages/{pageNumber} 🔒 Organizer + Attendee* Load a page by number (fields + options) 25 POST /events/{eventId}/preview/pages/{pageNumber}/validate 🔒 Organizer + Attendee* Validate answers without saving Attendee Submission 26 POST /events/{eventId}/start 🔒 Attendee Start form / get existing draft 27 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false 🔒 Attendee Auto-save answers (no validation) 27 PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=true 🔒 Attendee "Next" — validate page and advance if clean 28 GET /events/{eventId}/my-response 🔒 Attendee Get my response 29 POST /events/{eventId}/submit 🔒 Attendee Submit form (requires all pages completed via "Next") Organizer — Responses & Analytics 30 GET /events/{eventId}/responses/{responseId} 🔒 Organizer Get response by ID 31 GET /events/{eventId}/responses 🔒 Organizer Get all responses (paginated) 32 GET /events/{eventId}/analytics 🔒 Organizer Get form analytics * Attendee access to preview endpoints requires the event to be PUBLISHED . All paths are prefixed with /api/v1/e-events/applicant-form . Data Format Standards Concern Standard Timestamps ZonedDateTime — ISO 8601 with offset: 2025-02-17T10:30:45+03:00 Local timestamps LocalDateTime — ISO 8601 no offset: 2025-02-17T10:30:45 Dates YYYY-MM-DD — 2025-07-18 Times HH:mm — 18:00 IDs UUID v4 — 3fa85f64-5717-4562-b3fc-2c963f66afa6 Pagination (responses) 0-based page param, Spring Page wrapper Enums Uppercase: BEFORE_CHECKOUT , SUBMITTED , DROPDOWN , REQUIRED Attendance Analytics API Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-05-23 Version : v2.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Attendance Management API provides comprehensive attendee tracking and analytics for event organizers. Organizers can view overall attendance statistics with per-day and per-ticket-type breakdowns, browse a unified attendance list (present, absent, and partially attended in one view) with rich filtering, and drill into the full check-in history for any individual ticket. The system supports both single-day and multi-day events. Hints : Organizer Only : All endpoints restricted to the event organizer Unified List : Present and absent tickets are in one list — use ?status=PRESENT|ABSENT|PARTIALLY_ATTENDED to filter Ticket-Based Rows : Each row in the list is one ticket, not one person — one buyer can appear multiple times Attendee Number : Unique human-readable ticket ID = bookingReference + "-" + ticketSeries (e.g. EVT-A3F4B21C-VIP-0042 ) Buyer vs Attendee : buyer = who paid, attendee = who holds the ticket — can be different people Buyer Types : SYSTEM_USER = online purchase with account, AT_DOOR = walk-in name only (no account) Day Filtering : ?dayNumber is optional — omit for overall view, provide for per-day status Partial Attendance : PARTIALLY_ATTENDED only applies when no dayNumber filter is set on multi-day events Pagination : Default 20 per page Form Response : formResponseId on each attendee/ticket row links to the buyer's applicant form submission — null if the event had no form or the form was not yet submitted Standard Response Format All API responses follow a consistent structure using our Globe Response Builder pattern: Success Response Structure { "success": true, "httpStatus": "OK", "message": "Operation completed successfully", "action_time": "2026-05-23T10:30:45", "data": { } } Error Response Structure { "success": false, "httpStatus": "BAD_REQUEST", "message": "Error description", "action_time": "2026-05-23T10:30:45", "data": "Error description" } Standard Response Fields Field Type Description success boolean true for successful operations, false for errors httpStatus string HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.) message string Human-readable message describing the result action_time string ISO 8601 timestamp of when the response was generated data object Response payload for success, error string for failures HTTP Method Badge Standards GET - GET - Green (Safe, read-only operations) POST - POST - Blue (Create new resources) PUT - PUT - Yellow (Update/replace entire resource) PATCH - PATCH - Orange (Partial updates) DELETE - DELETE - Red (Remove resources) Endpoints 1. Get Attendance Summary Purpose : Returns overall attendance statistics for an event including per-day and per-ticket-type breakdowns. Always covers the full event — no filtering applied. Endpoint : GET /e-events/attendance/{eventId}/summary Access Level : 🔒 Protected — Event Organizer Only Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes Event identifier Must be a valid UUID of an existing event owned by the authenticated organizer Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Attendance summary retrieved", "action_time": "2026-05-23T10:30:45", "data": { "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "eventTitle": "East African Tech Summit 2026", "totalDays": 3, "eventSchedule": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2026-12-15" }, { "dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2026-12-16" }, { "dayNumber": 3, "dayName": "Day 3 - Closing", "date": "2026-12-17" } ], "overallStats": { "totalTickets": 500, "present": 310, "partiallyAttended": 45, "absent": 145, "attendanceRate": 71.0, "byDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "date": "2026-12-15", "totalTickets": 500, "checkedIn": 400, "absent": 100, "attendanceRate": 80.0, "status": "COMPLETED" }, { "dayNumber": 2, "dayName": "Day 2 - Conference", "date": "2026-12-16", "totalTickets": 500, "checkedIn": 355, "absent": 145, "attendanceRate": 71.0, "status": "ONGOING" }, { "dayNumber": 3, "dayName": "Day 3 - Closing", "date": "2026-12-17", "totalTickets": 500, "checkedIn": 0, "absent": 500, "attendanceRate": 0.0, "status": "UPCOMING" } ] }, "byTicketType": [ { "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7", "ticketTypeName": "VIP Pass", "totalSold": 100, "present": 75, "partiallyAttended": 10, "absent": 15, "attendanceRate": 85.0, "byDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "checkedIn": 90, "absent": 10, "attendanceRate": 90.0 } ] } ] } } Success Response Fields : Field Description data.eventId Event UUID data.eventTitle Event name data.totalDays Number of event days data.eventSchedule List of days with dayNumber , dayName , date data.overallStats.totalTickets Total tickets sold data.overallStats.present Tickets checked in for all days data.overallStats.partiallyAttended Tickets checked in for some days (multi-day only) data.overallStats.absent Tickets never checked in data.overallStats.attendanceRate ((present + partiallyAttended) / total) × 100 , 2 dp data.overallStats.byDay[].status COMPLETED , ONGOING , or UPCOMING data.byTicketType Same stats broken down per ticket type Error Response JSON Sample : { "success": false, "httpStatus": "NOT_FOUND", "message": "Event not found", "action_time": "2026-05-23T10:30:45", "data": "Event not found" } Standard Error Types : 401 UNAUTHORIZED : Missing or invalid token 403 FORBIDDEN : Authenticated user is not the event organizer 404 NOT_FOUND : Event does not exist 2. Get Attendance List Purpose : Returns a paginated, filterable list of all tickets for an event. Each row is one ticket. Present and absent tickets are unified in one list — use the status filter to narrow down. Endpoint : GET /e-events/attendance/{eventId}/list Access Level : 🔒 Protected — Event Organizer Only Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes Event identifier Must be a valid UUID of an existing event owned by the authenticated organizer Query Parameters : Parameter Type Required Description Validation Default status enum No Filter by attendance status PRESENT , ABSENT , PARTIALLY_ATTENDED All dayNumber integer No Filter for a specific event day Must be between 1 and totalDays None (overall) ticketTypeId UUID No Filter by ticket type Must belong to this event All types buyerType enum No Filter by how the ticket was purchased SYSTEM_USER , AT_DOOR All search string No Search by attendee name, email, or phone Partial match, case-insensitive None page integer No Page number (0-indexed) Min: 0 0 size integer No Items per page Min: 1 20 Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Attendance list retrieved", "action_time": "2026-05-23T10:30:45", "data": { "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "eventTitle": "East African Tech Summit 2026", "appliedFilters": { "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7", "ticketTypeName": "VIP Pass", "status": "ALL", "dayNumber": 1, "dayName": "Day 1 - Opening", "buyerType": "ALL", "search": null }, "summary": { "totalTickets": 100, "present": 90, "absent": 10, "partiallyAttended": null, "attendanceRate": 90.0 }, "attendees": [ { "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa8", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attendeeNumber": "EVT-A3F4B21C-VIP-0042", "ticketTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa7", "ticketType": "VIP Pass", "ticketSeries": "VIP-0042", "attendeeName": "John Doe", "attendeeEmail": "john@example.com", "attendeePhone": "+255712345678", "buyer": { "buyerName": "Jane Doe", "buyerEmail": "jane@example.com", "buyerId": "3fa85f64-5717-4562-b3fc-2c963f66afa9", "buyerType": "SYSTEM_USER" }, "bookingReference": "EVT-A3F4B21C", "pricePaid": 150.00, "attendanceStatus": "PRESENT", "checkInTime": "2026-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "scannerId": "3fa85f64-5717-4562-b3fc-2c963f66afb0" }, { "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afb1", "formResponseId": null, "attendeeNumber": "EVT-B5D2E12F-GENE-0101", "ticketType": "General Admission", "ticketSeries": "GENE-0101", "attendeeName": "Ali Hassan", "attendeeEmail": null, "attendeePhone": null, "buyer": { "buyerName": "Ali Hassan", "buyerEmail": null, "buyerId": null, "buyerType": "AT_DOOR" }, "bookingReference": "EVT-B5D2E12F", "pricePaid": 50.00, "attendanceStatus": "ABSENT", "checkInTime": null, "checkInLocation": null, "checkedInBy": null, "scannerId": null } ], "pagination": { "currentPage": 0, "pageSize": 20, "totalPages": 5, "totalElements": 100, "hasNext": true, "hasPrevious": false } } } Success Response Fields : Field Description data.appliedFilters Echo of all active filters so the client knows what was applied data.summary.totalTickets Total tickets matching the ticket type filter (unaffected by status/search) data.summary.present Tickets with PRESENT status under current filters data.summary.absent Tickets with ABSENT status under current filters data.summary.partiallyAttended null when dayNumber is active; count when viewing overall multi-day data.summary.attendanceRate ((present + partiallyAttended) / total) × 100 , 2 dp data.attendees[].ticketInstanceId Unique ticket instance UUID data.attendees[].formResponseId UUID of the buyer's applicant form response — null if the event had no form data.attendees[].attendeeNumber Unique readable ID = bookingReference + "-" + ticketSeries data.attendees[].attendanceStatus PRESENT , ABSENT , or PARTIALLY_ATTENDED data.attendees[].buyer.buyerType SYSTEM_USER (online) or AT_DOOR (walk-in) data.attendees[].buyer.buyerId Populated for SYSTEM_USER , null for AT_DOOR data.attendees[].checkInTime null when attendanceStatus is ABSENT data.pagination Standard pagination envelope Notes : When dayNumber is provided: attendanceStatus is PRESENT or ABSENT for that specific day only When dayNumber is omitted: attendanceStatus reflects the overall across all days ( PARTIALLY_ATTENDED possible) summary reflects the current filter scope, not the whole event partiallyAttended in summary is null when a dayNumber filter is active Error Response JSON Sample : { "success": false, "httpStatus": "NOT_FOUND", "message": "Invalid day 5. Valid: 1-3", "action_time": "2026-05-23T10:30:45", "data": "Invalid day 5. Valid: 1-3" } Standard Error Types : 401 UNAUTHORIZED : Missing or invalid token 403 FORBIDDEN : Authenticated user is not the event organizer 404 NOT_FOUND : Event not found / day number out of range / ticket type not found 3. Get Attendee Detail Purpose : Returns the full check-in history for a single ticket across all event days. Shows per-day status including upcoming days. Endpoint : GET /e-events/attendance/{eventId}/attendees/{ticketInstanceId} Access Level : 🔒 Protected — Event Organizer Only Authentication : Bearer Token Request Headers : Header Type Required Description Authorization string Yes Bearer Path Parameters : Parameter Type Required Description Validation eventId UUID Yes Event identifier Must be owned by the authenticated organizer ticketInstanceId UUID Yes Individual ticket instance identifier Must belong to a booking under this event Success Response JSON Sample : { "success": true, "httpStatus": "OK", "message": "Attendee detail retrieved", "action_time": "2026-05-23T10:30:45", "data": { "ticketInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa8", "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attendeeNumber": "EVT-A3F4B21C-VIP-0042", "attendeeName": "John Doe", "attendeeEmail": "john@example.com", "attendeePhone": "+255712345678", "ticketType": "VIP Pass", "ticketSeries": "VIP-0042", "bookingReference": "EVT-A3F4B21C", "pricePaid": 150.00, "overallStatus": "PARTIALLY_ATTENDED", "daysAttended": 2, "daysTotal": 3, "checkInsByDay": [ { "dayNumber": 1, "dayName": "Day 1 - Opening", "dayDate": "2026-12-15", "status": "CHECKED_IN", "checkInTime": "2026-12-15T09:15:00+03:00", "checkInLocation": "Main Gate", "checkedInBy": "Scanner Operator 1", "scannerId": "3fa85f64-5717-4562-b3fc-2c963f66afb0" }, { "dayNumber": 2, "dayName": "Day 2 - Conference", "dayDate": "2026-12-16", "status": "NOT_CHECKED_IN", "checkInTime": null, "checkInLocation": null, "checkedInBy": null, "scannerId": null }, { "dayNumber": 3, "dayName": "Day 3 - Closing", "dayDate": "2026-12-17", "status": "UPCOMING", "checkInTime": null, "checkInLocation": null, "checkedInBy": null, "scannerId": null } ] } } Success Response Fields : Field Description data.ticketInstanceId Unique ticket instance UUID data.formResponseId UUID of the buyer's applicant form response — null if the event had no form data.attendeeNumber Unique readable ID = bookingReference + "-" + ticketSeries data.overallStatus PRESENT (all days), PARTIALLY_ATTENDED (some), ABSENT (none) data.daysAttended Count of days this ticket was checked in data.daysTotal Total number of event days data.checkInsByDay[].status CHECKED_IN , NOT_CHECKED_IN , or UPCOMING data.checkInsByDay[].checkInTime null when not checked in or day is upcoming data.checkInsByDay[].checkedInBy Name of the scanner operator who processed the check-in Error Response JSON Sample : { "success": false, "httpStatus": "NOT_FOUND", "message": "Ticket not found", "action_time": "2026-05-23T10:30:45", "data": "Ticket not found" } Standard Error Types : 401 UNAUTHORIZED : Missing or invalid token 403 FORBIDDEN : Authenticated user is not the event organizer 404 NOT_FOUND : Event not found or ticket instance does not belong to this event Quick Reference Guide Attendance Status Values Value Meaning Available when PRESENT Checked in (all days if no day filter) Always ABSENT Never checked in Always PARTIALLY_ATTENDED Checked in some days but not all No dayNumber filter, multi-day events only Day Status Values Value Meaning CHECKED_IN Ticket was scanned on that day NOT_CHECKED_IN Day passed, ticket not scanned UPCOMING Day has not started yet Buyer Type Values Value buyerEmail buyerId Meaning SYSTEM_USER Populated Populated Bought online, has platform account AT_DOOR null null Walk-in sale, name only captured at door Common HTTP Status Codes 200 OK : Successful request 401 UNAUTHORIZED : Authentication required or token invalid 403 FORBIDDEN : Not the event organizer 404 NOT_FOUND : Event, ticket, or day number not found 500 INTERNAL_SERVER_ERROR : Unexpected server error Event Feedback API Author : Josh, Lead Backend Team Last Updated : 2025-12-11 Version : v1.0 Base URL : https://api.nexgate.com/api/v1 Short Description : The Event Feedback API enables attendees to rate and review events after attendance. This API provides a simple 5-star rating system with optional comments, prevents duplicate feedback, ensures only attendees (non-organizers) can submit reviews, and allows anyone to view event feedback with pagination. The system supports escrow release decisions based on feedback quality and helps organizers improve future events. Hints : One Feedback Per User : Each user can only submit one feedback per event Attendee Only : Event organizers cannot review their own events 5-Star Rating : Required rating from 1-5 stars Optional Comments : Text reviews up to 1000 characters Public Viewing : Anyone can view event feedback (paginated) Chronological Order : Newest feedback first Escrow Integration : Feedback may influence escrow release (future) Simple Model : Focus on rating quality over complex metrics Response Structure EventFeedbackResponse { "id": "550e8400-e29b-41d4-a716-446655440000", "eventId": "770e8400-e29b-41d4-a716-446655440002", "eventTitle": "East African Tech Summit 2025", "userId": "660e8400-e29b-41d4-a716-446655440001", "userName": "johndoe", "rating": 5, "comment": "Amazing event! The speakers were excellent and the venue was perfect. Definitely attending next year!", "createdAt": "2025-12-18T10:30:45Z" } Paginated Response { "success": true, "httpStatus": "OK", "message": "Feedbacks retrieved successfully", "action_time": "2025-12-11T10:30:45", "data": { "content": [ { "id": "uuid", "eventId": "uuid", "eventTitle": "East African Tech Summit 2025", "userId": "uuid", "userName": "johndoe", "rating": 5, "comment": "Great event!", "createdAt": "2025-12-18T10:30:45Z" } ], "pageable": { "pageNumber": 0, "pageSize": 20, "offset": 0 }, "totalElements": 145, "totalPages": 8, "last": false, "first": true, "numberOfElements": 20, "size": 20, "number": 0, "empty": false } } Endpoints 1. Create Feedback Endpoint : POST /feedbacks/event/{eventId} Access : 🔒 Authenticated Users (Non-Organizers) Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Request Headers : Header Type Required Description Authorization string Yes Bearer token Content-Type string Yes application/json Request Body : { "rating": 5, "comment": "Amazing event! The speakers were excellent and the venue was perfect. Definitely attending next year!" } Request Parameters : Parameter Type Required Description Validation rating integer Yes Star rating Min: 1, Max: 5 comment string No Text review Max: 1000 characters Success Response : Returns EventFeedbackResponse Success Response Message : "Feedback submitted successfully" HTTP Status Code : 201 CREATED Behavior : Validates user is authenticated Validates event exists Checks user is NOT the event organizer Checks user hasn't already submitted feedback Creates feedback record Returns created feedback Validation Rules : ✅ Rating must be 1-5 (integer) ✅ Comment optional, max 1000 chars ✅ One feedback per user per event ✅ Organizers cannot review own events ✅ Event must exist Standard Error Types : 400 BAD_REQUEST : Invalid rating (not 1-5) 401 UNAUTHORIZED : Not authenticated 403 FORBIDDEN : Organizer trying to review own event 404 NOT_FOUND : Event not found 409 CONFLICT : Already submitted feedback Error Response Examples : Invalid Rating (400): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Rating must be at least 1", "action_time": "2025-12-11T10:30:45", "data": "Rating must be at least 1" } Organizer Cannot Review (403): { "success": false, "httpStatus": "FORBIDDEN", "message": "Event organizers cannot submit feedback for their own events.", "action_time": "2025-12-11T10:30:45", "data": "Event organizers cannot submit feedback for their own events." } Already Submitted (409): { "success": false, "httpStatus": "CONFLICT", "message": "You have already provided feedback for this event", "action_time": "2025-12-11T10:30:45", "data": "You have already provided feedback for this event" } 2. Get Event Feedbacks Endpoint : GET /feedbacks/event/{eventId}?page=0&size=20 Access : 🔓 Public (No Authentication Required) Path Parameters : Parameter Type Required Description eventId string (UUID) Yes Event identifier Query Parameters : Parameter Type Required Description page integer No Page number (0-indexed, default: 0) size integer No Items per page (default: 20) Success Response : Returns Spring Page object with EventFeedbackResponse items Success Response Message : "Feedbacks retrieved successfully" HTTP Status Code : 200 OK Behavior : Returns paginated list of feedback Sorted by creation date (newest first) No authentication required (public access) Validates event exists Pagination Details : Zero-indexed pages (0, 1, 2...) Default page size: 20 Max page size: 100 (recommended) Includes total elements and pages Standard Error Types : 404 NOT_FOUND : Event not found Rating System Star Ratings (1-5) Rating Meaning Emoji Description 5 ⭐⭐⭐⭐⭐ Excellent 😍 Outstanding event, exceeded expectations 4 ⭐⭐⭐⭐ Very Good 😊 Great event, minor improvements possible 3 ⭐⭐⭐ Good 🙂 Satisfactory event, met expectations 2 ⭐⭐ Fair 😐 Below expectations, needs improvement 1 ⭐ Poor 😞 Disappointing event, significant issues Average Rating Calculation Average = Sum of all ratings / Number of feedbacks Example : Ratings: 5, 5, 4, 5, 3, 4, 5, 5, 4, 5 Total: 45 Count: 10 Average: 4.5 stars Display Format : 4.5 ⭐ (10 reviews) Comment Guidelines Recommended Comment Content Good Comments Include : ✅ What you enjoyed ✅ Specific highlights (speakers, venue, activities) ✅ Suggestions for improvement ✅ Overall experience summary Example Good Comments : "Amazing event! The keynote speakers were excellent and provided valuable insights. The venue was perfect and well-organized. Only suggestion would be to have longer lunch breaks. Will definitely attend next year!" "Great networking opportunities and diverse range of topics. Sound system could be improved in the main hall." "Well organized but sessions felt rushed. Would prefer fewer sessions with more time for Q&A." Avoid : ❌ Offensive language ❌ Personal attacks ❌ Spam or promotional content ❌ Off-topic comments Character Limit Maximum: 1000 characters Recommended: 100-500 characters Minimum: None (optional field) Access Control Who Can Submit Feedback? Allowed : ✅ Any authenticated user ✅ Attendees (purchased tickets) ✅ Non-attendees (also allowed currently) Not Allowed : ❌ Event organizers (for their own events) ❌ Users who already submitted feedback ❌ Unauthenticated users Future Restriction (Recommended): Only users who purchased tickets Requires checking booking history Prevents fake reviews Who Can View Feedback? Anyone (Public access): ✅ No authentication required ✅ Potential attendees researching events ✅ Event organizers viewing their reviews ✅ Platform admins Use Cases Submit Feedback After Event POST /feedbacks/event/{eventId} { "rating": 5, "comment": "Great event, highly recommend!" } User provides honest review after attending View Event Reviews Before Booking GET /feedbacks/event/{eventId}?page=0&size=20 Potential attendee checks reviews before purchasing tickets Sees average rating and recent comments Organizer Checks Feedback GET /feedbacks/event/{eventId}?page=0&size=20 Organizer views all feedback for their event Identifies areas for improvement Plans better future events Platform Quality Monitoring GET /feedbacks/event/{eventId}?page=0&size=20 Platform admin reviews feedback Identifies low-rated events May influence escrow release decisions Future Enhancements Planned Features 1. Verified Attendee Badge Only show "Verified Attendee" if user has booking Increases trust in reviews Current: Anyone can review (not implemented) 2. Average Rating Endpoint GET /feedbacks/event/{eventId}/summary { "averageRating": 4.5, "totalReviews": 145, "ratingDistribution": { "5": 90, "4": 35, "3": 15, "2": 3, "1": 2 } } 3. Helpful Votes Users can mark reviews as helpful Sort by most helpful Bubble up quality reviews 4. Organizer Response Organizers can reply to feedback Shows engagement and care Builds trust with future attendees 5. Escrow Integration Average rating influences escrow release Events <3 stars may require manual review Automatic release for 4+ stars Current: Not implemented Escrow Release Logic (Future) How Feedback May Affect Escrow High Ratings (4-5 stars average): ✅ Automatic escrow release Indicates successful event No manual review needed Medium Ratings (3-3.9 stars average): ⚠️ Manual review triggered Platform checks for issues May contact organizer Usually released after review Low Ratings (<3 stars average): ❌ Escrow release delayed Investigation required May require organizer explanation Possible refunds if serious issues Example : Event with 4.5 average rating (90% 5-star): → Escrow automatically released 24 hours after event Event with 2.8 average rating (50% 1-2 star): → Escrow held, manual review, contact organizer → Possible partial refunds if issues confirmed Current Status : Not implemented (all events release automatically) Best Practices For Attendees ✅ Submit honest, constructive feedback ✅ Mention specific positives and negatives ✅ Wait until after event to review ✅ Be respectful in comments ✅ Update review if organizer addresses issues (future) For Organizers ✅ Read all feedback carefully ✅ Identify patterns in complaints ✅ Thank reviewers for positive feedback (future) ✅ Address concerns in organizer response (future) ✅ Use feedback to improve future events For Platform ✅ Monitor feedback quality ✅ Flag suspicious reviews ✅ Use ratings in escrow decisions (future) ✅ Display average ratings prominently ✅ Encourage verified attendee reviews (future) Quick Reference HTTP Status Codes 200 OK : Feedbacks retrieved successfully 201 CREATED : Feedback submitted successfully 400 BAD_REQUEST : Invalid rating value 401 UNAUTHORIZED : Not authenticated 403 FORBIDDEN : Organizer reviewing own event 404 NOT_FOUND : Event not found 409 CONFLICT : Already submitted feedback Rating Range Minimum : 1 star (Poor) Maximum : 5 stars (Excellent) Type : Integer only (no half stars) Comment Length Minimum : 0 (optional) Maximum : 1000 characters Recommended : 100-500 characters Pagination Default Page : 0 (first page) Default Size : 20 items Max Size : 100 (recommended) Sort Order : Newest first (createdAt DESC) Data Formats Event ID : UUID format User ID : UUID format Created At : ISO 8601 timestamp (Instant) Rating : Integer (1-5) Integration Examples Submit Feedback Form const submitFeedback = async (eventId, rating, comment) => { const response = await fetch(`/api/v1/e-events/feedbacks/event/${eventId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ rating, comment }) }); if (response.status === 201) { alert('Thank you for your feedback!'); } else if (response.status === 409) { alert('You have already reviewed this event'); } else if (response.status === 403) { alert('Organizers cannot review their own events'); } }; Display Feedback List const loadFeedback = async (eventId, page = 0) => { const response = await fetch( `/api/v1/e-events/feedbacks/event/${eventId}?page=${page}&size=20` ); const data = await response.json(); const feedbacks = data.data.content; const averageRating = calculateAverage(feedbacks); displayFeedbacks(feedbacks, averageRating); }; const calculateAverage = (feedbacks) => { if (feedbacks.length === 0) return 0; const sum = feedbacks.reduce((acc, f) => acc + f.rating, 0); return (sum / feedbacks.length).toFixed(1); }; Star Rating Component const StarRating = ({ rating, onChange }) => { const stars = [1, 2, 3, 4, 5]; return (
{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